【[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\)都出现了\(x+1\)

\(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;
}
posted @ 2019-01-01 19:51  asuldb  阅读(237)  评论(0编辑  收藏  举报