●洛谷P3687 [ZJOI2017]仙人掌

题链:

https://www.luogu.org/problemnew/show/P3687
题解:

计数DP,树形DP。


(首先对于这个图来说,如果初始就不是仙人掌,那么就直接输出0)
然后由于本来图中就存在于环中的边,不可能再次被包含,
所以图中的环就把这个图分为的若干颗树。
那么答案就是分别求出每颗树的方案数并相乘。
现在问题变为了求:把一颗树通过连边使得仍然是仙人掌的方案数。
定义如下3个数组:
f[u]:表示u这颗子树中没有一条从u到子树内某个的节点的路径可以向其它子树连边的方案数。
g[u]:表示u这颗子树中有一条从u到子树内某个节点的路径可以其它子树连边的方案数。
以及一个预处理的h[n]:
表示有n个元素,每个元素可以选择另一个元素与其两两匹配或者该元素不与任何元素匹配的方案数。
先看看h[n]的求法:
h[n]=h[n-1]+h[n-2]*(n-1)
即表明第n个元素要么独立,要么与另外(n-1)个元素中的某一个匹配。
然后看看怎样DP:
对于当前的子树的根u,v是其儿子节点,num是其儿子节点个数
$$f[u]=\sum g[v] \times h[num] $$
因为num个儿子有h[num]种匹配方案数,所以乘上h[num]
(所谓的匹配就是把g[v]中的那条路径的末端和与它匹配的那个v'的g[v']中的那条路径的末端连边)

然后还要求出当前的g[u]:
$$g[u]=f[u]+\sum g[v] \times h[num-1] \times num$$
含义如下:
由于u点自己可以成为g[u]中的那条路径的末端,所以$$+f[u]$$
然后u还可以选择一个儿子g[son]中的路径来连上自己形成新的g[u]中的路径。
这样的话,方案数是 $g[son]* \sum_{v!=son} g[v] \times h[num-1] = \sum g[v] \times h[num-1]$
又因为u有num个儿子,即有num中选法,所以:
$$+\sum g[v] \times h[num-1] \times num $$

代码:

 

#include<bits/stdc++.h>
#define MAXN 500005
#define MOD 998244353
#define rint register int
using namespace std;
int Case,N,M,dnt,cactus,ANS;
int dfn[MAXN],lu[MAXN],fa[MAXN],f[MAXN],g[MAXN],h[MAXN];
struct node{
	int id,odr;
	bool operator < (const node &rtm) const{
		return odr<rtm.odr;
	}
}A[MAXN];
struct Edge{
	int ent;
	int to[MAXN*4],nxt[MAXN*4],head[MAXN];
	void Reset(int n){
		for(rint i=1;i<=n;i++) head[i]=0; ent=2;
	}
	void Adde(int u,int v){
		to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
	}
}E;
void dfs(int u,int dad){
	dfn[u]=++dnt; fa[u]=dad;
	for(int e=E.head[u];e;e=E.nxt[e]){
		int v=E.to[e];
		if(dfn[v]) continue;
		dfs(v,u);
	}
}
void DP(int u,int rt){
	lu[u]=-1; f[u]=1; int num=0,gson=1;
	for(int e=E.head[u];e;e=E.nxt[e]){
		int v=E.to[e];
		if(lu[v]!=1||v==fa[u]) continue;
		DP(v,0); num++;
		gson=1ll*gson*g[v]%MOD;
	}
	f[u]=1ll*gson*h[num]%MOD;
	g[u]=(1ll*f[u]+1ll*gson*h[num-1]%MOD*num)%MOD;
}
int main(){
	h[0]=h[1]=1;
	for(int i=1;i<=500000;i++) h[i]=(1ll*h[i-1]+1ll*h[i-2]*(i-1))%MOD;
	for(scanf("%d",&Case);Case;Case--){
		scanf("%d%d",&N,&M);
		E.Reset(N); dnt=0; cactus=1; ANS=1;
		for(rint i=1;i<=N;i++) lu[i]=dfn[i]=fa[i]=0;
		for(int i=1,a,b;i<=M;i++)
			scanf("%d%d",&a,&b),E.Adde(a,b),E.Adde(b,a);
		dfs(1,0);
		for(int e=1,u,v;e<=M;e++){
			u=E.to[e*2]; v=E.to[e*2+1];
			if(dfn[u]>dfn[v]) swap(u,v);
			while(v!=u){
				lu[v]++;
				if(lu[v]>2){cactus=0;break;}
				v=fa[v];
			}
		}
		if(!cactus){printf("%d\n",0); continue;}
		for(int i=1;i<=N;i++) A[i]=(node){i,dfn[i]};
		sort(A+1,A+N+1);
		for(int i=1,u;i<=N;i++){
			u=A[i].id;
			if(lu[u]==-1) continue;
			DP(u,1); ANS=1ll*ANS*f[u]%MOD; 
		}
		printf("%d\n",ANS);
	}
	return 0;
}

 

  

 

posted @ 2018-03-10 19:39  *ZJ  阅读(343)  评论(1编辑  收藏  举报