题目分析:
思路不难想,考虑三个dp状态$f,g,d$。
$g[i]$表示有$i$个点的堆的数量
$d[i]$表示有$i$个点的情况下所有的方案数中点到根的距离和
$f[i]$表示要求的答案。
不难发现$g[i]=i!$,然后$d[i]$就枚举左子树大小,然后把左右子树单独的$d[j]$加起来,最后对于每种方案都加上$i-1$,也就是$d[i] = g[i]*(i-1)+sum_{j=0}^{i-1}inom{i-1}{j}*(d[j]*g[i-j-1]+d[i-j-1]*g[j])$。
然后考虑$f[i]$,也是考虑左子树大小然后递归处理$f[j]$,然后再通过$d[i]$处理出到根的距离和,最后再通过$d[i]$之间的乘法求出跨越两个子树的情况,具体看我代码。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 2050; 5 6 int n,p; 7 int g[maxn],f[maxn],d[maxn]; 8 int C[maxn][maxn]; 9 10 void work(){ 11 g[0] = 1; d[0] = d[1] = 0; 12 for(int i=1;i<=n;i++) g[i] = 1ll*g[i-1]*i%p; 13 for(int i=2;i<=n;i++){ 14 for(int j=0;j<i;j++){ 15 int sub=1ll*C[i-1][j]*((1ll*d[j]*g[i-j-1]+1ll*d[i-1-j]*g[j])%p)%p; 16 d[i] += sub; d[i] %= p; 17 } 18 d[i] += 1ll*g[i]*(i-1)%p; d[i] %= p; 19 } 20 f[0] = 0; f[1] = 0; 21 for(int i=2;i<=n;i++){ 22 for(int j=0;j<i;j++){ 23 int sub=1ll*C[i-1][j]*(1ll*f[j]*g[i-j-1]%p+1ll*f[i-j-1]*g[j]%p)%p; 24 int lt=1ll*C[i-1][j]*((d[j]+1ll*g[j]*j)%p)%p*g[i-j-1]%p; 25 int rt=1ll*C[i-1][j]*((d[i-j-1]+1ll*g[i-j-1]*(i-j-1))%p)%p*g[j]%p; 26 int crs=1ll*lt*(i-1-j)%p,cts=1ll*rt*j%p; 27 f[i] += (1ll*sub+lt+rt+crs+cts)%p; 28 f[i] %= p; 29 } 30 } 31 printf("%d ",f[n]); 32 } 33 34 int main(){ 35 scanf("%d%d",&n,&p); 36 if(p == 1){puts("0");return 0;} 37 for(int i=1;i<=n;i++){ 38 C[i][0] = C[i][i] = 1; 39 for(int j=1;j<i;j++){ 40 C[i][j] = (C[i-1][j] + C[i-1][j-1])%p; 41 } 42 } 43 work(); 44 return 0; 45 }