1. 程式人生 > >loj2250/bzoj4784/洛谷P3687 仙人掌 DP

loj2250/bzoj4784/洛谷P3687 仙人掌 DP

題目分析

如果原圖不是一個仙人掌,答案就是0.

對於一個環,環上的兩個點,若分別連著不是該環上的點,點集為 S 1 S_1 S 2

S_2 ,那麼 S 1 S_1 S 2
S_2
之間不能連邊。所以我們可以去掉所有環上的邊,原圖就變成了一個森林,對於每棵樹單獨考慮。

由於題目中的仙人掌要求沒有重邊,所以我們可以認為每一條樹邊都要被一條非樹邊覆蓋,如果一條非樹邊只覆蓋一條樹邊,則認為在連出來的仙人掌上這條樹邊沒有被覆蓋。顯然不影響方案數。

對於一棵樹,考慮一個點相鄰的連通塊之間是怎麼覆蓋的,就會發現貢獻之和度數有關。對於點 x

x ,由於它與兒子之間的邊要被非樹邊覆蓋(所謂兒子就是相鄰點),所以要麼兩棵子樹互相連邊,要麼一棵子樹裡連一條邊到 x x 上。我先欽定 x x 的一個兒子,根據這個子樹怎麼連來DP,那麼度數為 i i 的點,子樹之間連邊的方案數就是 g ( i ) = g ( i 1 ) + ( i 1 ) g ( i 2 ) g(i)=g(i-1)+(i-1)g(i-2)

將所有點的貢獻乘起來即可。

程式碼

#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;
}