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

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

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

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

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

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

然后就看了题解qwq。

考虑再添加一维:设$f_{i,j}$表示$i$在以$i$为根的子树拓扑序内排名为$j$的方案数。那么总方案数即为$\sum\limits_{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}(1\leq i\leq \min(k,size_x))$和$f_{y,j}$($j$的范围后面会说)转移过来。这时区间里一共有$size_x+size_y$个数。

现在有前$k-1$个位置,要挑选出$i-1$个位置把原来的数放进去,有$\binom{k-1}{i-1}$种选法;后面有$size_x+size_y-k$个位置,还剩下$size_x-i$个数,有$\binom{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-1\leq k-i$。所以有:$j\in[k-i+1,size_y]$。

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

$f_{x,k}=\sum\limits_{i=1}^{\min(size_x,k)} f_{x,i}\times \sum\limits_{j=k-i+1}^{size_y} f_{y,j}\times \binom{k-1}{i-1}\times \binom{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$,所以$j\in[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\n",sum[1][n]);
        clear();
    }
    return 0;
}
posted @ 2020-10-20 22:13  我亦如此向往  阅读(187)  评论(0编辑  收藏  举报