• 【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;
    }
  • 相关阅读:
    JavaAndroid项目结构
    Python 常用系统模块整理
    Python 部分系统类的常用方法整理
    xpath语法笔记
    xml笔记
    Python 内置函数笔记
    剑指Offer-二叉搜索树的第k个结点
    Java中Set集合是如何实现添加元素保证不重复的?
    剑指Offer-链表中倒数第k个结点
    Leetcode#1.Two Sum(两数之和)
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793447.html
Copyright © 2020-2023  润新知