[jzoj]5954. 【NOIP2018模拟11.5A组】走向巅峰(期望)

版权声明:蒟蒻写的文章,能看就行了,同时欢迎大佬们指点错误 https://blog.csdn.net/Algor_pro_king_John/article/details/86353043

https://jzoj.net/senior/#main/show/5954

Problem
  • n n 个节点的树,每次随机染黑一个叶子结点,可以重复染黑,求直径第一次变小的期望次数.
Data constraint
  • n 5 1 0 5 n\le 5*10^5 .
Solution
  • 一道好题。

  • 首先,我们要考虑的是染黑一个对直径可能有贡献的叶子的期望步数。可能有贡献值的是这个叶子至少在一条直径中。

  • 譬如总共有 n n 个叶子结点,还有 m m 个点是在直径上且待染色的,设再染黑 m m 个点当中任意一个点的期望次数为 f f ,那么就有 f = 1 + n m n f f=1 + \frac{n - m}{n}f 即要么直接染黑,期望次数就是 1 1 ,要么选择染黑其余 n m n-m 个点,然后重新再来.

  • 解决了这个问题后,我们继续观察题目性质。不难发现,一棵树上的所有直径必定经过同一点,而对于长度为奇数的直径,就是经过同一点,对于长度为偶数的直径,必定经过同一条边,这个点,或这条边都必定在一条直径的中间位置.

  • 这样一来,对于长度为偶数的直径,我们就按必经边把树分成两个集合,我们需要做的操作就是对这两个集合进行染色,直到只剩下一个集合时,长度必然变小,而对于奇数也是一样,只不过分成的集合可以由很多个.

  • 现在我们就成功的把问题转化为给你若干个集合,你要对这些集合染色,直至剩下一个集合,这很好做,直接枚举一个集合,并枚举剩下的大小,直接计算就可以。

  • 譬如,最后剩下的集合为 x x ,集合大小为 w x w_x ,枚举 x x 集合最后剩下的个数 d d ,总共的叶子结点个数为 n n ,总共在直径上的叶子结点个数为 d 0 d_0 ,则集合 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 w x + d d_0-w_x+d 个节点要染。为了保证不算重,我们保证最后一个染色的点不能是 x x 集合里的点。所以单独乘个 ( d 0 w x ) (d_0-w_x) 出来。

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

猜你喜欢

转载自blog.csdn.net/Algor_pro_king_John/article/details/86353043