1. 程式人生 > >jzoj5954 【NOIP2018模擬11.5A組】走向巔峰(直徑性質,期望)

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 個,然後求期望。
在這裡插入圖片描述

然而PTY有一種更簡單的方法。先列舉一個集合x,假裝刪他們是失敗的,然後算出將其他集合的點刪完的期望步數。這樣多考慮的一部分是先把集合x全刪完,再刪完其他集合的貢獻,並且這個步數就是把所有點刪完的步數。

可以發現,對於每一種確定的方案,除了最後剩下的那個集合,其他集合都多算了這種方案。將最終答案減去 (

1 )
(集合數 - 1) \cdot 將所有點刪完的期望步數 即可。

死因: 不知道結論

實現

#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;
}