题意
一棵二叉树,初始只有1个节点。每次等概率选择一个叶子节点,给他两个儿子。
求一棵有n个叶子节点的如此生成的二叉树,(1)叶节点的平均深度的期望,(2)树深度的期望
思路
(1)
设 d d d表示当前随机选一个叶子节点的深度的期望(因为等概率,所以 d d d同时也就是叶节点的平均深度的期望)。再设 s u m = c n t ∗ d sum=cnt*d sum=cnt∗d表示节点深度和的期望(cnt是当前叶子节点个数)。
当给这个节点加上两个儿子,变化是: c n t = c n t + 1 , s u m = s u m + 2 ∗ ( d + 1 ) − d cnt=cnt+1,sum =sum+ 2*(d+1)-d cnt=cnt+1,sum=sum+2∗(d+1)−d,然后再用 d = s u m / c n t d=sum/cnt d=sum/cnt重新求出 d d d。
完事。
(2)
给满二叉树上每一个树上的节点标号。然后考虑把某一棵生成的树看成他的生成序列。
那么每一个不同的序列的概率相同,为 ∏ i = 1 n − 1 1 i \prod_{i=1}^{n-1}\frac{1}{i} ∏i=1n−1i1。
那么考虑对生成序列计数。
发现在叶子上面一个一个拓展的话状态很难表示并且难以转移,所以设 f [ i ] [ j ] f[i][j] f[i][j]表示此序列有i个数,深度为j的方案数,然后我们从根上加点。
对于 f [ i ] [ j ] , f[i][j], f[i][j],枚举 k k k表示根节点的左子树有 k k k个节点已经被拓展,右子树有 i − 1 − k i-1-k i−1−k个节点被拓展。因为左右子树的拓展互不影响,两个序列可以随便乱插,所以排列的方案数是 ( i − 1 k ) (^k_{i-1}) (i−1k)。然后左右子树中至少要有一个深度是 j − 1 j-1 j−1,写出式子用前缀和优化一下就好了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100 + 10;
int q, n;
double dep = 0, sum = 0;
void solve1()
{
for (int i = 2; i <= n; ++ i){
sum += dep + 2;
dep = sum / i;
}
printf("%.6f\n", dep);
}
double c[N][N];
double f[N][N], p[N][N], ans = 0;
void solve2()
{
for (int i = 0; i <= n; ++ i){
c[i][0] = 1;
for (int j = 1; j <= i; ++ j)
c[i][j] = c[i-1][j] + c[i-1][j-1];
}
n--;
f[0][0] = p[0][0] = 1; f[1][1] = 1;
for (int i = 1; i <= n; ++ i)
p[0][i] = p[0][i-1] + f[0][i], p[1][i] += p[1][i-1] + f[1][i];
for (int i = 1; i < n; ++ i)
for (int j = 1; j <= n; ++ j){
for (int k = 0; k <= i; ++ k)
f[i+1][j+1] += c[i][k] * (f[k][j] * p[i-k][j] + p[k][j] * f[i-k][j] - f[k][j] * f[i-k][j]);
p[i+1][j+1] = p[i+1][j] + f[i+1][j+1];
}
for (int i = 1; i <= n; ++ i)
ans += f[n][i] * i;
for (int i = 2; i <= n; ++ i)
ans /= i;
printf("%.6f\n", ans);
}
int main()
{
scanf("%d%d", &q, &n);
if (q == 1) solve1();
else solve2();
return 0;
}