题解:P15578 [USACO26FEB] Random Tree Generation G
闲话
第一次打金组就成功晋级!场切蓝题特此纪念。
非常好计数题。
题目大意
给定一棵
- 从节点
1 开始,对i=2,\dots,N ,将i 与\mathrm{randint}(1,i-1) 连边(每个i 独立均匀选择父节点)。这样生成一棵父节点编号小于子节点的树,共有(N-1)! 种等可能结果。 - 随机均匀打乱所有节点的编号(即随机排列
p_1,\dots,p_N ,将原编号v 改为p_v ),共有N! 种等可能结果。
现给定最终树的边集,求生成这棵树的概率,对
解题思路
设
对于以固定节点
而整棵树满足性质等价于每个节点都是其子树中最小的,故符合条件的排列数为
其中
故生成树
设
考虑换根 DP 求所有
考虑从父亲
因此:
通过第二次 DFS 即可
求出所有
其中
复杂度分析
- 时间复杂度:
O(TN\log M) 。 - 空间复杂度:
O(N) 。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 1e6 + 5, mod = 1e9 + 7;
ll tc, n, fac[N], invf[N], inv[N];
ll sz[N], fa[N], dp[N];
vector<ll> G[N];
ll qpow(ll x, ll y) {
if (y == 0) { return 1; }
ll res = qpow(x, y / 2);
res = res * res % mod;
if (y % 2) {
res = res * x % mod;
}
return res;
}
void init() {
for (ll i = 1; i <= n; i++) {
G[i].clear();
}
fac[0] = 1;
for (ll i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % mod;
}
}
void dfs1(ll u, ll ff = 0) {
fa[u] = ff;
sz[u] = 1;
for (ll v : G[u]) {
if (v == ff) { continue; }
dfs1(v, u);
sz[u] += sz[v];
}
}
void dfs2(ll u, ll ff = 0) {
if (u != 1) {
dp[u] = dp[ff] * qpow(sz[u], mod - 2) % mod * (n - sz[u]) % mod;
}
for (ll v : G[u]) {
if (v == ff) { continue; }
dfs2(v, u);
}
}
void solve() {
cin >> n;
init();
for (ll i = 1; i < n; i++) {
ll u, v;
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
dfs1(1ll);
dp[1] = 1;
for (ll i = 1; i <= n; i++) {
(dp[1] *= sz[i]) %= mod;
}
dfs2(1ll);
ll sum = 0;
for (ll i = 1; i <= n; i++) {
(sum += qpow(dp[i], mod - 2)) %= mod;
}
cout << sum * qpow(fac[n - 1], mod - 2) % mod << "\n";
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> tc;
while (tc--) { solve(); }
return 0;
}