Description
一棵树的 F 值是所有点的度数的平方和,
一个森林的 F 值是森林里所有树的 F 值的和。
求所有 n 个点的森林的 F 值的和 (有标号,树都是无根树)。
Solution
prufer序列:n个点的无根树共有$n^{n-2}$种
令$f(n)$为有n个点的森林的个数
$$f(n)=sum_{i=0}^{n-1} C_{n-1}^i f(n-i-1)(i+1)^{i-1}$$
意思是加入第n个点时,在原来的n-1个点中选择i个点与其连成一棵树
令$A(n)$为n个点的所有无根树中的权值之和
$$A(n)=n sum_{d=1}^{n-1}d^2 C_{n-2}^{d-1} (n-1)^{n-d-1}$$
意思是对于每一个点枚举度数d,统计其在prufer序列中仅出现d-1次的方案数
令$F(n)$记录答案
$$F(n)=sum_{i=0}^{n-1} C_{n-1}^{i} [(i+1)^{i-1}F(n-i-1)+f(n-i-1)A(i+1)]$$
意思是在加入第n个点时,在原来的n-1个点中选择i个点与其形成一棵树,答案为这样的树的总个数乘对应的权值和 + 其余的n-i-1个点形成森林的个数乘方案数
#pragma GCC optimize(2) #include<iostream> #include<cstdio> using namespace std; long long C[5005][5005],T,mod,A[5005],F[5005],f[5005],st[5005],n,qp[5005][5005]; inline long long read() { long long f=1,w=0; char ch=0; while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { w=(w<<1)+(w<<3)+ch-'0'; ch=getchar(); } return f*w; } long long ksm(long long a,long long p) { long long ret=1ll; while(p) { if(p&1) { (ret*=a)%=mod; } (a*=a)%=mod; p>>=1; } return ret; } int main() { T=read(),mod=read(); for(int i=1;i<=5000;i++) { qp[i][0]=1ll; for(int j=1;j<=5000;j++) { qp[i][j]=qp[i][j-1]*i%mod; } } C[0][0]=1ll; for(long long i=1;i<=5000;i++) { C[i][0]=1ll; for(long long j=1;j<=i;j++) { C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } } st[0]=st[1]=1ll; for(int i=2;i<=5000;i++) { st[i]=ksm(i,i-2); } f[0]=f[1]=1ll; for(long long i=2;i<=5000;i++) { for(long long j=0;j<=i-1;j++) { (f[i]+=C[i-1][j]*f[i-1-j]%mod*st[j+1]%mod)%=mod; } } for(long long i=2;i<=5000;i++) { for(long long d=1;d<=i-1;d++) { (A[i]+=d*d%mod*C[i-2][d-1]%mod*qp[i-1][i-2-d+1]%mod)%=mod; } (A[i]*=i)%=mod; } for(long long i=2;i<=5000;i++) { for(long long j=0;j<=i-1;j++) { (F[i]+=C[i-1][j]*(st[j+1]*F[i-1-j]%mod+f[i-j-1]*A[j+1]%mod)%mod)%=mod; } } for(;T;T--) { n=read(); printf("%lld ",F[n]); } return 0; }