JZOJ5954. 【NOIP2018模拟11.5A组】走向巅峰

12 篇文章 0 订阅
4 篇文章 0 订阅

题意:

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

数据范围:

对于 15 15 15%的数据,满足 n ≤ 10 n\leq10 n10
对于 30 30 30%的数据,满足 n ≤ 000 n\leq000 n000
另外有 20 20 20%数据,满足树为菊花图;
另外有 15 15 15%数据,满足每个点度数不超过 3 3 3
对于 100 100 100%的数据,满足 n ≤ 5 ∗ 1 0 5 n\leq5*10^5 n5105

Analysis:

据我所见,这是一道极其玄学的题目:
首先我们考虑用直径性质分析一波,对于所有直径,我们可以找到一个必经点,或必经边。
我们以其为根,然后建树,端点以所属的根的子树分类,会划分成若干个集合,想要让其变小,那么最后只会剩下一个集合里面有点没被染黑。
我们可以先知道一个值,若当前有 n n n个点没被染黑,总点数为 m m m,那么再染黑一个点的期望步数为: m n \frac{m}{n} nm
一种比较麻烦的方法是:
我们可以先把有用的叶子找出来,也就是为直径端点的叶子,这些叶子的深度在新树中为 ⌊ l e n 2 ⌋ \lfloor\frac{len}{2}\rfloor 2len l e n len len为直径长度。那么其它叶子就没用了,我们视为已经删去。设集合大小为直径端点叶子数。
在这里插入图片描述

第二种是老曹提供的比较简单的方法:
我们每一次枚举一个集合,使它最后删,那么对答案贡献为: ∑ i = 1 d 0 − d x m i \sum_{i=1}^{d_0-d_x}\frac{m}{i} i=1d0dxim。但是这样可能会出现不合法情况,即枚举的集合在其他集合删完前被删完了,那么此时贡献的是全部被染黑的贡献,这种情况会被算集合个数 − 1 -1 1次,减去即可。

复杂度 O ( n ) O(n) O(n)。我写了第一种方法。

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 5e5 + 5;
const int mo = 998244353;
typedef long long ll;
int st[N],to[N << 1],nx[N << 1],vis[N];
int fac[N],fac_[N],inv[N],sta[N],num[N],fa[N];
int n,m,tot,len,all,z;
inline void add(int u,int v)
{
	to[++tot] = v,nx[tot] = st[u],st[u] = tot;
	to[++tot] = u,nx[tot] = st[v],st[v] = tot;
}
inline int C(int n,int m) { return (ll)fac[n] * fac_[m] % mo * fac_[n - m] % mo; }
inline void dfs(int x,int s)
{
	if (s > len) len = s,z = x;
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fa[x]) fa[to[i]] = x,dfs(to[i],s + 1);
}
inline void dfs1(int x,int fr,int d,int g)
{
	if (d == len / 2 && vis[x]) ++num[g];
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fr) dfs1(to[i],x,d + 1,g);
}
int main()
{
	freopen("winer.in","r",stdin);
	freopen("winer.out","w",stdout);
	scanf("%d",&n),inv[1] = fac[0] = fac_[0] = 1;
	for (int i = 1 ; i <= n ; ++i)
	{
		if (i > 1) inv[i] = (ll)(mo - mo / i) * inv[mo % i] % mo;
		fac[i] = (ll)fac[i - 1] * i % mo,fac_[i] = (ll)fac_[i - 1] * inv[i] % mo;
	}
	for (int i = 1 ; i < n ; ++i)
	{
		int u,v; scanf("%d%d",&u,&v);
		add(u,v);
	}
	for (int i = 1 ; i <= n ; ++i) if (!nx[st[i]]) vis[i] = 1,++m;
	dfs(1,0);
	int fir = z; fa[fir] = len = 0,dfs(fir,0);
	int sec = z; len = -1;
	while (sec != fir) sta[++len] = sec,sec = fa[sec];
	sta[++len] = fir;
	if (len & 1)
	{
		dfs1(sta[len / 2],sta[len / 2 + 1],0,++all);
		dfs1(sta[len / 2 + 1],sta[len / 2],0,++all);
	}
	else
	{
		int rt = sta[len / 2];
		for (int i = st[rt] ; i ; i = nx[i]) dfs1(to[i],rt,1,++all);
	}
	int sum = 0,ans = 0;
	for (int i = 1 ; i <= all ; ++i) sum += num[i];
	inv[sum + 1] = 0;
	for (int i = sum ; i ; --i) inv[i] = ((ll)inv[i] * m % mo + inv[i + 1]) % mo;
	for (int i = 1 ; i <= all ; ++i)
		for (int j = 0 ; j < num[i] ; ++j)
		ans = (ll)(ans + (ll)C(num[i],j) * fac[sum - (num[i] - j + 1)] % mo * (sum - num[i]) % mo * inv[num[i] - j + 1] % mo * fac[num[i] - j] % mo) % mo;
	printf("%d\n",(ll)ans * fac_[sum] % mo);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值