题意:
小 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; }