带限制的无标号树计数

前言

我又来学数树了
最近化学老师教我们手把手写烷烃同分异构体
然后我又想到了这个远古老题

Dp

首先我们计有根树,假设度数限制为m
F i , j F_i{,_j} Fi,j表示i个点,根节点子树大小为j的树的个数
A i = ∑ j = 0 m − 1 F i , j Ai=\sum_{j=0}^{m-1}F_{i,j} Ai=j=0m1Fi,j
考虑去掉根节点会得到j个子树,按子树大小分组,设第i组的大小为si,有ki个
我们会有 F i , j = ∑ ∑ s t = j , ∑ s t k t = i ∏ ( k t + A s t − 1 k t ) F_{i,j}=\sum_{\sum s_t=j,{\sum s_tk_t=i}}\prod{{k_t+A_{s_t}-1}\choose {k_t}} Fi,j=st=j,stkt=i(ktkt+Ast1)
后面那个组合数的意思大概是挡板,就是将k种子树分成As组,每组对应一种方案
但是这个东西要怎么写呢?
考虑枚举最大的子树,用类似背包的方法来转移
因为是从小到大枚举最大的子树所以不会记重
复杂度大概是O(n^2m log m)的

Code

loj6269烷基计数

#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=5e3+5,Mo=1e9+7;

int pwr(int x,int y) {
	int z=1;
	for(;y;y>>=1,x=(ll)x*x%Mo)
		if (y&1) z=(ll)z*x%Mo;
	return z;
}

int n,f[N][5],inv[N],fac[N];

int main() {
	scanf("%d",&n);
	f[1][0]=1;
	fac[0]=1;fo(i,1,n) fac[i]=(ll)fac[i-1]*i%Mo;
	inv[n]=pwr(fac[n],Mo-2);fd(i,n-1,0) inv[i]=(ll)inv[i+1]*(i+1)%Mo;
	fo(mx,1,n-1) {
		int s=0;
		fo(j,0,3) (s+=f[mx][j])%=Mo;
		fd(i,n,mx+1) {
			fo(j,1,4) {
				int C=s;
				for(int k=1;k<=j&&mx*k<i;k++) {
					(f[i][j]+=(ll)f[i-mx*k][j-k]*C%Mo*inv[k]%Mo)%=Mo;
					C=(ll)C*(s+k)%Mo;
				}
			}
		}
	}
	int ans=0;
	fo(i,0,3) (ans+=f[n][i])%=Mo;
	printf("%d\n",ans);
	return 0;
}

生成函数

这个当m大的时候不太好处理,需要用拆分数的时间枚举划分
举个栗子,当m=4,即烷烃计数
考虑设 A ( x ) A(x) A(x)表示这个东西的生成函数
把同构看成是儿子之间的置换,根据burnside引理我们有 A ( x ) = 1 + x A ( x ) 3 + 3 A ( x 2 ) A ( x ) + 2 A ( x 3 ) 6 A(x)=1+x{A(x)^3+3A(x^2)A(x)+2A(x^3)\over 6} A(x)=1+x6A(x)3+3A(x2)A(x)+2A(x3)
这个东西可以直接N^2Dp了
也可以用分治FFT在O(n log^2 n)的时间内解决

多组询问的话有空再填
非常抱歉,这个代码咕咕咕了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值