Luogu P3687 [ZJOI2017]仙人掌 题解

首先这题巨坑,我们来看一看题意:

给出一张简单无向连通图,求有多少种加边方案可以使得加边后这张图是一棵仙人掌。

注意这题没有保证给出的图是一个仙人掌!那么这就意味着我们还得先判断它是不是一个仙人掌。

也就是说我们需要判断一张图是不是一个仙人掌,然后将环上的边删去。
有一种简单的方法是用tarjan求边双连通分量,tarjan时判断这棵树是否是仙人掌,tarjan后再把两个端点不在同一连通分量中的边删去。

然后我们就能得到若干棵树了,对每棵树进行树形dp,在每个结点处考虑非树边的情况,可以选择某一个儿子往上连一条边,也可以将两个儿子配对连边。其中将儿子两两配对连边的方案数可以预处理,用递推的方法求:
\(g[i]=g[i-1]+(i-1)*g[i-2]\)

然后把每棵树根处的dp值乘起来就是最终的答案了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1000007
#define M 2000007
#define ll long long
const int inf=0x3f3f3f3f;
const ll mod=998244353;
int hd[N],pre[M],to[M],num,dfn[N],ord,low[N],cl[N],st[N],tp;
ll f[N][2],g[N];

bool vis[N],del[M],flag;
void adde(int x,int y)
{
	num++;pre[num]=hd[x];hd[x]=num;to[num]=y;
}
void tarjan(int v,int fa)
{
	low[v]=dfn[v]=++ord;
	st[++tp]=v;
	bool bl=0;
	for(int i=hd[v];i;i=pre[i])
	{
		int u=to[i];
		if(u==fa)continue;
		if(!dfn[u])
		{
			tarjan(u,v);
			if(low[u]<dfn[v])flag|=bl,bl=1;
			low[v]=min(low[v],low[u]);
		}
		else
		{
			if(dfn[u]<dfn[v])flag|=bl,bl=1;
			low[v]=min(low[v],dfn[u]);
		}
	}
	if(low[v]==dfn[v])
	{
		while(st[tp]!=v)cl[st[tp--]]=v;
		cl[st[tp--]]=v;
	}
}
void dp(int v,int fa)
{
	vis[v]=1;
	int s=0;
	ll sum=1;
	for(int i=hd[v];i;i=pre[i])
	{
		int u=to[i];
		if(u==fa||del[i])continue;
		dp(u,v);s++;
		sum=sum*(f[u][0]+f[u][1])%mod;
	}
	f[v][0]=sum*g[s]%mod;
	if(s)f[v][1]=s*sum%mod*g[s-1]%mod;
	else f[v][1]=0;
}
int main()
{
	//freopen("data.in","r",stdin);
	//freopen("test.out","w",stdout);
	int t,n,m,x,y;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		//n=100;
		ord=0;
		for(int i=1;i<=n;i++)hd[i]=0,vis[i]=0,dfn[i]=low[i]=cl[i]=0;
		g[0]=1;g[1]=1;
		for(int i=2;i<=n;i++)g[i]=(g[i-1]+(i-1)*g[i-2])%mod;
		//for(int i=1;i<=10;i++)printf("%lld\n",g[i]);return 0;	
		num=1;
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&x,&y);
			adde(x,y),adde(y,x);
		}
		for(int i=1;i<=num;i++)del[i]=0;
		flag=0;
		tarjan(1,0);
		if(flag)
		{
			printf("%d\n",0);
			continue;
		}
		ll ans=1;
		for(int v=1;v<=n;v++)
			for(int i=hd[v];i;i=pre[i])
				if(cl[v]==cl[to[i]])del[i]=1;
		for(int i=1;i<=n;i++)
		{
			if(!vis[i])
			{
				dp(i,0);
				ans=ans*f[i][0]%mod;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2020-04-10 16:09  lyyi2003  阅读(145)  评论(0编辑  收藏  举报