【BZOJ4784】仙人掌(ZJOI2017)-仙人掌判定+树形DP

测试地址:仙人掌
做法:本题需要用到仙人掌判定+树形DP。
首先如果原图就不是仙人掌了,那么再怎么加边肯定也成不了仙人掌,所以我们应该先判断原图是不是仙人掌。判定方法是:对于原图求出DFS树,对于所有不在DFS树中的边,连接的两点一定具有祖孙关系,它会覆盖中间这一段的路径,如果一条边被覆盖一次以上,就表示这条边处于两个或更多个简单环中,不符合仙人掌的定义,树上差分判断一下即可。
特判掉不是仙人掌的情况后,显然图中的环对答案没有贡献,所以我们把原图中的环全部删掉,然后留下了若干棵树,因为这些树在原图中实际上是连通的,如果在不同的树之间加边就不符合仙人掌的性质,所以我们只需要分别考虑这些树上加边的方案,然后乘法原理乘起来即可。
思考一下之后我们发现,往一棵树中加边的方案数,等价于在树中取若干条长度大于等于2的边不相交的路径的方案数。之所以是长度大于等于2,是因为本题中仙人掌的定义和平常略有不同——没有重边,所以你不能加一条重边上去。考虑一棵子树的方案,我们发现它会对上面产生影响的,是这样的一些方案:要么在这棵子树中已经自给自足,要么有一条长度为1的路径在根上等待上面的边的加入。所以我们把根为i的子树中自给自足的方案数设为f(i),会对上面产生影响的方案数为g(i),考虑如何转移。
对于自给自足的方案,我们实际上是在根到儿子的边里做一个匹配,即选定几对儿子,并对于每一对儿子,将两个儿子的方案连起来,那么令h(i)i个点做匹配的方案数,我们知道:
h(i)=h(i1)+(i1)h(i2)
边界条件为h(0)=h(1)=1。若点i不和其他点做匹配,方案数为h(i1),否则和一个其他点做匹配,有i1种方案,其他点做匹配的方案数为h(i2),所以得到上式。
因为上面我们说了,在根上的状态转移就是做匹配,进一步地我们有:
f(i)=h(num)×g(son)
其中soni的每一个儿子,numi的儿子个数。
那么怎么计算g(i)g(i)里有一部分是f(i),另一部分就是有一条长度为1的路径等待上面边加入的方案数了。我们选定这个长度为1的路径是在根与某一个儿子之间,对于每一个儿子都产生h(num1)×g(son)的方案数,因此我们有:
g(i)=f(i)+num×h(num1)×g(son)
这样我们就得到了状态转移方程,注意最底层的节点中f(i)=g(i)=1,因为它们没有儿子所以直接求h(num1)会溢出。还要注意BZOJ上没有但是原题面中有的一个非常重要的提示:因为T可能较大,注意初始化的时间复杂度。如果你用memset的话铁定TLE,必须用for循环初始化。
这样我们就完成了这一题,时间复杂度为O(m)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T,n,m;
int first[500010],tot,dep[500010],cnt[500010];
ll f[500010],g[500010],h[500010],ans;
bool visp[500010],vis[1000010],flag;
struct edge
{
    int v,next,id;
}e[2000010];

void insert(int a,int b,int id)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    e[tot].id=id;
    first[a]=tot;
}

int dfs(int v,int laste)
{
    int totcnt=0;
    visp[v]=1;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].id!=laste)
        {
            if (!visp[e[i].v])
            {
                vis[e[i].id]=1;
                dep[e[i].v]=dep[v]+1;
                totcnt+=dfs(e[i].v,e[i].id);
            }
            else if (dep[e[i].v]<dep[v])
                cnt[v]++,cnt[e[i].v]--;
        }
    totcnt+=cnt[v];
    if (totcnt==1) vis[laste]=0;
    if (totcnt>1) flag=1;
    return totcnt;
}

void dp(int v,int fa)
{
    visp[v]=1;
    ll prod=1,son=0;
    for(int i=first[v];i;i=e[i].next)
        if (vis[e[i].id]&&e[i].v!=fa)
        {
            dp(e[i].v,v);
            prod=prod*g[e[i].v]%mod;
            son++;
        }
    f[v]=h[son]*prod%mod;
    if (son) g[v]=(f[v]+son*h[son-1]%mod*prod%mod)%mod;
    else g[v]=f[v];
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);

        for(int i=1;i<=n;i++)
            first[i]=0;
        tot=0;
        for(int i=1;i<=m;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            insert(a,b,i),insert(b,a,i);
        }

        dep[1]=0;
        for(int i=1;i<=m;i++)
            vis[i]=0;
        for(int i=1;i<=n;i++)
            visp[i]=cnt[i]=0;
        flag=0;
        dfs(1,0);
        if (flag) {printf("0
");continue;}

        h[0]=h[1]=1;
        for(ll i=2;i<=n;i++)
            h[i]=(h[i-1]+(i-1)*h[i-2]%mod)%mod;
        for(int i=1;i<=n;i++)
            visp[i]=0;
        ans=1;
        for(int i=1;i<=n;i++)
            if (!visp[i]) dp(i,0),ans=ans*f[i]%mod;
        printf("%lld
",ans);
    }

    return 0;
}
原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793447.html