題目描述
lre有一棵 個節點的樹, 號點是樹根,第 個點的父親是 。
每個點上都可以放一個彈珠或不放。之後每一回合,lre都會把所有彈珠移動到它們所在的節點的父親節點。
若一個節點上有大於 個彈珠,它們會一起被lre打爆消失。原來在 號節點上的彈珠則會被lre收集起來。
lre覺得這可以出一道題,就問你所有放置彈珠的方案操作完(樹上沒有彈珠)之後可收集到的彈珠的數目之和 。
題解
考慮到同一深度的貢獻爲 ,然後將方案數轉化成概率好像更好計算,所以考慮暴力 : 表示 子樹內,距離 深度爲 的貢獻爲 的概率,轉移的話 。然後這樣是 的。考慮優化,如果我們把深度最深的兒子的 值直接繼承到 ,然後剩下的暴力合併上去的話就是 的效率了。具體證明的話就是每個點對答案的貢獻最多隻有在某一次會被暴力合併。(by 蘇ak:這和長鏈剖分是一樣的)。
代碼
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5,P=1e9+7;
int n,hd[N],V[N],nx[N],t,f[N],p[N],d[N],id[N];
struct O{int x,y;};vector<O>g[N];
int K(int x,int y){
int z=1;
for (;y;y>>=1,x=1ll*x*x%P)
if (y&1) z=1ll*z*x%P;
return z;
}
void add(int u,int v){
nx[++t]=hd[u];V[hd[u]=t]=v;
}
void upd(int x,int u){
for (int i=1;i<=x;i++)
p[i]=1ll*g[id[u]][d[u]-i].x*p[i]%P;
}
void cal(int x,int u){
for (int i=1;i<=x;i++)
(f[i]+=1ll*K(g[id[u]][d[u]-i].x,P-2)*p[i]%P*g[id[u]][d[u]-i].y%P)%=P;
}
void dfs(int u){
int son=n+1,x=0;
for (int v,i=hd[u];i;i=nx[i]){
dfs(v=V[i]);
if (d[v]>d[son]) son=v;
}
if (son>n) id[u]=++t,d[u]=1;
else{
id[u]=id[son];d[u]=d[son]+1;
for (int i=hd[u];i;i=nx[i])
if (V[i]!=son) x=max(d[V[i]],x);
for (int i=1;i<=x;i++) p[i]=1,f[i]=0;
for (int i=hd[u];i;i=nx[i]){
if (V[i]==son) upd(x,son);
else upd(d[V[i]],V[i]);
}
for (int i=hd[u];i;i=nx[i]){
if (V[i]==son) cal(x,son);
else cal(d[V[i]],V[i]);
}
for (int i=1;i<=x;i++)
g[id[u]][d[son]-i].y=f[i],
g[id[u]][d[son]-i].x=(P+1-f[i])%P;
}
g[id[u]].push_back((O){(P+1)>>1,(P+1)>>1});
}
int main(){
cin>>n;
for (int i=1,x;i<=n;i++)
scanf("%d",&x),add(x,i);
dfs(t=0);int x=0;
for (int i=0;i<d[0];i++)
(x+=g[id[0]][i].y)%=P;
cout<<1ll*x*K(2,n+1)%P<<endl;
return 0;
}