https://jzoj.net/senior/#main/show/5954
Problem
- n n n个节点的树,每次随机染黑一个叶子结点,可以重复染黑,求直径第一次变小的期望次数.
Data constraint
- n ≤ 5 ∗ 1 0 5 n\le 5*10^5 n≤5∗105.
Solution
-
一道好题。
-
首先,我们要考虑的是染黑一个对直径可能有贡献的叶子的期望步数。可能有贡献值的是这个叶子至少在一条直径中。
-
譬如总共有 n n n个叶子结点,还有 m m m个点是在直径上且待染色的,设再染黑 m m m个点当中任意一个点的期望次数为 f f f,那么就有 f = 1 + n − m n f f=1 + \frac{n - m}{n}f f=1+nn−mf即要么直接染黑,期望次数就是 1 1 1,要么选择染黑其余 n − m n-m n−m个点,然后重新再来.
-
解决了这个问题后,我们继续观察题目性质。不难发现,一棵树上的所有直径必定经过同一点,而对于长度为奇数的直径,就是经过同一点,对于长度为偶数的直径,必定经过同一条边,这个点,或这条边都必定在一条直径的中间位置.
-
这样一来,对于长度为偶数的直径,我们就按必经边把树分成两个集合,我们需要做的操作就是对这两个集合进行染色,直到只剩下一个集合时,长度必然变小,而对于奇数也是一样,只不过分成的集合可以由很多个.
-
现在我们就成功的把问题转化为给你若干个集合,你要对这些集合染色,直至剩下一个集合,这很好做,直接枚举一个集合,并枚举剩下的大小,直接计算就可以。
-
譬如,最后剩下的集合为 x x x,集合大小为 w x w_x wx,枚举 x x x集合最后剩下的个数 d d d,总共的叶子结点个数为 n n n,总共在直径上的叶子结点个数为 d 0 d_0 d0,则集合 x x x对答案的贡献可以写成
∑ d = 0 w x − 1 ( w x d ) ( d 0 − w x ) ( d 0 − w x + d − 1 ) ! ( ∑ i = d 0 − d + 1 d 0 n i ) ( d − i ) ! d 0 ! \sum_{d=0}^{w_x-1}\binom{w_x}{d}(d_0-w_x)(d_0-w_x+d-1)!(\sum_{i=d_0-d+1}^{d_0}\frac{n}{i})\frac{(d-i)!}{d_0!} d=0∑wx−1(dwx)(d0−wx)(d0−wx+d−1)!(i=d0−d+1∑d0in)d0!(d−i)!
- 这是由于我们总共需要染 d 0 − w x + d d_0-w_x+d d0−wx+d个节点要染。为了保证不算重,我们保证最后一个染色的点不能是 x x x集合里的点。所以单独乘个 ( d 0 − w x ) (d_0-w_x) (d0−wx)出来。
Code
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
#define F(i, a, b) for (int i = a; i <= b; i ++)
#define G(i, a, b) for (int i = a; i >= b; i --)
#define REP(i, a) for (int i = las[a]; i ; i = nex[i])
const int N = 1e6 + 10, Mo = 998244353;
using namespace std;
int fa[N], dep[N], calc[N], dis, CNT;
int tov[N], nex[N], las[N], w[N], jc[N], ny[N];
int n, x, y, rt, RT, Lx, k, cnt, tot, Ans;
void ins(int x, int y) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot;
}
void Dfs(int k) {
if (dep[k] > Lx)
Lx = dep[k], rt = k;
REP(x, k) if (!dep[tov[x]])
dep[tov[x]] = dep[k] + 1, Dfs(tov[x]);
}
void Go(int k) {
if (dep[k] > Lx)
Lx = dep[k], RT = k;
REP(x, k) if (!dep[tov[x]])
fa[tov[x]] = k, dep[tov[x]] = dep[k] + 1, Go(tov[x]);
}
void Doit(int k, int v) {
CNT += nex[las[k]] == 0;
if (dep[k] == dis + 1 >> 1)
w[cnt] ++, tot ++;
REP(x, k)
if (!dep[tov[x]] && tov[x] != v)
dep[tov[x]] = dep[k] + 1, Doit(tov[x], v);
}
int ksm(int x, int y) {
int ans = 1;
for (; y; y >>= 1, x = (1LL * x * x) % Mo)
if (y & 1)
ans = (1LL * ans * x) % Mo;
return ans;
}
int C(int x, int y) {
return 1LL * jc[x] * ny[y] % Mo * 1LL * ny[x - y] % Mo;
}
int main() {
scanf("%d", &n);
F(i, 1, n - 1)
scanf("%d%d", &x, &y), ins(x, y), ins(y, x);
dep[1] = 1, Dfs(1), mem(dep, 0), dep[rt] = 1;
Lx = 0, Go(rt), dis = dep[RT];
F(i, 1, dis - 1 >> 1) RT = fa[RT];
mem(dep, 0), tot = 0;
if (~dis&1) {
rt = RT, RT = fa[RT];
++ cnt, dep[rt] = 1, Doit(rt, RT);
++ cnt, dep[RT] = 1, Doit(RT, rt);
}
else
REP(x, RT)
++ cnt, dep[tov[x]] = 2, Doit(tov[x], RT), cnt -= w[cnt] == 0;
jc[0] = ny[0] = 1;
F(i, 1, n)
jc[i] = 1LL * jc[i - 1] * i % Mo;
ny[n] = ksm(jc[n], Mo - 2);
G(i, n - 1, 1)
ny[i] = 1LL * ny[i + 1] * (i + 1) % Mo;
G(i, tot, 1)
calc[i] = (calc[i + 1] + 1LL * CNT * ksm(i, Mo - 2) % Mo) % Mo;
F(i, 1, cnt) F(j, 0, w[i] - 1)
Ans = (Ans + 1LL * C(w[i], j) * jc[tot - w[i] + j - 1] % Mo * (tot - w[i]) % Mo * 1LL * calc[w[i] - j + 1] % Mo * 1LL * jc[w[i] - j] % Mo) % Mo;
printf("%d", 1LL * Ans * ny[tot] % Mo);
}