本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
题目链接:UOJ290
正解:$DP$+仙人掌
解题报告:
考虑环上的边,不可能在连边中再被覆盖,所以只需要考虑树边就好了。
把环拆掉,只剩下若干棵树,就是一个森林,最后把每棵树的答案用乘法原理合并起来就好了。
对于每个节点$u$,我们考虑他的子树的连边方案数如何统计。
如果我们强制每个结点的子树必须向外连一条边(显然最多一条),往上统计的话,
那么假设$u$的子树内没有向外连边,那么就是把儿子节点的$ans$乘起来。
如果向外连边了,就需要考虑互相连边的合法情况有多少种了。我们发现这个方案数只和儿子节点个数有关,可以很容易的用递推式来表示:
$g[n]=g[n-1]+g[n-2]*(n-1)$
预处理出$g$数组,每次对于每个节点先把儿子节点的$ans$全乘进来,接下来需要分类讨论节点$u$是不是一棵树的根。
如果是根的话,则不能向外连边,那么再乘上儿子节点个数的$g$就好了(相当于是组合了节点个数个点的互相连边方式);
否则,可以向外连边,那么把节点$u$本身也可以算进来,就是再乘上儿子节点个数$+1$的$g$。
//It is made by ljh2000 //有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> #include <vector> #include <queue> #include <map> #include <set> #include <string> #include <complex> #include <bitset> using namespace std; typedef long long LL; typedef long double LB; typedef complex<double> C; const double pi = acos(-1); const int MAXN = 500011; const int MAXM = 1000011; const int mod = 998244353; int n,m,ecnt,first[MAXN],to[MAXM],next[MAXM],father[MAXN],lu[MAXN],dfn[MAXN],deep[MAXN]; LL g[MAXN],f[MAXN],ans; struct node{ int id,x; }a[MAXN]; inline bool cmp(node q,node qq){ return q.x<qq.x; } inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; } inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } inline void dfs(int x,int fa){ dfn[x]=++ecnt; for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==fa) continue; if(!dfn[v]) father[v]=x,deep[v]=deep[x]+1,dfs(v,x); } } inline void dp(int x,bool rt){ lu[x]=-1; f[x]=1; int tot=0;//the number of son for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==father[x]) continue; if(lu[v]!=1) continue; tot++; dp(v,0); f[x]*=f[v]; f[x]%=mod; } if(tot==0) return ; if(!rt) f[x]*=g[tot+1],f[x]%=mod; else f[x]*=g[tot],f[x]%=mod; } inline void work(){ g[0]=g[1]=1; for(int i=2;i<=500001;i++) g[i]=g[i-1]+g[i-2]*(i-1),g[i]%=mod; int T=getint(); int x,y; bool ck; while(T--) { n=getint(); m=getint(); ecnt=1; for(int i=1;i<=n;i++) first[i]=father[i]=dfn[i]=deep[i]=lu[i]=0; for(int i=1;i<=m;i++) { x=getint(); y=getint(); link(x,y); link(y,x); } ecnt=0; deep[1]=1; dfs(1,0); ck=true; for(int i=1;i<=m;i++) {//统计lu数组(到根的路径条数),判断是否为仙人掌 x=to[i<<1]; y=to[i<<1|1]; if(dfn[x]<dfn[y]) swap(x,y); while(x!=y) { lu[x]++; if(lu[x]>2) { ck=false; break; } x=father[x]; } } if(!ck) { printf("0 "); continue; } for(int i=1;i<=n;i++) a[i].id=i,a[i].x=deep[i]; sort(a+1,a+n+1,cmp); ans=1; for(int i=1;i<=n;i++) { x=a[i].id; if(lu[x]!=-1) { dp(x,1); ans*=f[x]; ans%=mod; } } printf("%lld ",ans); } } int main() { work(); return 0; } //有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。