题意:
现在给定一颗树的压缩方式:(按如下步骤)
- 先选择一个大小为 k k k的节点子集
- 对于不存在于子集中的所有节点,动态删去所有度为1的节点(即如果一个节点原本度为2,一个度为1的儿子被删去了,则该节点随后也应该被删去)
- 对于不存在于子动态集中的所有节点,动态删去所有度为2的节点,随后将其两边所练节点连接起来。
- 随后剩下的一棵树即为压缩以后的树
问当 k k k分别等于 1 1 1到 n n n时,压缩以后的树节点个数的期望分别为多少?
思路:
当 k k k给定时,期望可以转化为考虑每一个节点对当前答案的贡献。即一共存在的 C n k C_{n}^k Cnk种压缩方案中,当前节点在多少种压缩方案中始终保留。
故我们可以根据当前点在原树中的度进行分类讨论:
如果当前节点的度不大于2,则只有该节点被选择成为
k
k
k个保留节点之一时,才不会被删除,故贡献为:
C
n
−
1
k
−
1
C_{n-1}^{k-1}
Cn−1k−1
而对于度大于2的节点,我们可以考虑反面,假设以当前节点为根节点,如果所有保留节点都只存在于其一棵子树或者两棵子树中,则压缩后一定会被删除掉。设有
m
m
m棵子树,每棵子树的节点个数为:
s
[
1
]
,
s
[
2
]
,
s
[
3
]
.
.
.
s
[
m
]
s[1], s[2], s[3]...s[m]
s[1],s[2],s[3]...s[m]
则贡献为总方案数减去会被删掉的方案数:
C n k − ∑ i < j C s [ i ] + s [ j ] 2 C_{n}^{k} - \sum_{i<j} C_{s[i]+s[j]}^2 Cnk−i<j∑Cs[i]+s[j]2
但此时需要注意一个去重的问题,即对于
C
s
[
i
]
+
s
[
j
]
2
C_{s[i]+s[j]}^2
Cs[i]+s[j]2,是包含
C
s
[
i
]
2
C_{s[i]}^2
Cs[i]2的答案,而这部分答案只应该计算一次,可当
s
[
i
]
s[i]
s[i]与不同的
j
j
j计算时,该部分答案一直被重复计算,一共计算了
m
−
1
m-1
m−1次,而我们只需要计算一次的答案,故最后应该把重复的
(
m
−
2
)
C
s
[
i
]
2
(m-2)C_{s[i]}^2
(m−2)Cs[i]2减去
故度大于
2
2
2的节点总贡献为:
C n k − ∑ i < j C s [ i ] + s [ j ] 2 + ∑ i C s [ i ] 2 C_{n}^{k} - \sum_{i<j} C_{s[i]+s[j]}^2 + \sum_i C_{s[i]}^2 Cnk−i<j∑Cs[i]+s[j]2+i∑Cs[i]2
对于所有度数大于
2
2
2的节点,其计算式中的
C
s
[
i
]
2
C_{s[i]}^2
Cs[i]2或者
C
s
[
i
]
+
s
[
j
]
2
C_{s[i]+s[j]}^2
Cs[i]+s[j]2有重复相同的,故可以考虑提前预处理进行合并,定义参数数组
c
o
e
coe
coe:
c
o
e
[
i
]
coe[i]
coe[i]:所有参数大于2的节点计算式合并以后,
C
i
k
C_i ^ k
Cik前的系数大小。
故之后就可以枚举 k k k,再枚举每一个系数,利用 c o e [ i ] coe[i] coe[i]便可以在 O ( n ) O(n) O(n)的复杂度解决该问题了。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
代码:
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int A = 1e4 + 10;
class Gra{
public:
int v,next;
}G[A<<1];
int head[A], deg[A], coe[A], tot, n, fac[A], Inv[A];
ll add(ll x, ll y){
x += y;
return x>=mod?x-mod:x;
}
ll sub(ll x, ll y){
x -= y;
return x<0?x+mod:x;
}
ll mul(ll x, ll y){
x *= y;
return x>=mod?x%mod:x;
}
void Add(int u, int v){
G[tot].v = v;
G[tot].next = head[u];
head[u] = tot++;
}
ll fast_mod(ll n, ll m){
ll res = 1;
while (m) {
if (m & 1) res = mul(res, n);
m >>= 1;
n = mul(n,n);
}
return res;
}
ll Comb(ll n, ll m){
return mul(fac[n], mul(Inv[m], Inv[n - m]));
}
ll get_Inv(ll a){
return fast_mod(a, mod - 2);
}
void Init(){
memset(head, -1, sizeof(head));
tot = 0;
fac[0] = Inv[0] = 1;
for (int i = 1; i < A; i++) fac[i] = mul(fac[i-1], i);
Inv[A - 1] = get_Inv(fac[A - 1]);
for (int i = A - 2; i >= 1; i--) {
Inv[i] = mul(Inv[i + 1], i + 1);
}
}
void update(const vector<int>& vec){
int m = vec.size();
for (int i = 0; i < m; i++) {
for (int j = i + 1; j < m; j++) {
coe[vec[i] + vec[j]] = sub(coe[vec[i] + vec[j]], 1);
}
}
for (int i = 0; i < m; i++) coe[vec[i]] = add(coe[vec[i]], m - 2);
}
int dfs(int u, int pre){
vector<int> siz;
int sum = 1;
for (int i = head[u]; i != -1; i = G[i].next) {
int v = G[i].v;
if (v == pre) continue;
int now = dfs(v, u);
siz.push_back(now);
sum += now;
}
if (sum < n) siz.push_back(n - sum);
if (deg[u] > 2) update(siz);
return sum;
}
void solve(){
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (deg[i] <= 2) cnt++;
}
for (int k = 1; k <= n; k++) {
ll ans = mul(cnt, Comb(n - 1, k - 1));
ans = add(ans, mul(n - cnt, Comb(n, k)));
for (int i = k; i <= n; i++) {
ans = add(ans, mul(coe[i], Comb(i, k)));
}
ans = mul(ans, get_Inv(Comb(n, k)));
printf("%lld\n", ans);
}
}
int main(){
Init();
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
Add(u, v); Add(v, u);
deg[u]++;deg[v]++;
}
dfs(1, 1);
solve();
return 0;
}