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

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

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

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

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

考虑点\(i\)子树为一颗大小为\(j\)的二叉树,生成这个子树的方案数为\(j!\)。其中的点是有编号的,并且编号都比\(i\)大,所以安排子树中的点方案数还要考虑选出哪些点,总方案数即为\(j!\binom{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!\binom{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!\binom{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;
}
posted @ 2020-06-03 13:30  lhm_liu  阅读(199)  评论(0编辑  收藏  举报