Description
众所周知,DH是一位人生赢家,他不仅能虐暴全场,而且还正在走向人生巅峰;
在巅峰之路上,他碰到了这一题:
给出一棵n个节点的树,我们每次随机染黑一个叶子节点(可以重复染黑),操作无限次后,这棵树的所有叶子节点必然全部会被染成黑色。
定义R为这棵树不经过黑点的直径,求使R第一次变小期望的步数。
对于100%的数据,满足n<=5*10^5。
分析
有一个性质: 树上所有直径的交集当长度是偶数时是一个点,奇数时是一条边,而且在中点。
以中心为根,求出每个点的dep。 显然只有dep为R / 2的叶子对答案有影响。
接下来相当于,有k个集合,每个集合有ai个点。每次有p的概率随机删一个点,求删剩下一个集合的期望步数。(无效点已经被删除成功的概率考虑了)
按照LeFee的方法,可以枚举某个集合最后剩下
a
x
−
i
ax - i
ax−i个,然后求期望。
然而PTY有一种更简单的方法。先枚举一个集合x,假装删他们是失败的,然后算出将其他集合的点删完的期望步数。这样多考虑的一部分是先把集合x全删完,再删完其他集合的贡献,并且这个步数就是把所有点删完的步数。
可以发现,对于每一种确定的方案,除了最后剩下的那个集合,其他集合都多算了这种方案。将最终答案减去 ( 集 合 数 − 1 ) ⋅ 将 所 有 点 删 完 的 期 望 步 数 (集合数 - 1) \cdot 将所有点删完的期望步数 (集合数−1)⋅将所有点删完的期望步数即可。
死因: 不知道结论
实现
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10, mo = 998244353;
int n;
int final[N],nex[N*2],to[N*2],tot,fa[N],dep[N],deg[N];
void link(int x,int y) {
to[++tot] = y, nex[tot] = final[x], final[x] = tot;
deg[x]++;
}
int lcnt;
void dfs(int x){
for (int i = final[x]; i; i=nex[i]) {
int y = to[i]; if (y != fa[x]) {
fa[y] = x; dfs(y);
}
}
lcnt += deg[x] == 1;
}
int R,mid,Q[N],dis[N];
int find(int src,int &far) {
int h = 0, t = 0, ret = 0;
memset(dis,127,sizeof dis);
dis[src] = 0;
Q[++t] = src;
while (h < t) {
int x = Q[++h];
for (int i = final[x]; i; i=nex[i]) {
int y = to[i];
if (dis[y] > dis[x] + 1) {
dis[y] = dis[x] + 1;
Q[++t] = y;
}
}
}
far = Q[t];
return dis[Q[t]];
}
int s1,s2,tg,S[N];
void fdr(int x,int from) {
S[++S[0]] = x;
if (x == tg) {
if (R & 1) {
s1 = S[R / 2 + 1], s2 = S[R / 2 + 2];
} else {
s1 = S[R / 2 + 1];
}
return;
}
for (int i = final[x]; i; i=nex[i]) {
int y = to[i]; if (y != from) {
fdr(y, x);
}
}
S[0]--;
}
int s[N],sz[N];
void findset(int x,int from) {
for (int i = final[x]; i; i=nex[i]) {
int y = to[i]; if (y != from) {
dep[y] = dep[x] + 1;
findset(y, x);
sz[x] += sz[y];
if (x == s1 || x == s2) {
if (!(R & 1)) {
s[++s[0]] = sz[y] ;
}
}
}
}
sz[x] += dep[x] == R / 2;
}
void findset() {
int a,b;
find(1,a);
R = find(a,b);
tg = b, fdr(a, 0);
if (R & 1) {
findset(s1, s2);
findset(s2, s1);
s[0] = 2; s[1] = sz[s1], s[2] = sz[s2];
} else
findset(s1, 0);
}
typedef long long ll;
ll ksm(ll x,ll y) {
ll ret = 1; for (; y; y>>=1) {
if (y & 1) ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
ll sum[N];
ll ans;
void solve() {
for (int i = 1; i <= n; i++) sum[i] = (sum[i - 1] + lcnt * ksm(i, mo - 2)) % mo;
ll d0 = 0;
for (int i = 1; i <= s[0]; i++) d0 += s[i];
for (int i = 1; i <= s[0]; i++) {
ans = (ans + sum[d0 - s[i]]) % mo;
}
ans = (ans - (s[0] - 1) * sum[d0] % mo) % mo;
}
int main() {
freopen("winer.in","r",stdin);
freopen("winer.out","w",stdout);
cin>>n;
for (int i = 1; i < n; i++) {
int u,v; scanf("%d %d",&u,&v);
link(u,v), link(v,u);
}
dfs(1);
findset();
solve();
cout<<(ans+mo)%mo<<endl;
}