loj2250/bzoj4784/洛谷P3687 仙人掌 DP

题目分析

如果原图不是一个仙人掌,答案就是0.

对于一个环,环上的两个点,若分别连着不是该环上的点,点集为 S 1 S_1 S1 S 2 S_2 S2,那么 S 1 S_1 S1 S 2 S_2 S2之间不能连边。所以我们可以去掉所有环上的边,原图就变成了一个森林,对于每棵树单独考虑。

由于题目中的仙人掌要求没有重边,所以我们可以认为每一条树边都要被一条非树边覆盖,如果一条非树边只覆盖一条树边,则认为在连出来的仙人掌上这条树边没有被覆盖。显然不影响方案数。

对于一棵树,考虑一个点相邻的连通块之间是怎么覆盖的,就会发现贡献之和度数有关。对于点 x x x,由于它与儿子之间的边要被非树边覆盖(所谓儿子就是相邻点),所以要么两棵子树互相连边,要么一棵子树里连一条边到 x x x上。我先钦定 x x x的一个儿子,根据这个子树怎么连来DP,那么度数为 i i i的点,子树之间连边的方案数就是 g ( i ) = g ( i − 1 ) + ( i − 1 ) g ( i − 2 ) g(i)=g(i-1)+(i-1)g(i-2) g(i)=g(i1)+(i1)g(i2)

将所有点的贡献乘起来即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int mod=998244353,N=500005,M=2000005;
int T,n,m,tot,ans,tim;
int h[N],ne[M],to[M],g[N],du[N],inc[N],dfn[N],pre[N];
int qm(int x) {return x>=mod?x-mod:x;}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int dfs(int x,int las) {
	dfn[x]=++tim;
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		if(!dfn[to[i]]) {
			if(!dfs(to[i],x)) return 0;
			pre[to[i]]=x;
		}
		else if(dfn[to[i]]>dfn[x]) {
			int y=to[i];
			while(y!=x) {
				--du[y],--du[pre[y]];
				if(inc[y]) return 0;
				inc[y]=1,y=pre[y];
			}
			--du[x],--du[to[i]];
		}
	}
	return 1;
}
int main()
{
	int x,y;
	T=read();
	g[0]=g[1]=1;
	for(RI i=2;i<=500000;++i) g[i]=qm(g[i-1]+1LL*(i-1)*g[i-2]%mod);
	while(T--) {
		n=read(),m=read();
		tot=tim=0,ans=1;
		for(RI i=1;i<=n;++i) du[i]=h[i]=inc[i]=dfn[i]=0;
		for(RI i=1;i<=m;++i)
			x=read(),y=read(),add(x,y),add(y,x),++du[x],++du[y];
		if(!dfs(1,0)) {puts("0");continue;}
		for(RI i=1;i<=n;++i) ans=1LL*ans*g[du[i]]%mod;
		printf("%d\n",ans);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值