樹的解構 題解

sun123zxy發表於2020-11-27

bsoj7076,沒找到出處...

樹的解構

Mivik 喜歡 Eprom 的解構俱樂部,於是他想解構一棵樹。

Mivik 找到了一棵以 \(1​\) 為根的有 \(n​\) 個結點的有根外向樹。Mivik 會進行 \((n−1)​\) 次操作,每次 Mivik 都會從未刪掉的邊中等概率選擇一條邊將其刪去。記這條邊為 \(a \to b​\) ,則刪去這條邊的代價是刪邊時 \(b​\) 的子樹大小(包括 \(b​\) 自己);刪去這條邊後 \(b​\) 為根的子樹會形成一棵新的以 \(b​\) 為根的有根樹。

Mivik 想知道,他進行這 \((n−1)\) 次操作後期望的代價總和是多少。由於 Mivik 不喜歡太大的數,你只需要輸出期望的值對 \(10^9 + 7​\) 取模的結果。

對於所有測試點,滿足 \(1 \le n \le 2 \times 10^6\) 。保證給出的有根樹合法。

一道並不是特別難但沒有切掉的期望題。

考場寫了鏈過後就一直在想怎麼拆樹,無果而終。然後想到解決排列問題的一個常見的思路是考慮所有排列方案的貢獻和,最後乘個 \(\frac{1}{(n-1)!}\) 就可以了。嘗試在樹形dp邊的排列順序,同樣無果而終。

對整體dp不好做,那就轉而思考每個點對答案的貢獻。

考慮從樹上某個深度(到根需經過的邊數)為 \(d_u\) 的點 \(u\) ,其向上到根的路徑上各邊的相對刪除順序可以用一個階為 \(d_u\) 的排列 \(P\) 表示。若只考慮該路徑,設此時對答案的貢獻為 \(f_u(P)\) ,我們嘗試化簡 \(\sum_{P \in \mathrm{perm(d_u)}} f_u(P)\) 。(其中 \(\mathrm{perm(n)}\) 表示所有 \(n\) 階排列的集合)

對於其中某條位置為 \(i\) 的邊,若其被刪除時點 \(u\) 可對答案產生貢獻,則必須保證刪除該邊時該邊與 \(u\) 仍然連通,也就是對於任意位置為 \(j\)\(j < i\) 的的邊,有 \(P_j > P_i\) 。換句話說,若從後往前 (從上至下)決定邊的刪除時間,那麼一條刪除時間為 \(x\) 的邊必須在 \([1,x]\) 中最後被選中才能滿足上述條件。顯然其發生概率為 \(\frac{1}{x}\) ,故在所有排列中 \(x\) 產生貢獻的次數為 \(\frac{d_u !}{x}\) 。因此

\[\sum_{P \in \mathrm{perm(d_u)}} f_u(P) = \sum_{x=1}^{d_u} \frac{d_u!}{x} \]

\(P\) 的相對順序已經確定,而在整棵樹中還有其他邊的相對順序未被決定,因此點 \(u\) 的總貢獻還要再乘上對 \(P\) 消序後的全部排列方案數

\[\frac{(n-1)!}{d_u!} \sum_{x=1}^{d_u} \frac{d_u!}{x} = (n-1)! \sum_{x=1}^{d_u} \frac{1}{x} \]

整合所有點的貢獻並乘上概率就是最終的期望

\[\begin{aligned} \mathrm{ans} &= \frac{1}{(n-1)!} \sum_{u=1}^n (n-1)! \sum_{x=1}^{d_u} \frac{1}{x} \\ &= \sum_{u=1}^n \sum_{x=1}^{d_u} \frac{1}{x} \end{aligned} \]

意外的約掉了好多玩意兒呢

線性求逆元並字首和即可 \(O(n)​\) 解決。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
ll Rd(){
	int ans=0;bool fh=0;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') fh=1; c=getchar();}
	while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar();
	if(fh) ans=-ans;
	return ans;
}
typedef vector<ll> Vec;
typedef vector<ll>::iterator IVec;
#define foreach(it,v) for(IVec it=v.begin();it!=v.end();it++)
const ll MOD=1E9+7;
#define _ %MOD
ll PMod(ll x){
	if(x<0) return x+MOD;
	else if(x>=MOD) return x-=MOD;
	else return x;
}

const ll MXN=2E6+5;
ll N;Vec chd[MXN];

ll dep[MXN];
void InfoDFS(ll u,ll depth){
	dep[u]=depth;
	foreach(it,chd[u]) InfoDFS((*it),depth+1); 
}

ll inv[MXN],sInv[MXN];
void SpawnInv(){
	inv[1]=1;for(ll i=2;i<=N;i++) inv[i]=PMod(-(MOD/i)*inv[MOD%i]_); 
	sInv[1]=inv[1];for(ll i=2;i<=N;i++) sInv[i]=PMod(sInv[i-1]+inv[i]);
}
void Solve(){
	SpawnInv(); 
	InfoDFS(1,0);
	ll ans=0;
	for(ll u=1;u<=N;u++) ans=PMod(ans+sInv[dep[u]]);
	printf("%lld",ans); 
}
int main(){
	//freopen("deconstruct.in","r",stdin);
	//freopen("deconstruct.out","w",stdout);
	N=Rd();
	for(ll i=2;i<=N;i++) chd[Rd()].push_back(i);
	Solve();
	return 0;
}

相關文章