[HAOI2018]苹果树

题目

考虑题目生成二叉树的方式,发现能产生(n!)

也就是(i)个节点组成的二叉树有(n!)

我们再来考虑如何计算树上两两点对之间的距离

显然需要考虑每一条边的贡献,如果一条边连接的两个点种深度较大的点为(i),那么这条边的贡献就是((n-sz_i)sz_i)

我们考虑用这种方式来计算答案

我们枚举一个点(i),作为一条边连接的两个点中深度较小的那一个,由于允许(O(n^2))复杂度,我们枚举(i)的一棵子树的大小(j),也就是当前计算贡献的这一条边连接的两个点种深度较大的点的子树大小

贡献显然是(n(n-j)),考虑到这是一棵有标号的树,我们需要找到(j)个点来构成这棵树,考虑到生成二叉树的方式,一个点子树内的编号只可能比这个子树的根的编号更大,因此构成这个大小为(j)的子树的编号必须大于(i),有(n-i)中,我们从里面选择(j)个方案数是(C_{n-i}^j)(j)个点构成的树有(j!)种,于是又要乘上(j!),同时这棵树可能是左子树也可能是右子树,于是还要乘上(2)

我们再来考虑剩余的(n-j-i)个点的去向,由于这些点不能落在大小为(j)的那棵子树里,所以第一个点一共有(i)种可能的去向,之后是(i+1),之后是(i+2...)到最后一个点事(i+n-j-i-1=n-j-1),这里的一共有(prod_{k=i}^{n-j-1}k)种情况

于是我们的答案就是

[sum_{i=1}^ni!sum_{j=1}^{n-i}2 imes C_{n-i}^j imes j! imes (n-j)jprod_{k=i}^{n-j-1}k ]

组合数我们直接(O(n^2))递推,后面的那个连乘我么可以预处理(f[i][j]=prod_{k=i}^jk)

于是我们(O(n^2))就做完啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=2e3+5;
int f[maxn][maxn],c[maxn][maxn],fac[maxn];
int n,mod;
inline int calc(int i,int j) {if(i>j) return 1;return f[i][j];}
int main() {
	n=read(),mod=read();
	for(re int i=0;i<=n;i++) c[i][0]=c[i][i]=1;
	for(re int i=2;i<=n;i++)
		for(re int j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	fac[0]=1;
	for(re int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
	for(re int i=1;i<=n;i++) {
		f[i][i]=i;
		for(re int j=i+1;j<=n;j++) f[i][j]=1ll*f[i][j-1]*j%mod;
	}
	int ans=0;
	for(re int i=1;i<=n;i++) {
		int now=0;
		for(re int j=1;j<=n-i;j++)
			now=(now+1ll*c[n-i][j]*fac[j]%mod*(n-j)%mod*j%mod*calc(i,n-j-1)%mod)%mod;
		now=(now+now)%mod;
		ans=(ans+1ll*fac[i]*now%mod)%mod;
	}
	printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/asuldb/p/10774643.html