zoukankan      html  css  js  c++  java
  • Luogu P3687 [ZJOI2017]仙人掌 题解

    首先这题巨坑,我们来看一看题意:

    给出一张简单无向连通图,求有多少种加边方案可以使得加边后这张图是一棵仙人掌。

    注意这题没有保证给出的图是一个仙人掌!那么这就意味着我们还得先判断它是不是一个仙人掌。

    也就是说我们需要判断一张图是不是一个仙人掌,然后将环上的边删去。
    有一种简单的方法是用tarjan求边双连通分量,tarjan时判断这棵树是否是仙人掌,tarjan后再把两个端点不在同一连通分量中的边删去。

    然后我们就能得到若干棵树了,对每棵树进行树形dp,在每个结点处考虑非树边的情况,可以选择某一个儿子往上连一条边,也可以将两个儿子配对连边。其中将儿子两两配对连边的方案数可以预处理,用递推的方法求:
    (g[i]=g[i-1]+(i-1)*g[i-2])

    然后把每棵树根处的dp值乘起来就是最终的答案了。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define N 1000007
    #define M 2000007
    #define ll long long
    const int inf=0x3f3f3f3f;
    const ll mod=998244353;
    int hd[N],pre[M],to[M],num,dfn[N],ord,low[N],cl[N],st[N],tp;
    ll f[N][2],g[N];
    
    bool vis[N],del[M],flag;
    void adde(int x,int y)
    {
    	num++;pre[num]=hd[x];hd[x]=num;to[num]=y;
    }
    void tarjan(int v,int fa)
    {
    	low[v]=dfn[v]=++ord;
    	st[++tp]=v;
    	bool bl=0;
    	for(int i=hd[v];i;i=pre[i])
    	{
    		int u=to[i];
    		if(u==fa)continue;
    		if(!dfn[u])
    		{
    			tarjan(u,v);
    			if(low[u]<dfn[v])flag|=bl,bl=1;
    			low[v]=min(low[v],low[u]);
    		}
    		else
    		{
    			if(dfn[u]<dfn[v])flag|=bl,bl=1;
    			low[v]=min(low[v],dfn[u]);
    		}
    	}
    	if(low[v]==dfn[v])
    	{
    		while(st[tp]!=v)cl[st[tp--]]=v;
    		cl[st[tp--]]=v;
    	}
    }
    void dp(int v,int fa)
    {
    	vis[v]=1;
    	int s=0;
    	ll sum=1;
    	for(int i=hd[v];i;i=pre[i])
    	{
    		int u=to[i];
    		if(u==fa||del[i])continue;
    		dp(u,v);s++;
    		sum=sum*(f[u][0]+f[u][1])%mod;
    	}
    	f[v][0]=sum*g[s]%mod;
    	if(s)f[v][1]=s*sum%mod*g[s-1]%mod;
    	else f[v][1]=0;
    }
    int main()
    {
    	//freopen("data.in","r",stdin);
    	//freopen("test.out","w",stdout);
    	int t,n,m,x,y;
    	scanf("%d",&t);
    	while(t--)
    	{
    		scanf("%d%d",&n,&m);
    		//n=100;
    		ord=0;
    		for(int i=1;i<=n;i++)hd[i]=0,vis[i]=0,dfn[i]=low[i]=cl[i]=0;
    		g[0]=1;g[1]=1;
    		for(int i=2;i<=n;i++)g[i]=(g[i-1]+(i-1)*g[i-2])%mod;
    		//for(int i=1;i<=10;i++)printf("%lld
    ",g[i]);return 0;	
    		num=1;
    		for(int i=1;i<=m;i++)
    		{
    			scanf("%d%d",&x,&y);
    			adde(x,y),adde(y,x);
    		}
    		for(int i=1;i<=num;i++)del[i]=0;
    		flag=0;
    		tarjan(1,0);
    		if(flag)
    		{
    			printf("%d
    ",0);
    			continue;
    		}
    		ll ans=1;
    		for(int v=1;v<=n;v++)
    			for(int i=hd[v];i;i=pre[i])
    				if(cl[v]==cl[to[i]])del[i]=1;
    		for(int i=1;i<=n;i++)
    		{
    			if(!vis[i])
    			{
    				dp(i,0);
    				ans=ans*f[i][0]%mod;
    			}
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Hash大法
    最小表示法
    KMP算法题集
    分块总结
    2018 雅礼国庆集训
    二分图总结
    贪心总结
    Tire树总结(模板+例题)
    工具类文章合集
    网文胡乱汇总
  • 原文地址:https://www.cnblogs.com/lishuyu2003/p/12674164.html
Copyright © 2011-2022 走看看