测试地址:SAO
题目大意: 给定一棵树边是有向边的树(边不一定从根指向儿子),求拓扑序数量。
n
≤
1000
n\le 1000
n≤1000。
做法: 本题需要用到高维树形DP+组合数学。
如果边都是从根连向儿子的话,简单组合就可以树形DP
O
(
n
)
O(n)
O(n)做了,问题是这道题并不是这样的,而是一个“类树”的结构,但因为它还是棵树所以我们还是考虑树形DP。
考虑用类似树形背包的做法,把子树一一合并到根上。令
f
(
i
,
j
)
f(i,j)
f(i,j)为在以
i
i
i为根的子树中,点
i
i
i在拓扑序中的位置为
j
j
j的拓扑序方案数,那么我们一开始令
f
(
i
,
1
)
=
1
f(i,1)=1
f(i,1)=1,其它都是
0
0
0。那么在合并一棵子树时,这棵子树的根要求比点
i
i
i先取或者后取,因为这两个限制对称,所以在这里我们只讨论要求子树根比点
i
i
i先取的情况。
考虑枚举两个值
j
,
k
j,k
j,k,
j
j
j表示点
i
i
i在合并前的拓扑序中的位置,
k
k
k表示在当前子树的拓扑序中比点
i
i
i先取的点的数目,我们来考虑贡献。首先,因为满足性质的拓扑序可以随便选,因此贡献乘上一个
f
(
i
,
j
)
f(i,j)
f(i,j)。接着,因为要求子树根要比点
i
i
i先取,所以当且仅当
p
≤
k
p\le k
p≤k(
p
p
p表示子树根在当前子树的拓扑序中的位置)时才满足条件,此时所有的
f
(
s
o
n
,
p
)
f(son,p)
f(son,p)都是可以选的,那么贡献乘上一个
∑
p
=
1
k
f
(
s
o
n
,
p
)
\sum_{p=1}^k f(son,p)
∑p=1kf(son,p)。接着就是合并的环节了,当前子树拓扑序的前
k
k
k个塞到点
i
i
i之前,方案数为
C
j
+
k
−
1
k
C_{j+k-1}^k
Cj+k−1k,剩下的
s
i
z
(
s
o
n
)
−
k
siz(son)-k
siz(son)−k个塞到点
i
i
i之后,同理得到方案数为
C
s
i
z
(
i
)
+
s
i
z
(
s
o
n
)
−
j
−
k
s
i
z
(
s
o
n
)
−
k
C_{siz(i)+siz(son)-j-k}^{siz(son)-k}
Csiz(i)+siz(son)−j−ksiz(son)−k。那么令
n
o
w
(
x
)
now(x)
now(x)为合并后拓扑序中点
i
i
i在第
x
x
x位的方案数,那么一对
j
,
k
j,k
j,k对
n
o
w
(
j
+
k
)
now(j+k)
now(j+k)的贡献为:
f
(
i
,
j
)
⋅
[
∑
p
=
1
k
f
(
s
o
n
,
p
)
]
⋅
C
j
+
k
−
1
k
⋅
C
s
i
z
(
i
)
+
s
i
z
(
s
o
n
)
−
j
−
k
s
i
z
(
s
o
n
)
−
k
f(i,j)\cdot [\sum_{p=1}^k f(son,p)]\cdot C_{j+k-1}^k\cdot C_{siz(i)+siz(son)-j-k}^{siz(son)-k}
f(i,j)⋅[∑p=1kf(son,p)]⋅Cj+k−1k⋅Csiz(i)+siz(son)−j−ksiz(son)−k
类似地处理完要求
s
o
n
son
son比
i
i
i后取的情况,预处理组合数,再顺便算一个前缀和(要求那个
∑
\sum
∑)就可以很快地直接DP了。而因为这个DP合并子树的方式和树形背包极其相似,我们可以用类似树形背包的方法证明复杂度是
O
(
n
2
)
O(n^2)
O(n2)的(考虑每对点都在其LCA处恰好被计算一次),我个人把这种高维树形DP称为类树形背包。这样我们就解决了这一题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int T,n,first[1010],tot;
int siz[1010];
ll C[1010][1010],f[1010][1010],sum[1010][1010],now[1010];
struct edge
{
int v,next;
bool type;
}e[2010];
void insert(int a,int b,bool type)
{
e[++tot].v=b;
e[tot].type=type;
e[tot].next=first[a];
first[a]=tot;
}
ll CC(int n,int m)
{
if (n<0||m<0||n<m) return 0;
else return C[n][m];
}
void dp(int v,int fa)
{
siz[v]=1;
memset(f[v],0,sizeof(f[v]));
f[v][1]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa)
{
int y=e[i].v;
dp(y,v);
for(int j=1;j<=siz[v]+siz[y];j++)
now[j]=0;
if (e[i].type)
{
for(int j=1;j<=siz[v];j++)
for(int k=1;k<=siz[y];k++)
now[j+k]=(now[j+k]+f[v][j]*sum[y][k]%mod*CC(j+k-1,k)%mod*CC(siz[v]+siz[y]-j-k,siz[y]-k))%mod;
}
else
{
for(int j=1;j<=siz[v];j++)
for(int k=0;k<siz[y];k++)
now[j+k]=(now[j+k]+f[v][j]*(sum[y][siz[y]]-sum[y][k]+mod)%mod*CC(j+k-1,k)%mod*CC(siz[v]+siz[y]-j-k,siz[y]-k))%mod;
}
siz[v]+=siz[y];
for(int j=1;j<=siz[v];j++)
f[v][j]=now[j];
}
sum[v][0]=0;
for(int i=1;i<=siz[v];i++)
sum[v][i]=(sum[v][i-1]+f[v][i])%mod;
}
int 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;
}
scanf("%d",&T);
while(T--)
{
memset(first,0,sizeof(first));
tot=0;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int a,b;
char s;
scanf("%d %c%d",&a,&s,&b);
if (s=='>') swap(a,b);
insert(a,b,0);
insert(b,a,1);
}
dp(0,-1);
printf("%lld\n",sum[0][n]);
}
return 0;
}