卧薪尝胆,厚积薄发。
SHOI2012 随机树
Date: Fri Sep 28 20:49:14 CST 2018
In Category:
NoCategory
Description:
开始有一棵只有一个根节点的树。每次随机选择一个叶子节点,为他添上左右子节点,求:
- 生成一棵有 $N$ 个叶节点的树,所有叶节点平均高度的期望。
- 生成一棵有 $N$ 个叶节点的树,树高的期望。
$1\leqslant n\leqslant 100$
Solution1:
我的做法
首先有一个性质,就是如果当前树的叶子节点数为
$i$
,那么树的左子树的叶子节点数取
$[1,i-1]$
的概率是完全相同的,都是
$\frac 1{i-1}$
。
证明的话,总共有
$n$
个叶子节点,设左子树有
$k$
个叶子节点,左子树一定被选到了
$k-1$
次,也就是说生成的方案数有
$C_{n-1-1}^{k-1}$
,而每一种方案的概率一定可以表示成
$\frac\cdots2\times\frac\cdots 3\times\cdots\times\frac\cdots{n-1}$
,分母是
$n-1$
的阶乘,考虑分子,一定是
$(k-1$
的阶乘
$)\times (n-k-1$
的阶乘
$)$
,所以这
$C_{n-2}^{k-1}$
种方案的概率是相同的,都是
$\frac{(k-1)!(n-k-1)!}{(n-1)!}$
,总概率就是
$\frac 1 {n-1}$
。
第一问:
既然有这么一个性质,那么设
$f[i]$
表示一棵有
$i$
个叶子节点的树的叶子节点高度和的期望,由期望的线性性得
$ans=f[n]/n$
,转移就枚举树的左子树的叶子节点个数
$k$
,方程为
$f[i]=\frac{f[k]+k+f[i-k]+i-k}{i-1}$
。时间复杂度
$O(n^2)$
。
第二问:
设
$f[i][j]$
表示
$i$
个叶子节点的树深度为
$j$
的概率,必须有一个子树深度为
$j-1$
,转移枚举一下子树大小和另一棵子树的深度,
$\begin{align}f[i][j]+=\frac 1{i-1}\sum_{k=1}^{i-1}\sum_{l=0}^{k-1}2\times f[k][j-1]\times f[i-k][l]\end{align}$
,还要减掉多算的两个子树
$\begin{align}f[i][j]-=\frac 1{i-1}\sum_{k=1}^{i-1}f[k][j-1]\times f[i-k][j-1]\end{align}$
,最后答案为:
$$
ans=\sum_{i=1}^{n-1}f[n][i]\times i
$$
发现转移中
$l$
和谁都没什么关系,可以前缀和优化一下,时间复杂度
$O(n^3)$
。
Code:
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int q,n;
#define MAXN 110
double f[MAXN];
double g[MAXN][MAXN],s[MAXN][MAXN];
int main()
{
scanf("%d%d",&q,&n);
if(q == 1)
{
f[2] = 2;
for(int i = 3;i <= n;++i)
for(int j = 1;j < i;++j)
f[i] += (double)(f[j] + f[i - j] + i) / (i - 1);
printf("%.6lf\n",f[n] / n);
}
else
{
g[1][0] = 1;
for(int i = 0;i <= n;++i)s[1][i] = 1;
for(int i = 2;i <= n;++i)
{
for(int j = 1;j < i;++j)
{
double res = 0;
for(int k = 1;k < i;++k)
{
/*for(int l = 0;l <= j - 1;++l)
{
res += g[k][j - 1] * g[i - k][l];
}*/
res += g[k][j - 1] * s[i - k][j - 1];
}
res *= 2;
for(int k = 1;k < i;++k)
{
res -= g[k][j - 1] * g[i - k][j - 1];
}
res /= (i - 1);
g[i][j] = res;
}
for(int j = 1;j <= n;++j)
{
s[i][j] = s[i][j - 1] + g[i][j];
}
}
double ans = 0;
for(int i = 1;i <= n;++i)
{
ans += g[n][i] * i;
}
printf("%.6lf\n",ans);
}
return 0;
}
Solution2:
正解
第一问:
对于叶子
$v$
,设他的深度为
$d_v$
,那么扩展他会产生两个新的深度为
$d_v+1$
的叶子,失去他这个深度为
$d_v$
的叶子,增量为
$2\times (d_v+2)-d_v=d_v+2$
,平均增量为
$f[i]+2$
。考虑期望的增量,设
$f[i]$
表示
$i$
个叶子节点的树的叶子深度,期望的增量为:
$$
f[i]=\frac{f[i-1]\times(i-1)+f[i-1]+2}{i}=f[i-1]+\frac 2 i
$$
所以得到递推式
$f[i]=f[i-1]+\frac 2{i}$
。
第二问:
设
$p[i][j]$
表示
$i$
个叶子节点的树深度为
$j$
的概率,这题答案就是:
$$
\sum_{i=1}^{n-1}i\times p[n][i]=\sum_{i=1}^{n-1}\sum_{j=1}^ip[n][i]=\sum_{j=1}^{n-1}\sum_{i=j}^{n-1}p[n][i]
$$
设
$f[i][j]$
表示
$i$
个叶子节点的树深度
$\geqslant j$
的概率,那么答案就是:
$$
\sum_{i=1}^{n-1}f[n][i]
$$
还是按照上面那个性质和转移,有:
$$
f[i][j]=\frac 1{i-1}\sum_{k=1}^{i-1}(f[k][j-1]+f[i-k][j-1]-f[k][j-1]\times f[i-k][j-1])
$$
时间复杂度
$O(n^3)$
。
Code:
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int q,n;
#define MAXN 110
double g[MAXN];
double f[MAXN][MAXN];
int main()
{
scanf("%d%d",&q,&n);
if(q == 1)
{
g[1] = 0;
for(int i = 2;i <= n;++i)g[i] = g[i - 1] + 2.0 / i;
printf("%.6lf\n",g[n]);
return 0;
}
else
{
for(int i = 1;i <= n;++i)f[i][0] = 1.0;
for(int i = 1;i <= n;++i)
{
for(int j = 1;j < i;++j)
{
for(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;
for(int i = 1;i < n;++i)
{
ans += f[n][i];
}
printf("%.6lf\n",ans);
}
return 0;
}
Copyright © 2020
wjh15101051
ღゝ◡╹)ノ♡