前题提要:碰到了一道求树形图拓扑序的个数的题目,感觉十分有意思,故写一篇题解记录一下.
题目简述:给你n个点n条有向边,形成一张图,且这张图满足拓扑条件.问你这张图不同的拓扑序有几种.
考虑给的这张图是n个点以及n条边,并且满足拓扑序,也就是说,假设我们的边不是有向边的话,那么我们的这个图将形成一颗树.这个性质很重要,所以我们考虑从树形结构下手.
根据树形dp的一半套路,考虑使用$dp[u]$来表示以$u$为根节点的子树的拓扑序的个数.那么此时我们需要考虑的就是所有儿子$v$的合并问题了(此处的合并类似与树形背包).
需要注意的是,此时我们用遍历树的方法来遍历树形图,所以u,v的边的方向我们是需要考虑的,为了讨论方便起见,此时我们先假定u需要在v前面
然后我们发现光光记录上述的个数显然是无法将$u$和$v$合并的.因为我们根本不知道每一种拓扑序的样子,光光记录个数无从下手. 考虑将$dp$方程进一步扩展,使用$dp[u][i]$来表示以$u$为根的子树并且u在拓扑序中第$i$位的种类数.显然$u$所有的拓扑序个数就是所有的$dp[u][i]$的和 诶,此时我们似乎可以进行合并了. 对于我们现在的$dp[u][i]$和$u$的一个子树$v,dp[v][j]$,我们考虑如何将其合并.显然我们可以枚举合并后$u$在拓扑序中的位置,设合并后$u$在第$k$位.那么对于此次合并来说,本来在$u$前面的点现在肯定还是在$u$前面的,那么我们多出来的点就是原来在$v$前面的了(注意此时我们假定u在v前面,那么v后面的肯定不可能在u前面).并且因为u,v序列各自的相对位置是不能发生改变的,我们可以使用这样的想法来统计:我们考虑将u序列的每一个数放在合并后的序列的哪一个位置.因为假设我们将u放下了,相对的v也被放下了.那么此时不难推出合并带来的贡献就是:$$C{k-1}^{i-1}C{Size[u]+Size[v]-k}^{Size[u]-i}$$,那么合并的总式子就是$$dp[u][k]+=dp[u][i]dp[v][j]C{k-1}^{i-1}C{Size[u]+Size[v]-k}^{Size[u]-i}$$ 此时不难发现上述式子的朴素求法是$n^3$的(考虑枚举所有的$i,j,k$).所以需要优化一下求法. 然后我们会发现上述式子其实我们可以求出所有的前缀$dp[v][j]$,我们将原式子使用分配率搞一下,就会发现可以化解为$$dp[u][k]+=\sum{j=1}^{Size[v]}dp[v][j]dp[u][i]C{k-1}^{i-1}*C_{Size[u]+Size[v]-k}^{Size[u]-i}$$.用这种方法来省去一层循环.所以此时我们还需要做的就是如何通过$i,k$的范围来推出$j$的枚举范围.手模一下,不难发现我们的$k$存在一种范围$i \leq k \leq i+j-1$,然后我们考虑反推出$j \geq k-i+1$,并且此时的$k$的枚举范围是$[i,Size[u]+Size[v]-1]$.
现在考虑如果是$u$在$v$后面的情况,分析一下发现$dp$方程应该和上述是基本一样的.就是枚举范围有一点变化,此时我们的$k$的关系式是$k \geq i+j,k \leq i+Size[v]$,那么此时$i\in[1,Size[u]],j\in[1,k-i],k\in[i+1,Size[u]+Size[v]]$
至此,本题结束
下面是具体的代码部分:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
inline void print(__int128 x){
if(x<0) {putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
vector<int>edge[1010];int mp[1010][1010];
int dp[1010][1010],sum[1010][1010];int Size[maxn];int temp[1010];int C[1010][1010];
void dfs(int u,int per_u) {
Size[u]=1;dp[u][1]=1;
for(auto v:edge[u]) {
if(v==per_u) continue;
dfs(v,u);
memcpy(temp,dp[u],sizeof dp[u]);
memset(dp[u],0,sizeof dp[u]);
if(mp[u][v]==0) {
for(int i=1;i<=Size[u];i++) {
for(int j=i;j<=i+Size[v]-1;j++) {
dp[u][j]=(dp[u][j]+temp[i]*C[j-1][i-1]%mod*C[Size[u]+Size[v]-j][Size[u]-i]%mod\
*(sum[v][Size[v]]-sum[v][j+1-i-1]+mod)%mod)%mod;
}
}
}
else {
for(int i=1;i<=Size[u];i++) {
for(int j=i+1;j<=i+Size[v];j++) {
dp[u][j]=(dp[u][j]+temp[i]*C[j-1][i-1]%mod*C[Size[u]+Size[v]-j][Size[u]-i]%mod\
*sum[v][j-i]%mod)%mod;
}
}
}
Size[u]+=Size[v];
}
for(int i=1;i<=Size[u];i++) {
sum[u][i]=(sum[u][i-1]+dp[u][i])%mod;
}
}
void init() {
memset(dp,0,sizeof dp);memset(sum,0,sizeof sum);memset(Size,0,sizeof Size);
memset(temp,0,sizeof temp);
}
signed main() {
C[0][0]=1;
for(int i=1;i<=1000;i++) {
C[i][0]=1;
for(int j=1;j<=i;j++) {
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
int T=read();
while(T--) {
init();
int n=read();
for(int i=1;i<=n;i++) edge[i].clear();
for(int i=1;i<=n-1;i++) {
int u=read();char c;cin>>c;int v=read();u++;v++;
if(c=='<') mp[u][v]=0,mp[v][u]=1;
else mp[u][v]=1,mp[v][u]=0;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1,0);
cout<<sum[1][Size[1]]<<endl;
}
return 0;
}