题目描述
小 C 在自己家的花园里种了一棵苹果树, 树上每个结点都有恰好两个分支. 经过细心的观察, 小 C 发现每一天这棵树都会生长出一个新的结点.
第一天的时候, 果树会长出一个根结点, 以后每一天, 果树会随机选择一个当前树中没有长出过结点 的分支, 然后在这个分支上长出一个新结点, 新结点与分支所属的结点之间连接上一条边.
小 C 定义一棵果树的不便度为树上两两结点之间的距离之和, 两个结点之间 的距离定义为从一个点走到另一个点的路径经过的边数.
现在他非常好奇, 如果 N 天之后小 G 来他家摘苹果, 这个不便度的期望 E 是多少. 但是小 C 讨厌分数, 所以他只想知道 E×N! 对 P 取模的结果, 可以证明这是一个整数.
题意简介
给定n,定义一个二叉树的权值为树上所有点对的最短距离和,求大小为n 的所有带标号二叉树的权值和。
解题思路
首先考虑n个点的无标号二叉树的构造方案数(左右儿子不同)
当n=1时,方案数为1
当n=2时,方案数为2(空心表示可选节点)
当n=3时,方案数为
2
×
3
=
6
2\times3=6
2×3=6
因此,一颗n各节点的树构造方案数是
n
!
n!
n!
然后开始计算答案,设当前第i个节点,该节点子树大小为j(包含i节点)
则i与i的父亲的连边对答案的贡献为
j
×
(
n
−
j
)
j\times(n-j)
j×(n−j),也就是每一条经过这条边的路径都会产生贡献
构造之前i各节点的方案数为i!,构造子树的方案数为j!,但是因为有标号,所以子树的标号要从(n-i)里选 j-1 个(不算 i 节点),也就是
C
n
−
i
j
−
1
C_{n-i}^{j-1}
Cn−ij−1
最后一部分是生成i+j-1后面的节点,并且这些节点不能放在i的子树中,所以这些节点中的第一个的方案数为 i-1,第2个为 i ,最后一个 是(n-j-1),总体方案数是
也就是
(
n
−
j
−
1
)
!
(
i
−
2
)
!
\frac{(n-j-1)!}{(i-2)!}
(i−2)!(n−j−1)!
把答案乘起来,也就是
∑
i
=
1
n
∑
j
=
1
n
−
j
+
1
j
(
n
−
j
)
j
!
C
n
−
i
j
−
1
i
!
(
n
−
j
−
1
)
!
(
i
−
2
)
!
\sum_{i=1}^{n}\sum_{j=1}^{n-j+1}j(n-j)j!C_{n-i}^{j-1}i!\frac{(n-j-1)!}{(i-2)!}
i=1∑nj=1∑n−j+1j(n−j)j!Cn−ij−1i!(i−2)!(n−j−1)!
但是题目没保证模数p为质数,因此逆元可能没有,所以我们继续化简得
∑
i
=
1
n
∑
j
=
1
n
−
j
+
1
j
(
n
−
j
)
j
!
C
n
−
i
j
−
1
(
n
−
j
−
1
)
!
i
(
i
−
1
)
\sum_{i=1}^{n}\sum_{j=1}^{n-j+1}j(n-j)j!C_{n-i}^{j-1}{(n-j-1)!}i(i-1)
i=1∑nj=1∑n−j+1j(n−j)j!Cn−ij−1(n−j−1)!i(i−1)
算组合数也不能用逆元,
n
2
n^2
n2递推计算就可以了
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2010;
LL n,p,ans;
LL C[N][N],fact[N];
void prework()
{
fact[0]=1;
for(int i=1;i<=n;i++)
fact[i]=fact[i-1]*i%p;
for(int i=0;i<=n;i++)
C[i][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
}
}
}
int main()
{
scanf("%d%d",&n,&p);
prework();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n-i+1;j++)
{
ans=(ans+j*(n-j)%p*fact[j]%p*C[n-i][j-1]%p*fact[n-j-1]%p*i%p*(i-1)%p)%p;
}
}
cout<<ans;
return 0;
}