1. 程式人生 > >UOJ#351. 新年的葉子 概率期望

UOJ#351. 新年的葉子 概率期望

原文連結https://www.cnblogs.com/zhouzhendong/p/UOJ351.html

題目傳送門 - UOJ351

題意

  有一個 n 個節點的樹,每次塗黑一個葉子節點(度為 1 的節點),可以重複塗黑。

  問使得白色部分的直徑發生變化的期望塗黑次數。

  $n\leq 5\times 10^5$

題解

  首先考慮什麼情況下直徑長度會發生改變。

  考慮找到直徑的中點,可能在邊上。

  對於這個直徑相連的每一個子樹,分別算出在這個子樹中的距離直徑中點距離為直徑長度的一半的節點個數。

  於是我們就得到了一些集合。那麼,直徑長度將在 只剩下一個集合有白色節點 的時候發生改變。

  於是,很容易得出菊花圖的做法:

$$ ans = \sum_{i=1}^{n-1} \frac {n-1} {n-i}$$

  但是不是菊花圖的時候,仍然很棘手。

  設總葉子節點個數為 $l$ ,所有集合的元素個數總和為 $tot$ 。

 

官方題解的演算法三應該比較好理解吧

演算法三

 

我們考慮列舉哪個集合最終剩下了,設當前集合大小為 $k$ 。

 

設所有集合大小的和為 $n$ 。

 

我們列舉當其他所有集合都染黑時,當前集合還剩下 $i$ 個沒有被染黑,那麼這樣的情況對答案的貢獻為

 

$$\frac{\binom{k}{i}\times \binom{n-i-1}{n-k-1}\times \binom{n-k}{1}\times(k-i)!\times(n-k-1)!\times (i)!}{n!}\times (\sum_{j=i+1}^{n}\frac{m}{j})$$

 

線性預處理階乘,階乘逆元與逆元的字首和就可以做到 $O(n)$ 的時間複雜度。

 

 

演算法四比較神仙

演算法四

from WuHongxun

http://wuhongxun.blog.uoj.ac/blog/3345

放連結(逃

 

  對於演算法四,我再補充一句:

考慮任何一個方案裡我們染黑到所有集合都變黑了為止,它對答案的貢獻是多少呢?如果 是這個方案裡最後被染黑的集合,那麼是倒數第二個被染黑的時間,反之則是最後一個集合被染黑的時間。

  這裡,如果 x 不是最後一個被染黑的集合,那麼必然全部染黑了。

程式碼

#include <bits/stdc++.h>
using namespace std;
int read(){
	int x=0;
	char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
const int N=500005,mod=998244353;
int n;
vector <int> e[N],S;
int in[N],depth[N],fa[N];
int vis[N];
void dfs(int x,int pre,int d){
	depth[x]=d,fa[x]=pre;
	for (auto y : e[x])
		if (y!=pre)
			dfs(y,x,d+1);
}
int LCA(int x,int y){
	if (depth[x]<depth[y])
		swap(x,y);
	while (depth[x]>depth[y])
		x=fa[x];
	while (x!=y)
		x=fa[x],y=fa[y];
	return x;
}
int FindFar(int x,int d){
	vis[x]=d;
	int res=x;
	for (auto y : e[x])
		if (!~vis[y]){
			int tmp=FindFar(y,d+1);
			if (vis[tmp]>vis[res])
				res=tmp;
		}
	return res;
}
void Get(int x,int d,int md){
	if (d==md)
		S.back()++;
	vis[x]=1;
	for (auto y : e[x])
		if (!vis[y])
			Get(y,d+1,md);
}
void Get_S(){
	memset(vis,-1,sizeof vis);
	int s=FindFar(1,0);
	memset(vis,-1,sizeof vis);
	int t=FindFar(s,0);
	if (depth[s]<depth[t])
		swap(s,t);
	int d=depth[s]+depth[t]-2*depth[LCA(s,t)];
	memset(vis,0,sizeof vis);
	if (d&1){
		for (int i=d/2;i--;)
			s=fa[s];
		t=fa[s];
		vis[s]=vis[t]=1;
		S.push_back(0),Get(s,0,d/2);
		S.push_back(0),Get(t,0,d/2);
	}
	else {
		for (int i=d/2;i--;)
			s=fa[s];
		vis[s]=1;
		for (auto y : e[s])
			S.push_back(0),Get(y,1,d/2);
	}
}
int Pow(int x,int y){
	int ans=1;
	for (;y;y>>=1,x=1LL*x*x%mod)
		if (y&1)
			ans=1LL*ans*x%mod;
	return ans;
}
int Fac[N],Inv[N],Iv[N],h[N];
void Math_Prework(){
	for (int i=Fac[0]=1;i<=n;i++)
		Fac[i]=1LL*Fac[i-1]*i%mod;
	Inv[n]=Pow(Fac[n],mod-2);
	for (int i=n;i>=1;i--)
		Inv[i-1]=1LL*Inv[i]*i%mod;
	for (int i=1;i<=n;i++)
		Iv[i]=1LL*Inv[i]*Fac[i-1]%mod;
	for (int i=1;i<=n;i++)
		h[i]=(h[i-1]+Iv[i])%mod;
}
int C(int n,int m){
	if (m<0||m>n)
		return 0;
	return 1LL*Fac[n]*Inv[m]%mod*Inv[n-m]%mod;
}
int main(){
	n=read();
	for (int i=1;i<n;i++){
		int a=read(),b=read();
		e[a].push_back(b);
		e[b].push_back(a);
		in[a]++,in[b]++;
	}
	dfs(1,0,0);
	Get_S();
	int l=0;
	for (int i=1;i<=n;i++)
		if (in[i]==1)
			l++;
	Math_Prework();
	int tot=0;
	for (auto y : S)
		tot+=y;
	// 設當前剩餘 k 個,總共有 l 個,選出 x 個數:
	// l/k + l/(k-1) + ... + l/(k-x+1)
	// l * (h[k]-h[k-x])
	int ans=0;
	for (auto y : S)
		ans=(1LL*h[tot-y]+ans)%mod;
	ans=(-1LL*((int)S.size()-1)*h[tot]%mod+ans+mod)%mod;
	ans=1LL*ans*l%mod;
	printf("%d",ans);
	return 0;
}