【ZJOI2017】仙人掌

【ZJOI2017】仙人掌

img
img
img

参考博客:https://www.cnblogs.com/wfj2048/p/6636028.html

我们先求出(dfs)树(就是(dfs)一遍),然后问题就变成了树形(DP)

我们先判断无解:就用定义来判断,如果一条边出现在多个环里面就无解。

然后我们将所有在环上的边拆了,因为这些边不可能再出现在一个新的环中。于是我们得到了一个森林。

我们设(f_v)表示以(v)为根的子树得到仙人掌的方案数。(ans=prod_{v is root} f_v)

首先我们考虑子树之间不连边,容易得到:(f_v=prod f_{son_v}),也就是所有子树(f)之积。

然后我们观察子树之间的连边:一个子树最多只会连出去一条边,也就是只会两两配对连边。我们设(g_v)表示有(v)个子树两两之间连边的方案数,则:

[g_v=g_{v-1}+(v-1)*g_{v-2} ]

这个转移的意义是考虑第(v)个点连不连边,如果不连,方案数就是其他(v-1)个点连边的方案;如果连,就从之前的(v-1)个点中任选一个相连,剩下的再连边。

(|sn_v|)表示(v)的儿子个数。则(f_v=g_{|sn_v|}*prod f_{son_v})

我的理解是:假设(v)的两个儿子(a,b)的子树之间要连边,设(e_a,e_b)分别表示(a)(v)(b)(v)的边,那么我们一定是将之前覆盖了(e_a)(e_b)的两个环的起点(深度最深的那个点,如果没有,就是(a,b))连接在一起。所以答案是(g_{|sn_v|})乘上所有子树中连边的方案数。

但是(v)的子树中还可以向(v)的父亲连边。我们就把(v)的父亲也看做一个子节点。所以:

[egin{cases} f_v=g_{|sn_v|}*prod f_{sn_v} (v is not root)\ f_v=g_{|sn_v|+1}*prod f_{sn_v} (v is root) end{cases} ]

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 1000005

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;
int n,m;
int x[N],y[N];
struct graph {
	int cnt,to[N<<2],nxt[N<<2];
	int h[N<<1];
	void add(int i,int j) {
		to[++cnt]=j;
		nxt[cnt]=h[i];
		h[i]=cnt;
	}
	void Init() {
		cnt=0;
		for(int i=1;i<=n;i++) h[i]=0;
	}
}s;

ll f[N],g[N];
int dfn[N],id;
int dep[N],fa[N];
int tim[N];
void dfs(int v) {
	dfn[v]=++id;
	for(int i=s.h[v];i;i=s.nxt[i]) {
		int to=s.to[i];
		if(dfn[to]) continue ;
		fa[to]=v;
		dep[to]=dep[v]+1;
		dfs(to);
	}
}

bool cmp(int a,int b) {return dep[a]<dep[b];}

bool vis[N];
void DP(int v,int flag) {
	vis[v]=1;
	f[v]=1;
	int sn=0;
	for(int i=s.h[v];i;i=s.nxt[i]) {
		int to=s.to[i];
		if(tim[to]>1) continue ;
		if(to==fa[v]||fa[to]!=v) continue ;
		sn++;
		DP(to,0);
		f[v]=f[v]*f[to]%mod;
	}
	if(flag) f[v]=f[v]*g[sn]%mod;
	else f[v]=f[v]*g[sn+1]%mod;
}

int st[N];
void work() {
	for(int i=1;i<=m;i++) {
		int a=x[i],b=y[i];
		if(dfn[a]<dfn[b]) swap(a,b);
		while(a!=b) {
			tim[a]++;
			if(tim[a]>2) {
				cout<<0<<"
";
				return ;
			}
			a=fa[a];
		}
	}
	for(int i=1;i<=n;i++) st[i]=i;
	sort(st+1,st+1+n,cmp);
	ll ans=1;
	for(int i=1;i<=n;i++) {
		int now=st[i];
		if(vis[now]) continue ;
		DP(now,1);
		ans=ans*f[now]%mod;
	}
	cout<<ans<<"
";
}

void Init() {
	s.Init();
	for(int i=1;i<=n;i++) vis[i]=tim[i]=fa[i]=dfn[i]=dep[i]=0;
	id=0;
}

int main() {
	g[0]=g[1]=1;
	for(int i=2;i<=500005;i++) g[i]=(g[i-1]+g[i-2]*(i-1))%mod;
	int T=Get();
	while(T--) {
		n=Get(),m=Get();
		Init();
		for(int i=1;i<=m;i++) {
			x[i]=Get(),y[i]=Get();
			s.add(x[i],y[i]),s.add(y[i],x[i]);
		}
		dep[1]=1;
		dfs(1);
		work();
	}
	return 0;
}

原文地址:https://www.cnblogs.com/hchhch233/p/10573616.html