P3830 SHOI2012]随机树

链接 P3830 [SHOI2012]随机树

  • 这题好难啊……

  • 首先第一问:

  • \(f_i\)为有\(i\)个叶子节点的树的平均深度,\(d_i\)为任意树的任意一点深度

  • 则有\(f_i*i\)为有\(i\)个叶子节点的树的深度和

  • 随机一次操作所能对平均深度和的贡献\((d_i+1)*2-d_i\)

  • 可得:\(f_i=f_{i-1}+\frac{2}{i}\)

  • 可以看成是新增加了\(2\)的深度贡献分配在现在的\(i\)个点上。

  • 然后第二问:

  • \(g_{i,j}\)表示在有\(i\)个叶子的随机树中,树的深度等于\(j\)的概率。

  • 答案为\(\sum g_{n,i}*i\)

  • 然而(题解说他不好转移)我们发现他不好转移……。

  • 改成\(f_{i,j}\)表示在有\(i\)个叶子的随机树中,树的深度大于等于\(j\)的概率(猫贼讲过的套路)。

  • 显然,对于某一棵随机的,左儿子有\(k\)个叶子,右儿子有\(i−k\)个叶子的树,它的深度不小于\(j\)的概率为

\[G_k=f_{k,j-1}+f_{i-k,j-1}-f_{k,j-1}*f_{i-k,j-1} \]

  • 这也就是说,事件\(a,b\)发生的概率\(=\)事件\(a\)发生的概率加上事件\(b\)发生的概率,再减去两个事件同时发生的概率。
  • 然后题解说\(f_{i,j}=\sum \frac {G_k}{i-1}\)…………然而没有证明啊。
  • 证明一下:
  • 设生成一棵的左儿子恰有\(k\)个叶子,右儿子有\(i-k\)个叶子的树的概率为\(P_k\)
  • 那么我们只要证明对于任何\(P_k\),均有$$P_k=\frac {1}{i-1}$$
  • 考虑一棵左儿子有\(k\)个叶子,右儿子有\(i−k\)个叶子的树是怎样生成的?
  • 相当于你有长度为\(i\)的序列,每次选择改变左儿子或者右儿子,那么方案数为:
  • \[C_{i-2}^{k-1} \]

  • 因为第一次一定同时增加了左右子树一个叶子,所以不是\(C_{i}^{k}\)
  • 注意操作和操作之间是有区别的,所以操作的方案数乘阶乘,为\((i-2)!\)
  • 注意这是一个和\(k\)没有关系的量,即任意\(P_k\)均相等,所以得证。
  • 初始条件\(f_{i,0}=1\),表示一定存在深度。
  • 注意到答案显然不是\(\sum f_{n,i}*i\)了,考虑怎么算答案。
  • 发现\(f\)实际上就是\(g\)的前缀和,即\(f_{n,i}=\sum_{k=i}^{n}g_{n,k}\)
  • 考虑把这个东西写成一个矩阵,\(f\)的每一项是横着算得,而答案是竖着算得,所以答案就是
  • \[\sum f_{n,i} \]

#include<bits/stdc++.h>
#define R register int
#define ll long long
#define db double 
using namespace std;
const int N=200;
int q,n;db g[N],f[N][N],ans;
void sol1(){
	for(R i=2;i<=n;++i)g[i]=g[i-1]+2.0/(db)i;
	printf("%.6lf\n",g[n]);
}
void sol2(){
	for(R i=1;i<=n;++i)f[i][0]=1;
	for(R i=2;i<=n;++i)
		for(R j=1;j<i;++j){
			for(R 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]/=(db)(i-1);
		}
//	for(R i=1;i<n;++i)printf("f[%d][%d]=%.5lf\n",n,i,f[n][i]);
	for(R i=1;i<n;++i)ans+=f[n][i];
	printf("%.6lf\n",ans);
}
int main(){
	freopen("s.in","r",stdin);
	cin>>q>>n;if(q==1)sol1();else sol2();
	return 0;
}

posted @ 2018-10-23 22:41  Tyher  阅读(161)  评论(0编辑  收藏  举报