[HAOI2018]苹果树

本文介绍了一种计算所有不同二叉树中两两节点间距离总和的方法。通过将问题转化为求每条边对答案的贡献,利用组合数学技巧简化计算过程,并给出了一段C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

  • ……先自闭两个小时
  • 好吧,不会写,看了看题解,说一下思路吧。
  • 首先第一个小 t r i c k trick trick是,按照题意去建树, n n n个点能组成的二叉树的数量正好是 n ! n! n。因此求期望的时候我们不需要乘概率了,直接累加答案就好。
  • 再来描述一下问题:求所有不同的树上两两节点之间的距离之和。
  • 这个问题这样描述,真的很难求,考虑转化思路,求每一条边对答案的贡献。
  • 除了根节点每一个点都有一条连向父亲的边,我们就依次求这个边的贡献。先枚举点的编号,再枚举子树的大小。设当前点为 i i i,子树大小为 s i z siz siz,对一个方案的贡献就是 s i z ∗ ( n − s i z ) siz*(n-siz) siz(nsiz)。但是我们还需要乘上方案数。考虑当前点为 i i i,子树大小为 s i z siz siz的方案数如何求:首先从编号1到编号 i i i取的这 i i i个点有 i ! i! i种方案。然后从剩下的点里面选出了 s i z − 1 siz-1 siz1个点放在了 i i i这颗子树里,需要乘上一个组合数。这 s i z − 1 siz-1 siz1个点相当于以i为根任意选位置添加,方案数为 s i z ! siz! siz。剩下的 n − i − s i z + 1 n-i-siz+1 nisiz+1个节点,第一个点有 i − 1 i-1 i1种选择,第二个有 i − 1 + 1 i-1+1 i1+1种,第三个……最后一个有 i − 1 + n − i − s i z i-1+n-i-siz i1+nisiz n − s i z − 1 n-siz-1 nsiz1种选择。这些都乘起来,得到:
    a n s = ∑ i = 2 n i ! ∑ s i z = 1 n − i + 1 C n − i s i z − 1 s i z ! ( n − s i z ) s i z ( n − s i z − 1 ) ! ( i − 2 ) ! ans=\sum_{i=2}^ni!\sum_{siz=1}^{n-i+1}C_{n-i}^{siz-1}siz!(n-siz)siz\frac{(n-siz-1)!}{(i-2)!} ans=i=2ni!siz=1ni+1Cnisiz1siz!(nsiz)siz(i2)!(nsiz1)!
  • 模数 p p p不确定,显然除法不合适,我们需要设法把除法变成乘法。阶乘除以阶乘,考虑和组合数之间的转化。
    ( n − s i z − 1 ) ! ( i − 2 ) ! = C n − s i z − 1 i − 2 ∗ ( n − s i z − i + 1 ) ! \frac{(n-siz-1)!}{(i-2)!}=C_{n-siz-1}^{i-2}*(n-siz-i+1)! (i2)!(nsiz1)!=Cnsiz1i2(nsizi+1)!
    a n s = ∑ i = 2 n i ! ∑ s i z = 1 n − i + 1 C n − i s i z − 1 s i z ! ( n − s i z ) s i z C n − s i z − 1 i − 2 ∗ ( n − s i z − i + 1 ) ! ans=\sum_{i=2}^ni!\sum_{siz=1}^{n-i+1}C_{n-i}^{siz-1}siz!(n-siz)sizC_{n-siz-1}^{i-2}*(n-siz-i+1)! ans=i=2ni!siz=1ni+1Cnisiz1siz!(nsiz)sizCnsiz1i2(nsizi+1)!
  • O ( n 2 ) O(n^2) O(n2)预处理组合数,直接递推即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2010;
int n,p;ll ans,jie[N],c[N][N];
int main(){
	scanf("%d%d",&n,&p);
	jie[0]=jie[1]=1;
	for(int i=2;i<=n;++i) jie[i]=1LL*jie[i-1]*i%p;
	c[0][0]=1;
	for(int i=1;i<=n;++i){
		c[i][0]=1;
		for(int j=1;j<=i;++j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
	}
	for(int i=2;i<=n;++i){
		for(int siz=1;siz<=n-i+1;siz++){
			ans=(ans+1LL*jie[i]*c[n-i][siz-1]%p*jie[siz]%p*siz%p*(n-siz)%p*c[n-siz-1][i-2]%p*jie[n-siz-i+1]%p)%p;
		}
	}
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值