【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; }