1 条题解
-
0
自动搬运
来自洛谷,原作者为

shadowice1984
これが勘違いでも、これが勘違いでも搬运于
2025-08-24 22:00:43,当前版本为作者最后更新于2018-04-27 07:52:52,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
感谢zhoutb2333dalao提供做法~
本题题解
首先我们会发现按照题目中的方法生成二叉树,生成第1个点的时候有1种选择,第二个点的时候有2种选择,第3个点的时候有3种选择……,所以生成N个点的二叉树一共有种生成方式……
因此我们事实上就是在求所有可能的树的树上点对的距离和
发现太难根本没法算……,因为我们考虑问题的方向错了,我们应该从边的角度来考虑问题……
对于一条边来讲,我们考虑他会被多少点对经过,这就是这条边对树上点对距离的贡献了,换句话讲,枚举点对求树上距离和,和枚举边求经过点对数量和,是等价的,假如说我们考虑点i的父亲边对树上点对距离和的贡献的话,显然只有子树内->子树外的点对才会产生贡献,因此,点i的父亲边对总距离和的贡献为
那么我们现在要统计所有可能生成的二叉树,那么我们还是可以枚举边,计算在所有不同二叉树中的贡献,只是我们此时发现似乎没有办法知道siz
题目只要求了复杂度,我们可以枚举边的同时再枚举一维siz
事实上这样的话我们相当于枚举了每一个子树的所有可能形态,所以这样计数是准的,只不过原来的我们整棵树整棵树考虑的,现在是随便找一颗子树枚举所有可能形态考虑的
好了,现在我们钦定了点i和,那么的贡献就已经可以确定了,然而呢,我们还需要考虑有多少中可能的树形态是保证了点i的子树siz为某一个定值,然后再乘上的贡献就可以算出在这个情况下的答案了
我们此时可以仿照求合法序列数问题的做法来求合法树的个数,在序列问题中有一个非常常见的套路是取任意一个“分割点”然后分别考虑分割点左边和右边的情况,两个乘起来就是我们要求的序列个数
同理我们在树上也可以采取类似的套路,删掉一条边,考虑分开的两个联通块的方案数,两个乘起来就是合法树的个数了
那么我们现在考虑点i的子树中的情况,从树的形态来讲,一共有中不同的形态,从树的点的编号来讲,一共有中不同的编号,因为我们至少需要保证子树中的点编号都要比i大……所以子树内的方案数是
之后我们再来考虑点i的子树外面的情况
首先在生成点i之前一共是有中不同的生成方式,然后我们在生成了点i之后是不可以生成点放在的i子树中的,所以后边的点有中生成方式,化简下就是
然后我们可以给这个函数打个表,当然,现场计算也没问题
所以子树内外的方案相乘就是总的方案数啦~,大概是
总之,我们最后的计算树上点对距离和的式子就是
$\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;//拜拜程序~ }
- 1
信息
- ID
- 3478
- 时间
- 1000ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者