jzoj5954 【NOIP2018模拟11.5A组】走向巅峰(直径性质,期望)

Description

众所周知,DH是一位人生赢家,他不仅能虐暴全场,而且还正在走向人生巅峰;
在巅峰之路上,他碰到了这一题:
给出一棵n个节点的树,我们每次随机染黑一个叶子节点(可以重复染黑),操作无限次后,这棵树的所有叶子节点必然全部会被染成黑色。
定义R为这棵树不经过黑点的直径,求使R第一次变小期望的步数。

对于100%的数据,满足n<=5*10^5。

分析

有一个性质: 树上所有直径的交集当长度是偶数时是一个点,奇数时是一条边,而且在中点。

以中心为根,求出每个点的dep。 显然只有dep为R / 2的叶子对答案有影响。
接下来相当于,有k个集合,每个集合有ai个点。每次有p的概率随机删一个点,求删剩下一个集合的期望步数。(无效点已经被删除成功的概率考虑了)
按照LeFee的方法,可以枚举某个集合最后剩下 a x − i ax - i axi个,然后求期望。
在这里插入图片描述

然而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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值