1. 程式人生 > 其它 >UOJ#351-新年的葉子【樹的直徑,數學期望】

UOJ#351-新年的葉子【樹的直徑,數學期望】

正題

題目連結:https://uoj.ac/problem/351


題目大意

給出\(n\)個點的一棵樹,開始所有點都是白色,每次隨機點黑一個葉子(可以重複點),求期望多少次能使得白色點構成的圖直徑發生變化。
答案對\(998244353\)取模

\(1\leq n\leq 5\times 10^5\)


解題思路

考慮什麼時候會直徑會產生變化。

假設直徑的長度\(L\)為偶數,那麼所有的直徑都有一個共同的中心點,設為\(x\)。此時我們需要在\(x\)的兩棵子樹中各自找到兩個深度為\(\frac L 2\)的葉子,那麼就可以組成一條直徑。

換句話說,把所有深度為\(\frac L 2\)葉子取出來,然後把它們按照在那個根的子樹中分成若干個集合。然後當我們染色到只有一個集合沒有全部染色的時候就結束了。

那麼現在問題變成給出若干個集合和一些集合外的點,每次染一個點,求期望多少次能夠染成只有一個集合沒有全部染色。
考慮總共有\(n\)個點,有\(i\)個已經染色了,那麼染色下任意一個的概率就是\(\frac{i}{n}\),期望就是\(\frac{n}{i}\)

預處理\(f_i=\sum_{j=1}^i\frac{n}{j}\),然後我們可以考慮把集合中的點排列然後按順序染,最後除上方案就好了。

假設所有集合中總共有\(m\)個點,目前列舉到的集合有\(k\)個點,然後染到這個集合剩下\(p\)個點的時候其他集合都染完了,那麼期望就是

\[\frac{1}{m!}\binom{k}{p}\times \left(\ (m-p)!-(k-p)(m-p-1)!\ \right)\times p!\times (f_m-f_p) \]

(中間的減法是為了保證最後剩下的\(p\)

個點前面一定是一個不是列舉集合中的點,然後\(f_m-f_p\)是因為我們假設不在集合中的點已經染色了,那麼剩下需要染\(m-p\)個)。

至於直徑長度是奇數的情況,那麼有兩個中心點,也就是有一條中心邊,分成兩個集合按照上面的搞就好了。

時間複雜度:\(O(n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5e5+10,P=998244353;
struct node{
	ll to,next;
}a[N<<1];
ll n,m,cnt,mxdis,root,tot,ans;
ll ls[N],v[N],pre[N],fac[N],inv[N],f[N];
void addl(ll x,ll y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void findL(ll x,ll fa,ll dis){
	if(dis>mxdis)mxdis=dis,root=x;
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		pre[y]=x;findL(y,x,dis+1);
	}
	return;
}
void markL(ll x,ll fa,ll dis,ll k){
	if(dis==mxdis/2&&(!a[ls[x]].next))v[k]++;
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		markL(y,x,dis+1,k);
	}
	return;
}
ll C(ll n,ll m)
{return fac[n]*inv[m]%P*inv[n-m]%P;}
signed main()
{
	scanf("%lld",&n);
	for(ll i=1,x,y;i<n;i++){
		scanf("%lld%lld",&x,&y);
		addl(x,y);addl(y,x);
	}
	ll k=0;
	for(ll i=1;i<=n;i++)k+=!(a[ls[i]].next);
	inv[0]=inv[1]=fac[0]=1;
	for(ll i=2;i<N;i++)inv[i]=P-inv[P%i]*(P/i)%P;
	for(ll i=1;i<N;i++)f[i]=(f[i-1]+k*inv[i]%P)%P;
	for(ll i=1;i<N;i++)fac[i]=fac[i-1]*i%P,inv[i]=inv[i-1]*inv[i]%P;
	findL(1,0,0);mxdis=0;
	findL(root,0,0);
	if(mxdis&1){
		ll x=root;
		for(ll i=1;i<=mxdis/2;i++)x=pre[x];
		ll y=pre[x];markL(x,y,0,1);markL(y,x,0,2);
		cnt=2;
	}
	else{
		ll x=root;
		for(ll i=1;i<=mxdis/2;i++)x=pre[x];
		for(ll i=ls[x];i;i=a[i].next)
			cnt++,markL(a[i].to,x,1,cnt);
	}
	for(ll i=1;i<=cnt;i++)m+=v[i];
	for(ll i=1;i<=cnt;i++)
		for(ll j=1;j<=v[i];j++){
			ll w=(f[m]-f[j]+P)%P;
			w=(fac[m-j]-(v[i]-j)*fac[m-j-1]%P+P)%P*fac[j]%P*w%P;
			(ans+=w*inv[m]%P*C(v[i],j)%P)%=P;
		}
	printf("%lld\n",ans); 
	return 0;
}