题目
给出一棵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}
2R−1的点为第一个集合,v的子树的深度为
R
−
1
2
\frac{R-1}{2}
2R−1的点为第二个集合。(dep[u]=dep[v]=0)
共2个集合。
必经点,必经边的存在,是可以证明的。
直径改变了,当且仅当这些点被删剩一个集合。
期望怎么求?
求期望的套路:
①期望的线性性,考虑一个什么东西对答案的贡献。(这道题目中不可用)
②顺推DP,
f
[
i
]
=
∑
(
p
j
∗
f
[
j
]
)
f[i]=\sum(p_j*f[j])
f[i]=∑(pj∗f[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=0∑d−1P∗E
即
∑
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=0∑d−1d0!Cdi∗(d0−d+i−1)!∗(d0−d)∗(d−i)!∗j=d−i+1∑d0jm
解释:最后一个点不能够删所枚举的集合中的元素。
代码
#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;
}
博客介绍了NOIP2018模拟赛的一道题目,涉及树的直径问题。文章分析了如何寻找树的直径、必经点和边,讨论了直径为偶数和奇数时的不同情况,并探讨了计算使得直径变小的期望步数的方法,包括利用概率和权值来求期望的策略。

被折叠的 条评论
为什么被折叠?



