zoukankan      html  css  js  c++  java
  • 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);
    	}
    }
    
  • 相关阅读:
    【Android Developers Training】 85. 不要有冗余的下载
    【Android Developers Training】 84. 将定期更新的影响最小化
    【Android Developers Training】 83. 实现高效网络访问来优化下载
    【Android Developers Training】 82. 序言:传输数据时减少对电池寿命的影响
    【Android Developers Training】 81. 解析XML数据
    Linux下C程序进程地址空间布局[转]
    GNOME keyring [(null)] 的密码:
    Advanced Memory Allocation 内存分配进阶[转]
    Linux下进程信息的深入分析[转]
    安装juicer
  • 原文地址:https://www.cnblogs.com/naruto-mzx/p/13128931.html
Copyright © 2011-2022 走看看