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

博客介绍了NOIP2018模拟赛的一道题目,涉及树的直径问题。文章分析了如何寻找树的直径、必经点和边,讨论了直径为偶数和奇数时的不同情况,并探讨了计算使得直径变小的期望步数的方法,包括利用概率和权值来求期望的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给出一棵n个节点的树,我们每次随机染黑一个叶子节点(可以重复染黑),操作无限次后,这棵树的所有叶子节点必然全部会被染成黑色。
定义R为这棵树不经过黑点的直径,求使R第一次变小期望的步数。

解题思路

比赛的时候,想到了一个错误的方法:去找每条直径对答案的贡献,然后点分治寻找直径的条数。
正解:
寻找套路。寻找直径R,寻找必经点和必经边。
考虑R的奇偶性。
如果R为偶,那么必然有必经点p。
以p为根建树,则深度为 R 2 \frac{R}{2} 2R的点才可能成为直径的端点。
这些点按照它们是以p的哪个儿子为根的子树来分集合。
如果R为奇,那么必然有必经边 ( u , v ) (u,v) (u,v)
则u的子树的深度为 R − 1 2 \frac{R-1}{2} 2R1的点为第一个集合,v的子树的深度为 R − 1 2 \frac{R-1}{2} 2R1的点为第二个集合。(dep[u]=dep[v]=0)
共2个集合。
必经点,必经边的存在,是可以证明的。
直径改变了,当且仅当这些点被删剩一个集合。
期望怎么求?
求期望的套路:
①期望的线性性,考虑一个什么东西对答案的贡献。(这道题目中不可用)
②顺推DP, f [ i ] = ∑ ( p j ∗ f [ j ] ) f[i]=\sum(p_j*f[j]) f[i]=(pjf[j]) 不太好弄。
③概率*权值,将总和求出来,然后除以分母。
这道题目中可以用。
考虑剩下哪个集合,这个集合中被删去了多少个点。
设剩下的集合原本的大小为 d d d,删去了 i i i个点。
所有集合的大小之和为 d 0 d_0 d0,叶子的总数为 m m m
删除 d 0 d_0 d0个点的顺序共有 d 0 ! d_0! d0!种。
则对答案的贡献为:
∑ i = 0 d − 1 P ∗ E \sum_{i=0}^{d-1}P*E i=0d1PE

∑ i = 0 d − 1 C d i ∗ ( d 0 − d + i − 1 ) ! ∗ ( d 0 − d ) ∗ ( d − i ) ! d 0 ! ∗ ∑ j = d − i + 1 d 0 m j \sum_{i=0}^{d-1}\frac{C_d^i*(d_0-d+i-1)!*(d_0-d)*(d-i)!}{d_0!}*\sum_{j=d-i+1}^{d_0}\frac{m}{j} i=0d1d0!Cdi(d0d+i1)!(d0d)(di)!j=di+1d0jm
解释:最后一个点不能够删所枚举的集合中的元素。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 500010
#define mo 998244353
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
	int to,next;
}edge[N<<1];
int tot,head[N];
int i,j,k,l,n,m,u,v,rt,R;
int dep[N],fa[N],du[N];
int ny[N],Jc[N],Ny[N],cheng[N];
int gs[N],zs,opz,d0,d;
int temp,ans;
bool bz[N];
int read(){
	int fh=0,rs=0;char ch=0;
	while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
	if(ch=='-')fh=1,ch=getchar();
	while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
	return fh?-rs:rs;
}
void write(int x){
	if(x>9)write(x/10);
	P(x%10+'0');
}
void lb(int x,int y){
	edge[++tot].to=y;
	edge[tot].next=head[x];
	head[x]=tot;
	du[x]++;
}
void dg(int x){
	int i;
	for(i=head[x];i;i=edge[i].next)
	if(fa[x]^edge[i].to){
		fa[edge[i].to]=x;
		dep[edge[i].to]=dep[x]+1;
		dg(edge[i].to);
	}
}
void dg1(int x){
	int i;
	if(dep[x]==(R>>1)){
		gs[opz]++;
	}
	for(i=head[x];i;i=edge[i].next)
	if(fa[x]^edge[i].to){
		fa[edge[i].to]=x;
		if(bz[edge[i].to])continue;
		dep[edge[i].to]=dep[x]+1;
		dg1(edge[i].to);
	}
}
int ksm(int x,int y){
	int rs=1;
	for(;y;y>>=1,x=(1ll*x*x)%mo)if(y&1)rs=(1ll*rs*x)%mo;
	return rs;
}
void pre(){
	int i;
	fo(i,1,n)ny[i]=ksm(i,mo-2);
	Jc[0]=Jc[1]=Ny[0]=Ny[1]=1;
	fo(i,2,n)Jc[i]=(1ll*i*Jc[i-1])%mo;
	Ny[n]=ksm(Jc[n],mo-2);
	fd(i,n-1,2)Ny[i]=(1ll*Ny[i+1]*(i+1))%mo;
}
int C(int n,int m){
	return ((1ll*Jc[n]*Ny[m])%mo*Ny[n-m])%mo;
}
int main(){
	n=read();
	fo(i,1,n-1){
		u=read(),v=read();
		lb(u,v);lb(v,u);
	}
	fo(i,1,n){
	    m=m+(du[i]==1);	
	    if(du[i]>1&&!rt)rt=i;
	}
	dep[rt]=1;
	dg(rt);
	rt=0;
	fo(i,1,n)if(dep[i]>dep[rt])rt=i;
	dep[rt]=0;
	fo(i,1,n)fa[i]=0;
	dg(rt);
	u=0;
	fo(i,1,n)if(dep[i]>dep[u])u=i;
	R=dep[u];
	if(R&1){
		fo(i,1,R>>1)u=fa[u];
		v=fa[u];
		bz[u]=1,bz[v]=1;
		opz=1;
		fo(i,1,n)fa[i]=0;
		dep[u]=0;
		dg1(u);
		opz=2;
		fo(i,1,n)fa[i]=0;
		dep[v]=0;
		dg1(v);
		zs=2;
	}else{
		fo(i,1,R>>1)u=fa[u];
		fo(i,1,n)fa[i]=0;
		bz[u]=1;
		dep[u]=0;
		for(i=head[u];i;i=edge[i].next){
			opz++;
			dep[edge[i].to]=1;
			dg1(edge[i].to);
			if(gs[opz]==0)opz--;
		}
		zs=opz;
	}
	fo(i,1,zs)d0+=gs[i];
	pre();
	fo(i,1,n){
		cheng[i]=(1ll*m*ny[i])%mo;
		cheng[i]=(cheng[i]+cheng[i-1])%mo;
	}
	fo(j,1,zs){
		d=gs[j];
		fo(i,0,d-1){
			temp=(1ll*C(d,i)*Jc[d0-d+i-1])%mo;
			temp=(1ll*temp*(d0-d))%mo;
			temp=(1ll*temp*(cheng[d0]-cheng[d-i]+mo)%mo)%mo;
			temp=(1ll*temp*Jc[d-i])%mo;
			ans=(ans+temp)%mo;
		}
	}
	ans=(1ll*ans*Ny[d0])%mo;
	write(ans);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值