[HAOI2018] 苹果树

题意:

小 C 在自己家的花园里种了一棵苹果树, 树上每个结点都有恰好两个分支. 经过细心的观察, 小 C 发现每一天这棵树都会生长出一个新的结点.

第一天的时候, 果树会长出一个根结点, 以后每一天, 果树会随机选择一个当前树中没有长出过结点 的分支, 然后在这个分支上长出一个新结点, 新结点与分支所属的结点之间连接上一条边.

小 C 定义一棵果树的不便度为树上两两结点之间的距离之和, 两个结点之间 的距离定义为从一个点走到另一个点的路径经过的边数.

现在他非常好奇, 如果 N 天之后小 G 来他家摘苹果, 这个不便度的期望 E 是多少. 但是小 C 讨厌分数, 所以他只想知道 E×N! P 取模的结果, 可以证明这是一个整数.

(这里,节点长出的顺序不同,即使最后形态一样,也算是不同的)

n<=2000,p<=1e9

分析:

期望好懵啊。

考虑每种不同方案的树的产生的概率都是相等的。必然分母就是种类数了。

我们第一天只有1种方案,第二天2个空位2种,第三天3个空位3种。。。相当于,每放上一个节点,就少一个空位,多出两个。

所以一共有N!种不同的方案数。

这下明白了为什么要乘上N!了。

所以我们要做的就是统计所有方案数里,每种方案任意点对的距离和,再做和。

 

发现,我们针对每种方案枚举点对简直丧心病狂。

又发现,我们枚举每条树边被点对经过的次数,与枚举点对算距离是等价的。

对于节点i,i到父亲的边被经过的次数,就是size[i]*(n-size[i]),子树内所有的点,与子树外所有的点组成的所有点对都会经过这条边。

我们肯定要循环一遍i从2到n,表示枚举i到父亲的所有n-1条边。

但是,子树大小不知道,而且不确定,发现,n<=2000复杂度支持n^2,所以我们内层再枚举一个子树大小siz(包括根)

 

对于给定的i和siz,我们要统计,在所有可能情况中,这条边做的总贡献。

首先,每个方案作出的贡献,都是(n-siz)*siz

其次,我们要整出方案数。

利用乘法原理,方案数就是子树的生成方案数,乘上子树外生成的方案数。

i号点就是我们现在子树的根节点。

 

先考虑子树:

子树的形态,有siz!种,但是不要忘了,生成先后也算不同的方案,即编号不同,子树也不同。

由于前面i个点已经确定,所以,还剩n-i个点,要选siz-1个点。C(siz-1,n-i)

 

再考虑子树外:

子树外的形态,第一次1个空位一种,第二次两种,……,第i次i种,但是,到了i+1次,它已经不能放在i的两侧了。因为我们已经把它给了i的子树算过了。

所以,子树外的形态就是,i!*((i-1)*(i-2)*..*(n-(siz-1)-2).

而子树外的编号就不用再乘了,因为我们考虑子树内的时候,已经预留了编号 ,剩下的就给了子树外。

 

所以:

点对贡献 -> 每条边被点对覆盖次数 -> 枚举边 -> 枚举子树大小 -> siz*(n-siz) 个点对会经过i到父节点的边 -> 子树形态 -> 子树编号 (即生成节点编号的顺序) -> 子树外形态 (编号之前留下的就是,不用再算了) -> 变形 推公式 -> 定范围。

公式:          

    (2<=i<=n)(1<=size<=n-i+1) (size[i]*(n-size[i]))*size[i]!*C(size[i]-1,n-i)*i!*((i-1)*...*(n-(size[i]-1)-2))

-> 组合数打表,最后面的阶乘,利用循环顺序处理

或者:

    (2<=i<=n)(1<=size<=n-i+1) (size[i]*(n-size[i]))*size[i]!*C(size[i]-1,n-i)* (n-siz-1)! * (i-1) * i 

 利用之前打好的阶乘表直接处理,效率会更高。

复杂度:O(n^2)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2000+2;
int n,p;
ll c[N][N];
ll fac[N];
ll ans;
ll last;
ll cas;
int main()
{
    scanf("%d%d",&n,&p);
    c[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        c[i][0]=1;
        for(int j=1;j<=n;j++)
         c[i][j]=(c[i-1][j-1]+c[i-1][j])%p;//组合数打表
    }
    fac[0]=1;
    for(int i=1;i<=n;i++)
     fac[i]=(fac[i-1]*i)%p;//阶乘打表
    for(int i=2;i<=n;i++)
    {
     last=1;
     cas=i-1;
     for(int siz=n-i+1;siz>=1;siz--)
     {
         ans=(ans+siz*(n-siz)*fac[siz]%p*c[n-i][siz-1]%p*fac[i]%p*last)%p;
         last=(last*cas)%p;//last记录子树外那部分的乘积
         cas++;
     }
    }//计算
    printf("%lld",ans);
    return 0;
}

 

posted @ 2018-05-24 09:32  *Miracle*  阅读(242)  评论(0编辑  收藏  举报