生成树计数

本文详细探讨了SPOJ中的PT07D问题,涉及无标号有根树和无标号无根树的计数。通过解析算法,介绍了如何利用递推关系和数学公式高效求解四种不同类型的树的数量。对于无标号无根树,通过重心的概念进行求解,并讨论了偶数节点时的特殊情况。文章提供了O(n^2)和O(n log^2 n)两种复杂度的解决方案。

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

Description

SPOJ PT07D
1:求n个点有标号无根树个数
2:求n个点有标号有根树个数
3:求n个点无标号有根树个数
4:求n个点无标号无根树个数

Solution

我太菜了现在才来做这道题
1和2很好求,略去
设fn为n个点无标号有根树,考虑删去根得到了一些无标号有根树
因为是无标号,考虑一种大小为k的子树,贡献为 1 + x k + x 2 k + x 3 k + . . . = 1 1 − x k 1+x^k+x^{2k}+x^{3k}+...={1\over 1-x^k} 1+xk+x2k+x3k+...=1xk1
且有fk种,所以 F ( x ) = x ∏ k > = 1 1 ( 1 − x k ) f k F(x)=x\prod_{k>=1}{1\over (1-x^k)^{f_k}} F(x)=xk>=1(1xk)fk1
两边取ln ln ⁡ F ( x ) = l n x − ∑ k > = 1 f k ln ⁡ ( 1 − x k ) \ln F(x)=ln x-\sum_{k>=1}f_k\ln (1-x^k) lnF(x)=lnxk>=1fkln(1xk)
两边求导 F ′ ( x ) F ( x ) = 1 x + ∑ k > = 1 k f k x k − 1 1 − x k {F'(x)\over F(x)}={1\over x}+\sum_{k>=1}kf_k{x^{k-1}\over 1-x^k} F(x)F(x)=x1+k>=1kfk1xkxk1
x F ′ ( x ) = F ( x ) + F ( x ) ∑ k > = 1 k f k x k 1 − x k xF'(x)=F(x)+F(x)\sum_{k>=1}kfk{x^k\over 1-x^k} xF(x)=F(x)+F(x)k>=1kfk1xkxk
两边取[x^n], n f n = f n + ∑ i = 1 n − 1 f i [ x n − i ] ∑ k > = 1 k f k x k 1 − x k nfn=fn+\sum_{i=1}^{n-1}fi[x^{n-i}]\sum_{k>=1}kfk{x^k\over 1-x^k} nfn=fn+i=1n1fi[xni]k>=1kfk1xkxk
注意到 x k 1 − x k = x k + x 2 k + x 3 k + . . . . {x^k\over 1-x^k}=x^k+x^{2k}+x^{3k}+.... 1xkxk=xk+x2k+x3k+....,即 ( n − 1 ) f n = ∑ i = 1 n − 1 f i ∑ k ∣ n − i k f k (n-1)fn=\sum_{i=1}^{n-1}fi\sum_{k|n-i}kfk (n1)fn=i=1n1fiknikfk
后面的东西可以提前记一下然后O(n^2)递推
当然也可以用分治NTT做到O(n log^2 n)

无标号无根树
首先先求出有根树fn,设无根树为hn,考虑如何唯一表示一棵树
容易想到重心,那么我们把根不是重心的全部减掉,不是重心就是有一个子树大小>n/2
h n = f n − ∑ i = 1 n / 2 f i f n − i h_n=f_n-\sum_{i=1}^{n/2}f_if_{n-i} hn=fni=1n/2fifni
注意当n为偶数时有两个重心,所以多减了两边子树相同的情况,要加回来

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

const int N=1e3+5;

int n,k,p,f[N],g[N];

int pwr(int x,int y) {
	if (y<0) y+=p-1;
	int z=1;x%=p;
	for(;y;y>>=1,x=(ll)x*x%p)
		if (y&1) z=(ll)z*x%p;
	return z;
}

int solve_f(int n) {
	f[1]=1;fo(j,1,n) g[j]=1;
	fo(i,2,n) {
		f[i]=0;
		fo(j,1,i-1) f[i]=(f[i]+(ll)f[j]*g[i-j]%p)%p;
		f[i]=(ll)f[i]*pwr(i-1,p-2)%p;
		for(int j=i;j<=n;j+=i) g[j]=(g[j]+(ll)i*f[i]%p)%p;
	}
	return f[n];
}

int solve_h(int n) {
	int ans=solve_f(n);
	fo(i,1,n/2) ans=(ans-(ll)f[i]*f[n-i]%p+p)%p;
	if (!(n&1)) {
		ans=(ans+(ll)f[n/2]*f[n/2]%p)%p;
		ans=(ans-(ll)f[n/2]*(f[n/2]-1)%p*pwr(2,p-2)%p+p)%p;
	}
	return ans;
}

int main() {
	while (scanf("%d%d%d",&k,&n,&p)!=EOF) {
		if (k==1) printf("%d\n",pwr(n,n-2));
		if (k==2) printf("%d\n",pwr(n,n-1));
		if (k==3) printf("%d\n",solve_f(n));
		if (k==4) printf("%d\n",solve_h(n));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值