题解 P4492 【[HAOI2018]苹果树】
shadowice1984 · · 题解
感谢zhoutb2333dalao提供做法~
本题题解
首先我们会发现按照题目中的方法生成二叉树,生成第1个点的时候有1种选择,第二个点的时候有2种选择,第3个点的时候有3种选择……,所以生成N个点的二叉树一共有
因此我们事实上就是在求所有可能的树的树上点对的距离和
发现太难根本没法算……,因为我们考虑问题的方向错了,我们应该从边的角度来考虑问题……
对于一条边来讲,我们考虑他会被多少点对经过,这就是这条边对树上点对距离的贡献了,换句话讲,枚举点对求树上距离和,和枚举边求经过点对数量和,是等价的,假如说我们考虑点i的父亲边对树上点对距离和的贡献的话,显然只有子树内->子树外的点对才会产生贡献,因此,点i的父亲边对总距离和的贡献为
size_{i}(n-size_{i})
那么我们现在要统计所有可能生成的二叉树,那么我们还是可以枚举边,计算在所有不同二叉树中的贡献,只是我们此时发现似乎没有办法知道siz
题目只要求了
事实上这样的话我们相当于枚举了每一个子树的所有可能形态,所以这样计数是准的,只不过原来的我们整棵树整棵树考虑的,现在是随便找一颗子树枚举所有可能形态考虑的
好了,现在我们钦定了点i和
我们此时可以仿照求合法序列数问题的做法来求合法树的个数,在序列问题中有一个非常常见的套路是取任意一个“分割点”然后分别考虑分割点左边和右边的情况,两个乘起来就是我们要求的序列个数
同理我们在树上也可以采取类似的套路,删掉一条边,考虑分开的两个联通块的方案数,两个乘起来就是合法树的个数了
那么我们现在考虑点i的子树中的情况,从树的形态来讲,一共有
siz_{i}!C_{n-i}^{siz_{i}-1}
之后我们再来考虑点i的子树外面的情况
首先在生成点i之前一共是有
i(i-1)(n-size_{i}-1)!
然后我们可以给这个函数打个表,当然,现场计算也没问题
所以子树内外的方案相乘就是总的方案数啦~,大概是
siz_{i}!C_{n-i}^{siz_{i}-1}i(i-1)(n-size_{i}-1)!
总之,我们最后的计算树上点对距离和的式子就是
\sum_{i=2}^{n}\sum_{siz=1}^{n-i+1}siz!C_{n-i}^{siz-1}siz(n-siz)(n-siz+1)!i(i-1)
然后我们就可以愉快的计算啦~
上代码~
#include<cstdio>
#include<algorithm>
using namespace std;const int N=2010;typedef long long ll;
ll mod;int n;ll dp[N][N];ll fac[N];ll c[N][N];ll res;
int main()
{
scanf("%d%lld",&n,&mod);fac[0]=1;
for(int i=0;i<=n;i++)c[i][i]=c[0][i]=1;//组合数
for(int i=0;i<=n;i++)for(int j=1;j<n;j++)c[j][i]=(c[j-1][i-1]+c[j][i-1])%mod;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;dp[1][1]=1;//阶乘
for(int i=2;i<=n;i++)for(int j=1;j<=i;j++)dp[i][j]=fac[i-2]*j%mod*(j-1)%mod;//打表下子树外部的方案数
for(int i=2;i<=n;i++)//n^2枚举计算
for(int j=1;j<=n-i+1;j++)
(res+=fac[j]*c[j-1][n-i]%mod*j*(n-j)%mod*dp[n-j+1][i])%=mod;
printf("%lld",res);return 0;//拜拜程序~
}