luogu P3687 [ZJOI2017]仙人掌 |树形dp

Posted naruto-mzx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu P3687 [ZJOI2017]仙人掌 |树形dp相关的知识,希望对你有一定的参考价值。

题目描述

如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过重复的结点的环。

技术图片

现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,所以她想要在图上连上一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。经过权衡,她想要加边后得到的图为一棵仙人掌。

不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。

两个加边方案是不同的当且仅当一个方案中存在一条另一个方案中没有的边。

输入格式

多组数据,第一行输入一个整数 (T) 表示数据组数。

每组数据第一行输入两个整数 (n),(m),表示图中的点数与边数。

接下来 m 行,每行两个整数 (u),(v)(1 ≤ (u),(v)(n),(u)(v)) 表示图中的一条边。保证输入的图联通且没有自环与重边。

输出格式

对于每组数据,输出一个整数表示方案数,当然方案数可能很大,请对 998244353 取模后输出。


用f[i]表示子树i的方案数,g[i]表示可向上拓展的子树的方案数,num表示子树的节点个数(即大小)

(f[i]=h[num]*prod g[son](sonin Child(i)))

每一个子节点都可以向上扩展并相对独立,然后一共有h[num]种儿子的匹配方案

(g[i]=f[i]+h[num-1]*num*prod g[son](sonin Child(i)))

该节点可以自己想上扩展为f[i],并且有num个子节点,每个子节点还可以选择一个儿子,并且一共有h[num-1]种匹配方案


#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+5,mod=998244353;
inline int read(){
    int x=0,f=1; char ch=getchar();
    while(ch>‘9‘||ch<‘0‘){ if(ch==‘-‘)f=-1; ch=getchar(); }
    while(ch>=‘0‘&&ch<=‘9‘){ x=x*10+ch-‘0‘; ch=getchar(); }
    return x*f;
}
int nxt[N<<1],head[N],go[N<<1],flag[N<<1],tot;
struct node{
	int u,v;
}e[N<<1];
inline void add(int u,int v){
	nxt[++tot]=head[u],head[u]=tot,go[tot]=v; e[tot]=(node){u,v}; flag[tot]=1;
	nxt[++tot]=head[v],head[v]=tot,go[tot]=u; e[tot]=(node){u,v}; flag[tot]=1;
}
int dfn[N],low[N],st[N],co[N],top,num,col;
#define ll long long
bool isca;
void Tarjan(int u,int fa){
	bool flag=0;
	dfn[u]=low[u]=++num;
	st[++top]=u;
	for(int i=head[u];i;i=nxt[i]){
		int v=go[i];
		if(v==fa)continue;
		if(!dfn[v]){
			Tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]<dfn[u]){
				if(flag){ isca=0; return; }
				flag=1;
			}
		}else {
			low[u]=min(low[u],dfn[v]);
			if(dfn[v]<dfn[u]){
				if(flag){ isca=0; return; }
				flag=1;
			}
		}
	}
	if(low[u]==dfn[u]){
		co[u]=++col;
		while(st[top]!=u){
			co[st[top]]=col;
			--top;
		}
		--top;
	}
}
ll h[N],f[N],g[N];
bool vis[N];
void dfs(int u,int fa){
	vis[u]=1,f[u]=1,g[u]=0;
	int sum=0;
	for(int i=head[u];i;i=nxt[i]){
		int v=go[i];
		if(!flag[i]||v==fa)continue;
		dfs(v,u);
		f[u]=f[u]*g[v]%mod;
		sum++;
	}
	g[u]=(f[u]*h[sum]%mod+f[u]*h[sum-1]%mod*sum%mod)%mod;
	f[u]=f[u]*h[sum]%mod;
}

signed main(){
	h[0]=h[1]=1;
	for(int i=2;i<=N-5;i++)h[i]=(h[i-1]+(h[i-2]*(i-1)%mod))%mod;
	for(int T=read();T;T--){
		int n=read(),m=read();
		isca=1; tot=top=col=num=0; 
		for(int i=1;i<=n;i++)head[i]=co[i]=dfn[i]=low[i]=vis[i]=0;
		for(int i=1;i<=m;i++)add(read(),read());
		Tarjan(1,0);
		for(int i=1;i<=tot;i++)if(co[e[i].u]==co[e[i].v])flag[i]=0;
		if(!isca){ printf("0
"); continue; }
		ll ans=1;
		for(int i=1;i<=n;i++){
			if(vis[i])continue;
			dfs(i,0);
			ans=ans*f[i]%mod;	
		}
		for(int i=1;i<=tot;i++)nxt[i]=0;
		printf("%lld
",ans);
	}
}

以上是关于luogu P3687 [ZJOI2017]仙人掌 |树形dp的主要内容,如果未能解决你的问题,请参考以下文章

ZJOI2017仙人掌

[ZJOI2017]仙人掌

tarjan+dp——luoguP3687 [ZJOI2017]仙人掌

tarjan+dp——luoguP3687 [ZJOI2017]仙人掌

「ZJOI2017」仙人掌

[BZOJ4784][ZJOI2017]仙人掌(树形DP)