Summary
题意很清楚:
小 \(C\) 在自己家的花园里种了一棵苹果树, 树上每个结点都有恰好两个分支.
经过细心的观察, 小 \(C\) 发现每一天这棵树都会生长出一个新的结点.
第一天的时候, 果树会长出一个根结点, 以后每一天, 果树会随机选择一个当前树中没有长出过结点的分支, 然后在这个分支上长出一个新结点, 新结点与分支所属的结点之间连接上一条边.
小 \(C\) 定义一棵果树的不便度为树上两两结点之间的距离之和, 两个结点之间 的距离定义为从一个点走到另一个点的路径经过的边数.
现在他非常好奇, 如果 \(N\) 天之后小 \(G\) 来他家摘苹果, 这个不便度的期望 \(E\) 是多少. 但是小 \(C\) 讨厌分数, 所以他只想知道 \(E \times N!\) 对 \(P\) 取模的结果, 可以证明这是一个整数.
Solution
一开始想用树形DP,但是发现子树间不是独立的,所以用不了。
首先可以发现,每次加上一个点以后,其父节点少了一个空位,但是他自己有提供了两个空位。
因此每增加一个节点,都会多一个空位。添加第\(i\)个点的时候,就有\(i\)个空位。
所以总方案数为\(N!\)。因此,\(E\times N!\)就是所有形态的树的的不便度总和。
根据这种题目的套路,应该考虑每条边的贡献。但是发现所有的边都是不确定的。
但是好好想想,其实只要一个点的子树大小确定了,其连向父节点的边的贡献也就定下来了。每一个子树内的点和子树外的点的搭配,都会经过这条边。因此,假设子树大小为\(j\),那么那个点连向其父节点的边的贡献就是\(j\cdot(n-j)\)。
然后考虑每一个子树的根节点,设其为\(i\)。因为加点是有顺序的,我们不妨假设编号为\(i\)的点是第\(i\)个加入的。
那么这\(i\)个点的形态就有\(i!\)种。然后子树内,也有\(j!\)种形态。
但是子树内有哪些点呢?
首先这些点的组合肯定是\(C_{n-i}^{j-1}\)种。因为加点顺序是定下来的,所以不需要考虑加点顺序带来的不同的树。
然后只剩下了子树外的。
子树外呢,前\(i-1\)个点加完以后,有\(i\)个空位。有一个空位被\(i\)占掉了,那么还剩下\(i-1\)个空位。
因为除了\(i\)这棵子树,以及已经加过的点,还剩下\(n-i-j+1\)个点。
每次加一个点,多一个空位。所以每一多一个空位,直到加完,那么最后一次加点前的空位数为\(i-1+n-i-j+1-1=n-j-1\),注意最后一次\(-1\)是因为最后一次还没有加点。
所以那些点的形态有\(\prod \limits_{k=i-1}^{n-j-1} k\)个。
那么总共,第\(i\)个点的子树大小为\(j\)的贡献就是
\[ j\cdot(n-j)\cdot j! i!\cdot C_{n-i}^{j-1}\cdot \prod_{k=i-1}^{n-j-1}k \]
这个东西再化一化,后面的连乘和前面的\(i!,\ (n-j)\)可以合并到一起,然候再拎出中间重复的。
\[ ij(i-1)\cdot j!\cdot (n-j)!\cdot C_{n-i}^{j-1} \]
然候就可以\(O(n^2)\) 枚举\(i,\ j\),计算出来了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define REP(i,a,n) for(register int i=a;i<=n;++i)
#define dbg(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
const int N=2000+7;
int n,P,C[N][N],fac[N],ans;
inline void SADD(int&x,const int&y){x+=y;x>=P?x-=P:0;}//´íÎó±Ê¼Ç£º-=µÈºÅÍü´òÁË
inline int SMOD(int x){return x>=P?x-P:x;}
inline void Preprocess(){
C[0][0]=fac[0]=1;
REP(i,1,n){
C[i][0]=1;fac[i]=(ll)fac[i-1]*i%P;
REP(j,1,i)C[i][j]=SMOD(C[i-1][j-1]+C[i-1][j]);
}
}
int main(){
scanf("%d%d",&n,&P);
Preprocess();
REP(i,2,n)REP(j,1,n-i+1)SADD(ans,(ll)j*i*(i-1)%P*fac[j]%P*fac[n-j]%P*C[n-i][j-1]%P);
printf("%d\n",ans);
}