\(\\\)
\(Description\)
开始有一棵只有一个根节点的树。每次随机选择一个叶子节点,为他添上左右子节点,求:
- 生成一棵有\(N\)个叶节点的树,所有叶节点平均高度的期望。
- 生成一棵有\(N\)个叶节点的树,树高的期望。
约定根节点深度为\(0\)。
- \(N\in [1,100]\)
\(\\\)
\(Solution\)
果然还是太菜了只会抄题解 一道期望和概率间巧妙转化的好题。
\(\\\)
第一问,平均的性质。
考虑每次扩展会随机选择一个叶节点,假设该叶节点的深度为\(d_i\),对总叶节点深度和的贡献为\((d_i+1)\times 2-d_i=d_i+2\)。
等概率的选择,对深度的平均值增量之和\(=\frac{\sum_{v\in Leaf}d_v+2}{|Leaf|}=2+\frac{\sum_{v\in Leaf}d_v}{|Leaf|}=2+ave\)。所以一次扩展节点对期望的增量是\(\frac{ave+2}{|Leaf|}\)。
设\(g[i]\)表示\(i\)个叶节点的深度平均值的期望,有
\[ g[0]=0.0\ ,\ g[i]=\frac{g[i-1]+2}{i}+\frac{g[i-1]\times(i-1)}{i}=g[i-1]+\frac{2}{i} \]
愉快的递推就好。
\(\\\)
第二问,一种期望向概率的转化。
先考虑一种期望的表示方式。
\[ E(X)=\sum_{i=1}^\infty i\times p_i=\sum_{i=1}^\infty\sum_{j=1}^i p_i=\sum_{i=1}^\infty \sum_{j=i}^\infty p_j \]
关于证明,第一个等号显然成立。
第二个等号可以理解为,每个概率会被累加权值那么多次。
第三个等号可以理解为,越大的权值对应的概率被累加的越多,当可取的权值\(+1\)时,对应的所有小于它的数字都会累加上一份最大权的概率,但是小于最大权的数字个数为最大权\(-1\),所以还需要累加上一份。
于是设\(f_i\)表示大于等于\(i\)的概率,进一步转化有
\[ E(X)=\sum_{i=1}^\infty f_i \]
思考如何用这个性质求解树高期望。
将状态定义为一种基于最小值的设计方式。设\(f[i][j]\)表示,有\(i\)个叶节点的树,树高\(\ge j\)的期望,答案有
\[ E(树高)=\sum_{i=0}^{N-1} f[N][i] \]
原理和上面是一样的。
考虑如何求\(f\)数组。有边界\(f[i][0]=1.0\ \big |\ i\in [1,N]\),因为任意时刻树高都会\(\ge 0\)。
然后将任务下放到子树,左右子树的大小分布是等概率的,注意只要不是叶节点就一定左右子树都有。
所以只要有一棵深度合法就好,注意容斥掉两种情况重合时的部分。
\[ f[i][j]=\frac{1}{i-1}\sum_{k=1}^{i-1}\ \big(\ f[k][j-1]+f[i-k][j-1]-f[k][j-1]\times f[i-k][j-1]\ \big) \]
\(\\\)
\(Code\)
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 110
#define R register
using namespace std;
int n,q;
double g[N],f[N][N];
int main(){
scanf("%d%d",&q,&n);
if(q==1){
g[1]=0.0;
for(R int i=2;i<=n;++i) g[i]=g[i-1]+2.0/i;
printf("%.6lf",g[n]);
}
else{
for(R int i=1;i<=n;++i) f[i][0]=1.0;
for(R int i=2;i<=n;++i)
for(R int j=1;j<i;++j){
for(R int k=1;k<i;++k)
f[i][j]+=f[k][j-1]+f[i-k][j-1]-f[k][j-1]*f[i-k][j-1];
f[i][j]/=(i-1);
}
double ans=0.0;
for(R int i=1;i<=n;++i) ans+=f[n][i];
printf("%.6lf\n",ans);
}
return 0;
}