题解 洛谷 P4492 【[HAOI2018]苹果树】

考虑生成一颗二叉树的过程,加入第一个节点方案数为(1),加入第二个节点方案数为(2),加入第三个节点方案数为(3),发现生成一颗(n)个节点的二叉树的方案数为(n!)

所以题目中所求即为点与点之间的距离之和,考虑每一条边的贡献,即(sumlimits_esize_x imes size_y)(x)(y)为这条边的两个端点。

可以枚举每一个节点(i),再枚举节点(i)子树大小(j),其和父亲的连边对答案的贡献为(j(n-j)),然后贡献再乘上这个状态对应的方案数,就是所求的答案,因此问题转化为了求节点(i)子树大小为(j)的方案数。

可以把计算方案数看作三部分,安排点(i)子树中点的方案数,安排比(i)编号小的点的方案数,安排比(i+j-1)编号大的点的方案数。

考虑点(i)子树为一颗大小为(j)的二叉树,生成这个子树的方案数为(j!)。其中的点是有编号的,并且编号都比(i)大,所以安排子树中的点方案数还要考虑选出哪些点,总方案数即为(j!inom{n-i}{j-1})

比点(i)编号小的点的安排方案数即为生成一颗(i)个点的二叉树的方案数,即(i!)

比点(i)编号大的点在安排时都不能放到(i)的子树中。考虑安排好(i)子树内所有点后,即安排好前(i+j-1)个点后,再新加入一个点的方案数为(i-1),加入第二点的方案数为(i),加入第二点的方案数为(i+1),一直加到最后一个点,加入最后一个点的方案数为(n-j-1),总方案数即为(frac{(n-j-1)!}{(i-2)!})

把边的贡献和上面得出的方案数乘起来,即为答案,得:

[sum_{i=1}^n sum_{j=1}^{n-i+1} j(n-j)j!inom{n-i}{j-1}i!frac{(n-j-1)!}{(i-2)!} ]

考虑模数可能没有逆元,所以将式子进一步化简,去掉除法,得:

[sum_{i=1}^n sum_{j=1}^{n-i+1} j(n-j)j!inom{n-i}{j-1}(n-j-1)!i(i-1) ]

然后预处理阶乘和组合数,(n^2)计算即可。

(code:)

#include<bits/stdc++.h>
#define maxn 2010
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
ll n,p,ans;
ll f[maxn],C[maxn][maxn];
void init()
{
    f[0]=1;
    for(int i=1;i<=n;++i) f[i]=f[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()
{
    read(n),read(p),init();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n-i+1;++j)
            ans=(ans+j*(n-j)%p*f[j]%p*C[n-i][j-1]%p*f[n-j-1]%p*i%p*(i-1)%p)%p;
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/lhm-/p/13037002.html