树形DP树上拓扑序计数
Posted 行码棋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树形DP树上拓扑序计数相关的知识,希望对你有一定的参考价值。
树上拓扑序计数|树形DP
题目链接:
https://ac.nowcoder.com/acm/contest/38630/F
思路
每个公司是一棵树,然后每个公司可以看做连在一个虚拟的根上。每个公司的计算方案实际上就是计算这棵树的拓扑序的个数。用树形DP求解。
f
[
u
]
f[u]
f[u] : 以u
为根的子树的拓扑序数
s
z
[
u
]
sz[u]
sz[u] : 以u
为根的子树的大小(节点的数量)
当树为二叉树时,将两个子树v1,v2
进行合并:即先把各子树的方案数乘起来算出总方案,然后考虑各子树元素的相对排列顺序,即在总的节点个数中选sz[v1]
排在前面的sz[v1]
个位置,剩下的排在后面,保证每颗子树的相对拓扑序不变。
f [ u ] = f [ v 1 ] ∗ f [ v 2 ] ∗ C ( s z [ v 1 ] + s z [ v 2 ] , s z [ v 1 ] ) f[u] = f[v1] * f[v2] * C(sz[v1] + sz[v2], sz[v1]) f[u]=f[v1]∗f[v2]∗C(sz[v1]+sz[v2],sz[v1])
例子:
u
节点有四颗子树,子树大小分别为a, b, c, d
,则方案数为:
f [ u ] = f [ a ] ∗ f [ b ] ∗ f [ c ] ∗ f [ d ] ∗ C ( a + b + c + d , a ) ∗ C ( b + c + d , b ) ∗ C ( c + d , c ) = f [ a ] ∗ f [ b ] ∗ f [ c ] ∗ f [ d ] ∗ ( a + b + c + d ) ! a ! ( b + c + d ) ! ∗ ( b + c + d ) ! b ! ( c + d ) ! ∗ ( c + d ) ! c ! d ! = f [ a ] ∗ f [ b ] ∗ f [ c ] ∗ f [ d ] ∗ ( a + b + c + d ) ! a ! b ! c ! d ! f[u] = f[a]*f[b]*f[c]*f[d]*C(a+b+c+d, a)*C(b+c+d, b)*C(c+d,c)\\\\ =f[a]*f[b]*f[c]*f[d]*\\frac(a+b+c+d)!a!(b+c+d)!*\\frac(b+c+d)!b!(c+d)!*\\frac(c+d)!c!d!\\\\ =f[a]*f[b]*f[c]*f[d]*\\frac(a+b+c+d)!a!b!c!d! f[u]=f[a]∗f[b]∗f[c]∗f[d]∗C(a+b+c+d,a)∗C(b+c+d,b)∗C(c+d,c)=f[a]∗f[b]∗f[c]∗f[d]∗a!(b+c+d)!(a+b+c+d)!∗b!(c+d)!(b+c+d)!∗c!d!(c+d)!=f[a]∗f[b]∗f[c]∗f[d]∗a!b!c!d!(a+b+c+d)!
推广到一般树:
f
[
u
]
=
(
∏
v
∈
s
o
n
(
u
)
f
[
v
]
)
∗
(
s
z
[
u
]
−
1
)
!
∏
v
∈
s
o
n
(
u
)
s
z
[
v
]
!
f[u] = (\\prod \\limits_v \\in son(u) f[v] ) * \\frac(sz[u] - 1)!\\prod \\limits_v \\in son(u)sz[v]!
f[u]=(v∈son(u)∏f[v])∗v∈son(u)∏sz[v]!(sz[u]−1)!
换一下形式:
f
[
u
]
=
(
s
z
[
u
]
−
1
)
!
∗
∏
v
∈
s
o
n
(
u
)
f
[
v
]
s
z
[
v
]
!
f[u] = (sz[u] - 1)! * \\prod \\limits_v \\in son(u) \\fracf[v]sz[v]!
f[u]=(sz[u]−1)!∗v∈son(u)∏sz[v]!f[v]
拓扑序数量还可以这样计算:
n
!
∗
∏
i
=
1
n
1
s
z
[
i
]
n! * \\prod \\limits_i = 1 ^ n \\frac1sz[i]
n!∗i=1∏nsz[i]1
代码1
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using arr = array<int, 3>;
using vi = vector<int>;
using vl = vector<ll>;
const int N = 1e5 + 5, M = N;
const int mod = 1e9 + 7;
ll fac[N], inv[N];
ll ksm(ll a, ll b)
ll res = 1;
while(b)
if(b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
return res % mod;
void solve()
int n;
cin >> n;
ll ans = 1, tot = 1;
int cnt = 0;
for(int i = 1; i <= n; i++)
int c;
cin >> c;
cnt += c;
vector<vi> g(c);
for(int j = 1; j < c; j++)
int u;
cin >> u;
u--;
g[u].push_back(j);
vi sz(c, 1);
vl f(c, 1);
function<void(int)> dfs = [&](int u)
for(auto v : g[u])
dfs(v);
sz[u] += sz[v];
(f[u] *= f[v] * inv[sz[v]] % mod) %= mod;
(f[u] *= fac[sz[u] - 1]) %= mod;
;
dfs(0);
(tot *= f[0] * inv[c] % mod) %= mod;
ans = ans * tot % mod * fac[cnt] % mod;
cout << ans << "\\n";
int main()
ios::sync_with_stdio(false);
cin.tie(0);
fac[0] = 1;
for(int i = 1; i < N; i++)
fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = ksm(fac[N - 1], mod - 2);
for(int i = N - 2; i >= 1; i--)
inv[i] = (i + 1) * inv[i + 1] % mod;
int t;
t = 1;
// cin >> t;
while(t--)
solve();
return 0;
代码2
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using arr = array<int, 3>;
using vi = vector<int>;
using vl = vector<ll>;
const int N = 1e5 + 5, M = N;
const int mod = 1e9 + 7;
// assume -P <= x < 2P
int norm(int x)
if (x < 0)
x += mod;
if (x >= mod)
x -= mod;
return x;
template<class T>
T power(T a, ll b)
T res = 1;
for (; b; b /= 2, a *= a)
if (b % 2)
res *= a;
return res;
struct Z
以上是关于树形DP树上拓扑序计数的主要内容,如果未能解决你的问题,请参考以下文章