【HNOI2018】毒瘤
【HNOI2018】毒瘤




设\(f_{v,0}\)表示\(v\)的子树中\(v\)不选的方案数,\(f_{v,1}\)表示\(v\)选的方案数。
显然
f_{v,1}=\prod f_{sn,0}
\]
我们可以写成矩阵乘法的形式
\begin{bmatrix} f_{v,0}&f_{v,1}\\f_{v,0}& 0 \end{bmatrix}=\begin{bmatrix}f_{v,0}& f_{v,1}\end{bmatrix}
\]
然后我们就枚举非树边两端的点选与不选,用动态\(DP\)维护。
设非树边有\(k\)条,暴力枚举复杂度\(O(2^{2k}log^2N)\)。
但是我们发现,每条边只需要枚举其中一个点就好了。如果枚举为不选,那么另一个点就没有限制;如果必选,那么另一个点就不选。复杂度\(O(2^{k}log^2N)\)。
和【SDOI 2017】切树游戏一样要用一个类来处理除法中除\(0\)的情况。
代码:
#include<bits/stdc++.h>
#define ll long long
#define N 100005
using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
const ll mod=998244353;
ll ksm(ll t,ll x) {
	ll ans=1;
	for(;x;x>>=1,t=t*t%mod)
		if(x&1) ans=ans*t%mod;
	return ans;
}
int n,m;
struct road {
	int to,next;
}s[N<<1];
int h[N],cnt;
void add(int i,int j) {s[++cnt]=(road) {j,h[i]};h[i]=cnt;}
struct edge {
	int x,y;
}e[N];
int tot;
int FA[N];
int Getf(int v) {return FA[v]==v?v:FA[v]=Getf(FA[v]);}
int fa[N],size[N],son[N];
int top[N];
struct info {
	ll a,z;
	info() {a=1,z=0;}
	info(int x,int y) {a=x,z=y;}
	ll val() {return z?0:a;}
};
info operator *(info x,ll y) {
	if(!y) x.z++;
	else x.a=x.a*y%mod;
	return x;
}
info operator /(info x,ll y) {
	if(!y) x.z--;
	else x.a=x.a*ksm(y,mod-2)%mod;
	return x;
}
struct matrix {
	int a[2][2];
	void Init() {a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;}
};
matrix operator *(const matrix &x,const matrix &y) {
	matrix tem;
	tem.Init();
	tem.a[0][0]=(1ll*x.a[0][0]*y.a[0][0]+1ll*x.a[0][1]*y.a[1][0])%mod;
	tem.a[0][1]=(1ll*x.a[0][0]*y.a[0][1]+1ll*x.a[0][1]*y.a[1][1])%mod;
	tem.a[1][0]=(1ll*x.a[1][0]*y.a[0][0]+1ll*x.a[1][1]*y.a[1][0])%mod;
	tem.a[1][1]=(1ll*x.a[1][0]*y.a[0][1]+1ll*x.a[1][1]*y.a[1][1])%mod;
	return tem;
}
struct tree {
	int l,r;
	matrix w;
}tr[N<<2];
void update(int v) {tr[v].w=tr[v<<1|1].w*tr[v<<1].w;}
void build(int v,int l,int r) {
	tr[v].l=l,tr[v].r=r;
	if(l==r) return ;
	int mid=l+r>>1;
	build(v<<1,l,mid),build(v<<1|1,mid+1,r);
}
void dfs(int v) {
	size[v]=1;
	for(int i=h[v];i;i=s[i].next) {
		int to=s[i].to;
		if(to==fa[v]) continue ;
		fa[to]=v;
		dfs(to);
		size[v]+=size[to];
		if(size[son[v]]<size[to]) son[v]=to;
	}
}
matrix query(int v,int l,int r) {
	if(l<=tr[v].l&&tr[v].r<=r) return tr[v].w;
	int mid=tr[v].l+tr[v].r>>1;
	if(r<=mid) return query(v<<1,l,r);
	else if(l>mid) return query(v<<1|1,l,r);
	else return query(v<<1|1,l,r)*query(v<<1,l,r);
}
int dfn[N],lst[N],id;
int bot[N];
ll t[N];
info G[N][2];
void Modify(int v,int p) {
	if(tr[v].l>p||tr[v].r<p) return ;
	if(tr[v].l==tr[v].r) {
		matrix &w=tr[v].w;
		w.a[0][0]=G[lst[p]][0].val(),w.a[0][1]=G[lst[p]][1].val();
		w.a[1][0]=G[lst[p]][0].val(),w.a[1][1]=0;
		if(~t[lst[p]]) {
			w.a[0][t[lst[p]]^1]=w.a[1][t[lst[p]]^1]=0;
		}
		return ;
	}
	Modify(v<<1,p),Modify(v<<1|1,p);
	update(v);
}
void dfs2(int v,int tp) {
	dfn[v]=++id;
	lst[id]=v;
	top[v]=tp;
	bot[v]=v;
	if(son[v]) {
		dfs2(son[v],tp);
		bot[v]=bot[son[v]];
	}
	G[v][0]=G[v][1]=info(1,0);
	for(int i=h[v];i;i=s[i].next) {
		int to=s[i].to;
		if(to==son[v]||to==fa[v]) continue ;
		dfs2(to,to);
		matrix tem=query(1,dfn[to],dfn[bot[to]]);
		G[v][0]=G[v][0]*(tem.a[0][0]+tem.a[0][1]);
		G[v][1]=G[v][1]*tem.a[0][0];
	}
	Modify(1,dfn[v]);
}
int dep;
void Confirm(int v,int flag) {
	dep++;
	t[v]=flag;
	matrix tem;
	for(int i=top[v];fa[i];i=top[fa[i]]) {
		tem=query(1,dfn[i],dfn[bot[i]]);
		G[fa[i]][0]=G[fa[i]][0]/(tem.a[0][0]+tem.a[0][1]);
		G[fa[i]][1]=G[fa[i]][1]/tem.a[0][0];
	}
	Modify(1,dfn[v]);
	for(int i=top[v];fa[i];i=top[fa[i]]) {
		tem=query(1,dfn[i],dfn[bot[i]]);
		G[fa[i]][0]=G[fa[i]][0]*(tem.a[0][0]+tem.a[0][1]);
		G[fa[i]][1]=G[fa[i]][1]*tem.a[0][0];
		Modify(1,dfn[fa[i]]);
	}
}
vector<int>st;
vector<int>E[N];
int ban[N];
ll ans;
vector<int>ea,eb;
void solve(int now) {
	if(now==ea.size()) {
		matrix tem=query(1,dfn[1],dfn[bot[1]]);
		(ans+=tem.a[0][0]+tem.a[0][1])%=mod;
		return ;
	}
	int x=ea[now],y=eb[now];
	if(t[x]==0||t[x]==-1) {
		int pre=t[x];
		Confirm(x,0);
		solve(now+1);
		Confirm(x,pre);
	}
	if((t[x]==1||t[x]==-1)&&(t[y]==0||t[y]==-1)) {
		int prex=t[x],prey=t[y];
		Confirm(x,1),Confirm(y,0);
		solve(now+1);
		Confirm(x,prex),Confirm(y,prey);
	}
}
int f[N][2];
void DP(int v,int fr) {
	f[v][0]=f[v][1]=1;
	for(int i=h[v];i;i=s[i].next) {
		int to=s[i].to;
		if(to==fr) continue ;
		DP(to,v);
		f[v][0]=f[v][0]*(f[to][0]+f[to][1])%mod;
		f[v][1]=f[v][1]*f[to][0]%mod;
	}
}
int main() {
	memset(t,-1,sizeof(t));
	n=Get(),m=Get();
	int a,b;
	for(int i=1;i<=n;i++) FA[i]=i;
	for(int i=1;i<=m;i++) {
		a=Get(),b=Get();
		if(Getf(a)==Getf(b)) {
			e[++tot]=(edge) {a,b};
			ea.push_back(a);
			eb.push_back(b);
			st.push_back(a);
			st.push_back(b);
			E[a].push_back(b);
			E[b].push_back(a);
		} else {
			add(a,b),add(b,a);
			FA[Getf(a)]=Getf(b);
		}
	}
	sort(st.begin(),st.end());
	int cc=unique(st.begin(),st.end())-st.begin();
	while(st.size()>cc) st.pop_back();
	build(1,1,n);
	dfs(1);
	dfs2(1,1);
	solve(0);
	cout<<ans;
	return 0;
}
【HNOI2018】毒瘤的更多相关文章
- 【BZOJ5287】[HNOI2018]毒瘤(动态规划,容斥)
		[BZOJ5287][HNOI2018]毒瘤(动态规划,容斥) 题面 BZOJ 洛谷 题解 考场上想到的暴力做法是容斥: 因为\(m-n\le 10\),所以最多会多出来\(11\)条非树边. 如果就 ... 
