[SHOI2012]隨機樹[期望dp]
阿新 • • 發佈:2018-11-01
題意
初始 \(1\) 個節點,每次選定一個葉子節點並加入兩個兒子直到葉子總數為 \(n\),問葉子節點深度和的平均值的期望以及最大葉子深度的期望。
\(n\leq 100\) .
分析
對於第一問,根據答案定義狀態 \(f_i\) 表示有 \(i\) 個葉子節點的深度和平均值的期望。
考慮對於之前的每一棵樹對期望的貢獻,記其發生的概率為 \(p\) ,深度和為 \(w\) ,有 \(i-1\) 個葉子節點。貢獻為 \(p*\frac{w}{i-1}\) 。現在要多選定一個葉子節點有 \(i-1\) 種方案,總貢獻可以寫成:
\[p*\frac{1}{i-1}*\frac{(i-1)w+w+2(i-1)}{i} =\frac{ip\frac{w}{i-1}+2p}{i}\]
也就有\(f_i=f_{i-1}+\frac{2}{i}\)。對於第二問,定義狀態 \(g_{i,j}\) 表示子樹內有 \(i\) 個葉子,最大深度為 \(j\) 的概率。
再定義 \(p_{i,j}\) 表示 \(i\) 個葉子節點有 \(j\) 個在左子樹的概率,轉移:
\[f_{i,j}=\sum_{l=1}^{i-1}p_{i,l}*\sum_x\sum_y[\max(x,y)+1=j]f_{l,x}*f_{i-l,y}\]\(p\) 的遞推直接列舉最後一個葉子是接在左子樹還是右子樹即可。
可以字首和優化,總時間複雜度為 \(O(n^3)\).
程式碼
#include<bits/stdc++.h> using namespace std; #define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].last,v=e[i].to) #define rep(i,a,b) for(int i=a;i<=b;++i) #define pb push_back typedef long long LL; inline int gi(){ int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();} return x*f; } template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;} template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;} const int N=104; int type,n; namespace task1{ double f[N]; void solve(){ f[1]=0; rep(i,2,n) f[i]=f[i-1]+2.0/i; printf("%.6lf\n",f[n]); } } namespace task2{ double f[N][N],s[N][N],p[N][N]; void solve(){ p[2][1]=1; rep(i,3,n)rep(j,1,i-1) p[i][j]=( p[i-1][j-1]*1.0*(j-1)/(i-1) + p[i-1][j]*1.0*(i-1-j)/(i-1)); f[1][0]=1;rep(j,0,n) s[1][j]=(j?s[1][j-1]:0)+f[1][j]; f[2][1]=1;rep(j,1,n) s[2][j]=s[2][j-1]+f[2][j]; rep(i,3,n){ rep(j,1,i-1){ rep(l,1,i-1) f[i][j]+=p[i][l]*((j-1>=0?s[l][j-1]:0)*(j-1>=0?f[i-l][j-1]:0)+(j-1>=0?f[l][j-1]:0)*(j-2>=0?s[i-l][j-2]:0)); s[i][j]=s[i][j-1]+f[i][j]; } fill(s[i]+i,s[i]+1+n,s[i][i-1]); } double ans=0; for(int j=0;j<=n;++j) ans+=f[n][j]*j; printf("%.6lf\n",ans); } } int main(){ type=gi(),n=gi(); if(type==1) task1::solve(); else task2::solve(); return 0; }