【[SHOI2012]隨機樹】
感覺第一問就非常神仙,還有第二問怎麽被我當成組合數學題來做了
首先是第一問
期望具有線性性,於是深度平均值的期望等於深度和的期望值的平均
設\(dp_x\)表示具有\(x\)個葉子節點的樹的深度和的期望值是多少
我們發現擴展一個葉子節點的實質將其變成兩個深度原來大一的葉節點,所以對整個答案的貢獻也就是這個被擴展的葉子節點的深度乘\(2\),再加上\(2\)
比如我們當前擴展的是葉子節點\(1\),那麽答案就從\(dep_1+dep_2+...+dep_x\)變成了\(2*(dep_1+1)+dep_2+...+dep_x\)
\(dep_2,dep_3\)同理,也就會發現在最終的答案裏每個\(dep\)
設\(dp[x]\)表示\(x\)個葉子節點的期望深度和,\(dp[x]=\sum_{i=1}^xdep_x\)
那麽期望是
\[dp[x+1]=\frac{\sum_{i=1}^x(dep_x+2)+x*\sum_{i=1}^xdep_x}{x}\]
\[dp[x+1]=\frac{2*x+(x+1)\sum_{x=1}^xdep_x}{x}\]
也就是
\[dp[x+1]=\frac{(x+1)dp[x]+2*x}{x}\]
最後的答案就是\(\frac{dp[n]}{n}\)了
之後第二問我就感受到了玄學的力量,各種玄學調參數
第二問好像非常麻煩的樣子,沒有辦法像剛才那個樣子從平均的角度來考慮了,而直接求期望好像不太好求,於是可以求出概率來
設\(dp[x][h]\)表示有\(x\)個葉子節點構成的樹深度為\(h\)的概率是多少,那麽答案就是\(\sum_{h=1}^ndp[n][h]*h\)
我們考慮一下如何求這個\(dp[n][h]\)
有一個比較套路的東西就是枚舉左右子樹有幾個葉子節點
所以就有
\[dp[i][j]=\sum_{k=1}^{i-1}P_{i,k}(dp[k][j-1]*p[i-k][j-1]+dp[i-k][j-1]*p[k][j-1])\]
其中\(p[i][j]=\sum_{k=1}^jdp[i][k]\)也就是一個概率的前綴和,\(P_{i,k}\)表示一共\(i\)個葉子節點其中\(k\)個在左子樹上的概率
也就是枚舉左右子樹的葉子節點的個數,之後對應好相應的深度,乘上這個轉移發生的概率
先不考慮這個\(P_{i,k}\)怎麽求,也會發現上面那個轉移好像有些問題,它算重了左右兩邊子樹的深度都是\(j-1\)的情況,於是上面還需要再減掉\(dp[k][j-1]*dp[i-k][j-1]\)
現在的問題就變成了\(P_{i,k}\)怎麽求了
首先經過感性理解/手玩樣例/歸納證明可以發現,在不同的擴展順序下使得左子樹上有\(k\)個葉子節點的概率是一個固定的值
我們要讓左右兩邊共有\(i\)個葉子節點,也就是說我們一共需要擴展\(i-1\)次,第一次擴展肯定是需要擴展在當前的這個節點上的,於是還要有\(i-2\)次擴展被分給了左右子樹
我們再來考慮一下使得左子樹上有\(k\)個葉子節點的實質是什麽,不就是分給左邊的擴展次數為\(k-1\)嗎,那麽這樣一共有\(\binom{i-2}{k-1}\)種擴展情況會使得左子樹上擴展了\(k-1\)次
又因為這些不同的擴展順序出現的概率是一樣的,所以我們可以考慮一些求出這個概率
這個概率的分母上肯定是\(2*3*4*5*...*(i-1)\),因為一共需要擴展產生\(i\)個節點每次選中左子樹或者右子樹的概率是\(\frac{\text{左/右子樹上葉子節點數量}}{\text{葉子節點的總數量}}\),而分子上由於我們在左邊一共選擇了\(k\)次,所以分母會有\(1*2*..*(k-1)\),也就會有相應的\(1*2*...*(i-k-1)\)
所以
\[P_{i,k}=\frac{\binom{i-2}{k-1}(k-1)!(i-k-1)!}{(i-1)!}\]
我們再順便化一下柿子
\[P_{i,k}=\frac{\frac{(i-2)!}{(k-1)!(i-2-k+1)!}(k-1)!(i-k-1)!}{(i-1)!}=\frac{(i-2)!}{(i-1)!}=\frac{1}{i-1}\]
我下面的代碼預處理了階乘和組合數,其實直接用\(\frac{1}{i-1}\)就好了
我才不會說我看到題解才想起來繼續化柿子的
於是這樣轉移就好了
代碼
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define re register
#define maxn 105
inline int read()
{
char c=getchar();
int x=0;
while(c<‘0‘||c>‘9‘) c=getchar();
while(c>=‘0‘&&c<=‘9‘)
x=(x<<3)+(x<<1)+c-48,c=getchar();
return x;
}
int opt_Q;
int n;
namespace Ask1
{
double dp[maxn];
inline void init()
{
n=read();
dp[1]=0;
for(re int i=1;i<n;i++)
dp[i+1]=((i+1)*dp[i]+2*i)/double(i);
printf("%.6lf",dp[n]/double(n));
}
}
namespace Ask2
{
double dp[maxn][maxn+100],pre[maxn][maxn];
long double fac[maxn];
long double c[maxn][maxn];
inline void init()
{
n=read();
dp[1][0]=1;dp[2][1]=1;dp[3][2]=1;
for(re int i=0;i<=n;i++) pre[1][i]=1;
for(re int i=1;i<=n;i++) pre[2][i]=1;
for(re int i=2;i<=n;i++) pre[3][i]=1;
fac[0]=1;
for(re int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
c[0][0]=1;
for(re int i=1;i<=n;i++) c[i][0]=c[i][i]=1;
for(re int i=2;i<=n;i++)
for(re int j=1;j<n;j++)
c[i][j]=c[i-1][j-1]+c[i-1][j];
for(re int i=4;i<=n;i++)
for(re int j=log2(i);j<=n;j++)
{
for(re int k=1;k<i;k++)
dp[i][j]+=(dp[k][j-1]*pre[i-k][j-1]+dp[i-k][j-1]*pre[k][j-1]-dp[k][j-1]*dp[i-k][j-1])*c[i-2][k-1]*fac[k-1]*fac[i-k-1]/fac[i-1];
pre[i][j]=dp[i][j]+pre[i][j-1];
}
double ans=0;
for(re int h=log2(n);h<=n;h++)
ans+=dp[n][h]*h;
printf("%.6lf",ans);
}
}
int main()
{
opt_Q=read();
if(opt_Q==1) Ask1::init();
else Ask2::init();
return 0;
}
【[SHOI2012]隨機樹】