BZOJ4787/UOJ290 【ZJOI2017】仙人掌

本文版权归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\n"); 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\n",ans);
	}
}
 
int main()
{
    work();
    return 0;
}
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。

  

posted @ 2017-03-24 22:50  ljh_2000  阅读(1202)  评论(5编辑  收藏  举报