- [bzoj5287] [HNOI2018]毒瘤
		题目描述 从前有一名毒瘤. 毒瘤最近发现了量产毒瘤题的奥秘.考虑如下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操作(比如区间加一个数,或者区间开平方),并支持询问区间和.毒瘤考虑了n ... 
- [HNOI2018]毒瘤
		Description 从前有一名毒瘤. 毒瘤最近发现了量产毒瘤题的奥秘.考虑如下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操作(比如区间加一个数,或者区间开平方),并支持询问区间和 ... 
- bzoj 5287: [Hnoi2018]毒瘤
		Description Solution \(dfs\) 出一棵生成树之后,多出来的边就都是反祖边了 把反祖边两个端点都拿出来,就会得到最多 \(k=2*(m-n+1)\) 个关键点 除了关键点以外的 ... 
- BZOJ.5287.[AHOI HNOI2018]毒瘤(虚树 树形DP)
		BZOJ LOJ 洛谷 设\(f[i][0/1]\)表示到第\(i\)个点,不选/选这个点的方案数.对于一棵树,有:\[f[x][0]=\prod_{v\in son[x]}(f[v][0]+f[v] ... 
- BZOJ5287 HNOI2018毒瘤(虚树+树形dp)
		显然的做法是暴力枚举非树边所连接两点的选或不选,大力dp.考场上写的是最暴力的O(3n-mn),成功比大众分少10分.容斥或者注意到某些枚举是不必要的就能让底数变成2.但暴力的极限也就到此为止. 每次 ... 
- HNOI2018毒瘤
		题面链接 luogu sol 这篇博是骗访问量的QwQ. 考虑树怎么做,简单容斥.诸如\(f[u][0]=\prod (f[v][0]+f[v][1]),f[u][1]=\prod f[v][0]\) ... 
- [BZOJ5287][HNOI2018]毒瘤(虚树DP)
		暴力枚举非树边取值做DP可得75. 注意到每次枚举出一个容斥状态的时候,都要做大量重复操作. 建立虚树,预处理出虚树上两点间的转移系数.也可动态DP解决. 树上倍增.动态DP.虚树DP似乎是这种问题的 ... 
- 【比赛】HNOI2018 毒瘤
		虚树+dp 直接看zlttttt的强大题解 zlttttt的题解看这里 #include<bits/stdc++.h> #define ui unsigned int #define ll ... 
- BZOJ 5287: [Hnoi2018]毒瘤 动态dp(LCT+矩阵乘法)
		自己 yy 了一个动态 dp 做法,应该是全网唯一用 LCT 写的. code: #include <bits/stdc++.h> #define ll long long #define ... 
随机推荐
- 获取json串里的某个属性值
			string jsonText = "{\"beijing\":{\"zone\":\"海淀\",\"zone_en\& ... 
- webpack  笔记
			webpack.config.json entry:入口,可有多个 devtool:'inline-source-map' source map,遇到错误时,追踪到原文件,而不是编译后的文件 ... 
- vb.net 水晶報表CrystalReport 動態設定資料庫來源
			沒有出現CrystalReportViewer時,須安裝CRforVS_13_0. 新增1個數據集,新增1個數據表,添加二列,列名要和資料庫名一樣. 修改目標Framework 修改app.confi ... 
- 33.QT-UTF8,GBK互转
			首先需要用到QString的静态成员函数来获取字符数组: QByteArray QString::toLocal8Bit () ; //获取字节数组对象 char * QByteArray::data ... 
- struts2_Action的三种实现方式
			1.普通java类 package com.ahd.action; public class HelloAction{ public String execute() throws Exception ... 
- 4.1 explain 之 id
			一.id 是什么 select 查询的序列化,包含一组数字,表示查询中执行select子句或操作的顺序 二.三种情况 a. id相同,执行顺序由上至下 b. 如果是子查询,id的序号会递增,id值越大 ... 
- Eclipse中SVN插件的安装和配置(离线安装)
			Eclipse利用svn的离线安装包进行配置svn,这种方式配置起来很简单,但是前提是必须下载可用的的svn离线包.因为有的从网上下载的svn离线包有问题. 第一步:下载svn离线包 我下载的是下面这 ... 
- python网络编程-udp
			目录 1. 创建socket 2. udp网络程序-发送数据 3. udp网络程序-接收数据 4. python3中的编码转换 5. udp端口绑定 1. 创建socket 在 Python 中 使用 ... 
- ITEXT5.5.8转html为pdf文档解决linux不显示中文问题
			在windows中支持中文,在linux中不显示中文. 解决方法:添加字体库 下载simsun.ttc字体文件,把这文件拷贝到Linux系统的 /usr/share/fonts/ 下就可以了. 
- 小tips:JS之break,continue和return这三个语句的用法
			break语句 break语句会使运行的程序立刻退出包含在最内层的循环或者退出一个switch语句.由于它是用来退出循环或者switch语句,所以只有当它出现在这些语句时,这种形式的break语句才是 ... 
