• 【HEOI2013】SAO 题解(树形DP+组合数学)


    部分内容参考自Miracle的博客,感谢。

    题目大意:给定一张树形拓扑图,求其拓扑序个数。

    对DAG求拓扑序是NP问题,然而这是一张树形图,这启发我们尝试用树形DP解决此问题。

    一开始的想法是定义$f_i$表示以$i$为根的子树内拓扑序的个数。把儿子的拓扑序看成一个区间,我们可以发现几个儿子转移到父亲实际上是一个区间合并的操作。

    但是合并之后儿子和父亲真正的拓扑序我们并不清楚。感觉无从下手。

    然后就看了题解qwq。

    考虑再添加一维:设$f_{i,j}$表示$i$在以$i$为根的子树拓扑序内排名为$j$的方案数。那么总方案数即为$sumlimits_{j=1}^{size} f_{i,j}$。可以发现,当$j$不同时,方案数一定数独立的。

    考虑怎么转移:当循环到$x$的一个儿子$y$的时候,设$size_x$记录的是之前$x$的儿子$size$和(没有包括$y$)。考虑区间合并,我们可以以$x$为断点,分类讨论进行合并(因为树的边实际上是有向边,我们先看成无向边再考虑边的限制)。

    1.$x<y$,即先通过$x$再通过$y$。

    这个时候合并后的$x$一定在$y$的前面。

    对于$f_{x,k}$,最终$x$前有$k-1$个位置。可以从$f_{x,i}(1leq ileq min(k,size_x))$和$f_{y,j}$($j$的范围后面会说)转移过来。这时区间里一共有$size_x+size_y$个数。

    现在有前$k-1$个位置,要挑选出$i-1$个位置把原来的数放进去,有$inom{k-1}{i-1}$种选法;后面有$size_x+size_y-k$个位置,还剩下$size_x-i$个数,有$inom{size_x+size_y-k}{size_x-i}$种选法。

    最后再乘上$f_{x,i}$(每种不同的方案都对其有贡献)。

    剩下的位置就是$f_{y,j}$的了,直接乘上$f_{y,j}$就好。

    关于$j$的取值:

    因为在$x$之前已经放了$i$个数,还剩$k-i$个位置,而$y$前面有$j-1$个数。为了让$y$在$x$后面,则必然有$j-1leq k-i$。所以有:$jin[k-i+1,size_y]$。

    于是我们可以愉快得列出DP方程:

    $f_{x,k}=sumlimits_{i=1}^{min(size_x,k)} f_{x,i} imes sumlimits_{j=k-i+1}^{size_y} f_{y,j} imes inom{k-1}{i-1} imes inom{size_x+size_y-k}{size_x-i}$

    这个式子是$O(n^3)$的。发现关于$j$的项可以前缀和优化,时间复杂度降为$O(n^2)$。

    2.$x>y$,即先通过$y$再通过$x$。

    与上面类似,只不过要求$y$在$x$前面,则一定有$j-1<k-i$,所以$jin[1,k-i]$。

    DP方程与上面一样,把$j$的取值范围换一下即可。

    然而交上去发现爆long long了QAQ,于是码风变得比较毒瘤……

    代码:

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #define ull unsigned long long
    using namespace std;
    char c;
    const int N=1005;
    const int p=1e9+7;
    ull f[N][N],sum[N][N],C[N][N];
    int size[N],T,n;
    int head[N],cnt;
    struct node{
        int next,to,op;
    }edge[N*2];
    inline void clear()
    {
        cnt=0;
        memset(head,0,sizeof(head));
        memset(f,0,sizeof(f));
        memset(sum,0,sizeof(sum));
    }
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void add(int from,int to,int op)
    {
        edge[++cnt]=(node){head[from],to,op};
        head[from]=cnt;
    }
    inline void dfs(int now,int fa)
    {
        size[now]=1;f[now][1]=1;
        for (int o=head[now];o;o=edge[o].next)
        {
            int to=edge[o].to;
            if (to==fa) continue;
            dfs(to,now);
            if (edge[o].op)
            {
                for (int k=size[now]+size[to];k>=1;k--)
                {
                    ull tot=0;
                    for (int i=1;i<=min(size[now],k);i++)
                    {
                        int l=k-i,r=size[to];
                        ull del=(sum[to][size[to]]+p-sum[to][k-i])%p;
                        if (l<r)
                        {
                            ull t1=(f[now][i]*del)%p,t2=(C[k-1][i-1]*C[size[now]+size[to]-k][size[now]-i])%p;
                            t1=(t1*t2)%p;tot=(tot+t1)%p;
                        }
                    }
                    f[now][k]=tot;
                }
            }
            else
            {
                for (int k=size[now]+size[to];k>=1;k--)
                {
                    ull tot=0;
                    for (int i=1;i<=min(size[now],k-1);i++)
                    {
                        int r=min(size[to],k-i);
                        ull del=sum[to][r];
                        ull t1=(f[now][i]*del)%p,t2=(C[k-1][i-1]*C[size[now]+size[to]-k][size[now]-i])%p;
                        t1=(t1*t2)%p;tot=(tot+t1)%p;
                    }
                    f[now][k]=tot;
                }
            }
            size[now]+=size[to];
        }
        for (int i=1;i<=size[now];i++)
            sum[now][i]=(sum[now][i-1]+f[now][i])%p;
    }
    signed main()
    {
        for (int i=0;i<N;i++) C[i][0]=1;
        for (int i=1;i<N;i++)
            for (int j=1;j<=i;j++)
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
        T=read();
        while(T--)
        {
            n=read();
            for (int i=1;i<n;i++)
            {
                int x=read();scanf("%c",&c);int y=read();
                x++;y++;
                if (c=='<') add(x,y,1),add(y,x,0);
                else add(x,y,0),add(y,x,1);
            }
            dfs(1,0);
            printf("%llu
    ",sum[1][n]);
            clear();
        }
        return 0;
    }
  • 相关阅读:
    青蛙的约会
    欧拉函数
    Tarjan
    计算器的改良
    记忆化搜索
    火柴棒等式
    hdu6601 Keen On Everything But Triangle(主席树)
    P2774 方格取数(网络流)
    第四百二十七、八、九天 how can I 坚持
    第四百二十五、六天 how can I 坚持
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13849442.html
Copyright © 2020-2023  润新知