zoukankan      html  css  js  c++  java
  • ●洛谷P3687 [ZJOI2017]仙人掌

    题链:

    https://www.luogu.org/problemnew/show/P3687
    题解:

    计数DP,树形DP。


    (首先对于这个图来说,如果初始就不是仙人掌,那么就直接输出0)
    然后由于本来图中就存在于环中的边,不可能再次被包含,
    所以图中的环就把这个图分为的若干颗树。
    那么答案就是分别求出每颗树的方案数并相乘。
    现在问题变为了求:把一颗树通过连边使得仍然是仙人掌的方案数。
    定义如下3个数组:
    f[u]:表示u这颗子树中没有一条从u到子树内某个的节点的路径可以向其它子树连边的方案数。
    g[u]:表示u这颗子树中有一条从u到子树内某个节点的路径可以其它子树连边的方案数。
    以及一个预处理的h[n]:
    表示有n个元素,每个元素可以选择另一个元素与其两两匹配或者该元素不与任何元素匹配的方案数。
    先看看h[n]的求法:
    h[n]=h[n-1]+h[n-2]*(n-1)
    即表明第n个元素要么独立,要么与另外(n-1)个元素中的某一个匹配。
    然后看看怎样DP:
    对于当前的子树的根u,v是其儿子节点,num是其儿子节点个数
    $$f[u]=sum g[v] imes h[num] $$
    因为num个儿子有h[num]种匹配方案数,所以乘上h[num]
    (所谓的匹配就是把g[v]中的那条路径的末端和与它匹配的那个v'的g[v']中的那条路径的末端连边)

    然后还要求出当前的g[u]:
    $$g[u]=f[u]+sum g[v] imes h[num-1] imes num$$
    含义如下:
    由于u点自己可以成为g[u]中的那条路径的末端,所以$$+f[u]$$
    然后u还可以选择一个儿子g[son]中的路径来连上自己形成新的g[u]中的路径。
    这样的话,方案数是 $g[son]* sum_{v!=son} g[v] imes h[num-1] = sum g[v] imes h[num-1]$
    又因为u有num个儿子,即有num中选法,所以:
    $$+sum g[v] imes h[num-1] imes num $$

    代码:

    #include<bits/stdc++.h>
    #define MAXN 500005
    #define MOD 998244353
    #define rint register int
    using namespace std;
    int Case,N,M,dnt,cactus,ANS;
    int dfn[MAXN],lu[MAXN],fa[MAXN],f[MAXN],g[MAXN],h[MAXN];
    struct node{
    	int id,odr;
    	bool operator < (const node &rtm) const{
    		return odr<rtm.odr;
    	}
    }A[MAXN];
    struct Edge{
    	int ent;
    	int to[MAXN*4],nxt[MAXN*4],head[MAXN];
    	void Reset(int n){
    		for(rint i=1;i<=n;i++) head[i]=0; ent=2;
    	}
    	void Adde(int u,int v){
    		to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
    	}
    }E;
    void dfs(int u,int dad){
    	dfn[u]=++dnt; fa[u]=dad;
    	for(int e=E.head[u];e;e=E.nxt[e]){
    		int v=E.to[e];
    		if(dfn[v]) continue;
    		dfs(v,u);
    	}
    }
    void DP(int u,int rt){
    	lu[u]=-1; f[u]=1; int num=0,gson=1;
    	for(int e=E.head[u];e;e=E.nxt[e]){
    		int v=E.to[e];
    		if(lu[v]!=1||v==fa[u]) continue;
    		DP(v,0); num++;
    		gson=1ll*gson*g[v]%MOD;
    	}
    	f[u]=1ll*gson*h[num]%MOD;
    	g[u]=(1ll*f[u]+1ll*gson*h[num-1]%MOD*num)%MOD;
    }
    int main(){
    	h[0]=h[1]=1;
    	for(int i=1;i<=500000;i++) h[i]=(1ll*h[i-1]+1ll*h[i-2]*(i-1))%MOD;
    	for(scanf("%d",&Case);Case;Case--){
    		scanf("%d%d",&N,&M);
    		E.Reset(N); dnt=0; cactus=1; ANS=1;
    		for(rint i=1;i<=N;i++) lu[i]=dfn[i]=fa[i]=0;
    		for(int i=1,a,b;i<=M;i++)
    			scanf("%d%d",&a,&b),E.Adde(a,b),E.Adde(b,a);
    		dfs(1,0);
    		for(int e=1,u,v;e<=M;e++){
    			u=E.to[e*2]; v=E.to[e*2+1];
    			if(dfn[u]>dfn[v]) swap(u,v);
    			while(v!=u){
    				lu[v]++;
    				if(lu[v]>2){cactus=0;break;}
    				v=fa[v];
    			}
    		}
    		if(!cactus){printf("%d
    ",0); continue;}
    		for(int i=1;i<=N;i++) A[i]=(node){i,dfn[i]};
    		sort(A+1,A+N+1);
    		for(int i=1,u;i<=N;i++){
    			u=A[i].id;
    			if(lu[u]==-1) continue;
    			DP(u,1); ANS=1ll*ANS*f[u]%MOD; 
    		}
    		printf("%d
    ",ANS);
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    H5 + 3D + AR/VR 综述
    10中典型的软件开发模型
    总线宽度VS总线带宽
    最全程序设计流程、技术、工具、交付结果【软件全生命周期】
    一幅图读懂面向对象和面向过程程序设计的区别!
    C语言实现计算器,附源码,超简单!
    request.getRequestDispatcher()的两个方法forward()/include()!!!
    关于使用ResultSet ---结果集没有当前行
    JComboBox添加item的赋值类型问题!不一致的话会导致不能更改jcombobox的选择值
    关于String的两种赋值方式
  • 原文地址:https://www.cnblogs.com/zj75211/p/8541648.html
Copyright © 2011-2022 走看看