【Learning】无标号生成树计数

无标号有根树

f n f_n fn n n n个节点的无标号有根树数量。
把生成函数写出来,设 F ( x ) = ∑ i = 1 ∞ f i x i F(x)=\sum_{i=1}^{\infty}f_ix^i F(x)=i=1fixi
考虑钦点一个点作为根,则需要在下面接若干棵节点数之和为 n − 1 n-1 n1的子树。
f n = [ x n ] F ( x ) = [ x n − 1 ] ∏ i = 1 ∞ ( ∑ j = 0 ∞ x i j ) f i = [ x n − 1 ] ∏ i = 1 ∞ ( 1 − x i ) − f i = [ x n ] x ∏ i = 1 ∞ ( 1 − x i ) − f i \begin{aligned} f_n&=[x^{n}]F(x)\\ &=[x^{n-1}]\prod_{i=1}^{\infty}(\sum_{j=0}^{\infty}x^{ij})^{f_i}\\ &=[x^{n-1}]\prod_{i=1}^{\infty}(1-x^i)^{-f_i}\\ &=[x^{n}]x\prod_{i=1}^{\infty}(1-x^i)^{-f_i} \end{aligned} fn=[xn]F(x)=[xn1]i=1(j=0xij)fi=[xn1]i=1(1xi)fi=[xn]xi=1(1xi)fi
上式中等比数列方案数次方的意义就是在每种大小为 i i i的不同形态的树中选子树。
F ( x ) = x ∏ i = 1 ∞ ( 1 − x i ) − f i \begin{aligned} F(x)=x\prod_{i=1}^{\infty}(1-x^i)^{-f_i} \end{aligned} F(x)=xi=1(1xi)fi
两边同时取 ln ⁡ \ln ln
ln ⁡ F ( x ) = ln ⁡ x − ∑ i = 1 ∞ f i ln ⁡ ( 1 − x i ) \begin{aligned} \ln F(x)=\ln x-\sum_{i=1}^{\infty}f_i\ln(1-x^i) \end{aligned} lnF(x)=lnxi=1filn(1xi)
两边同时求导
F ′ ( x ) F ( x ) = 1 x + ∑ i = 1 ∞ f i i x i − 1 1 − x i \begin{aligned} \frac{F'(x)}{F(x)}=\frac{1}{x}+\sum_{i=1}^{\infty}f_i\frac{ix^{i-1}}{1-x^i}\\ \end{aligned} F(x)F(x)=x1+i=1fi1xiixi1
化简得到
x F ′ ( x ) = F ( x ) + F ( x ) ∑ i = 1 ∞ i ⋅ f i x i 1 − x i \begin{aligned} xF'(x)=F(x)+F(x)\sum_{i=1}^{\infty}i \cdot f_i\frac{x^{i}}{1-x^i}\\ \end{aligned} xF(x)=F(x)+F(x)i=1ifi1xixi
单独考虑 x n x^n xn这一项的系数
n f n = f n + ∑ i = 1 n − 1 f i ∑ j = 1 ∞ j ⋅ f j ⋅ [ x n − i ] x j 1 − x j = f n + ∑ i = 1 n − 1 f i ∑ j = 1 ∞ j ⋅ f j ⋅ [ x n − i ] ∑ k = 1 ∞ x j k = f n + ∑ i = 1 n − 1 f i ∑ j = 1 ∞ j ⋅ f j [ j ∣ n − i ] = f n + ∑ i = 1 n − 1 f i ∑ j ∣ n − i j ⋅ f j \begin{aligned} nf_n&=f_n+\sum_{i=1}^{n-1}f_i\sum_{j=1}^{\infty}j\cdot f_j\cdot[x^{n-i}]\frac{x^{j}}{1-x^j}\\ &=f_n+\sum_{i=1}^{n-1}f_i\sum_{j=1}^{\infty}j\cdot f_j\cdot[x^{n-i}]\sum_{k=1}^{\infty}x^{jk}\\ &=f_n+\sum_{i=1}^{n-1}f_i\sum_{j=1}^{\infty}j\cdot f_j[j|n-i]\\ &=f_n+\sum_{i=1}^{n-1}f_i\sum_{j|n-i}j\cdot f_j \end{aligned} nfn=fn+i=1n1fij=1jfj[xni]1xjxj=fn+i=1n1fij=1jfj[xni]k=1xjk=fn+i=1n1fij=1jfj[jni]=fn+i=1n1fijnijfj
化简得
f n = ∑ i = 1 n − 1 f i ∑ j ∣ n − i j ⋅ f j n − 1 f_n=\frac{\sum_{i=1}^{n-1}f_i\sum_{j|n-i}j\cdot f_j}{n-1} fn=n1i=1n1fijnijfj
可以 n 2 n^2 n2递推,也可以 n log ⁡ 2 2 n n\log_2^2n nlog22n分治 F F T FFT FFT
分治的时候把贡献拆成两部分算,而且取的是多项式后一半,前一半循环卷积了也不用管。

无标号无根树

g n g_n gn n n n个节点的无标号无根树数量。
只考虑以重心为根的有根树,进行容斥。
n n n为奇数时,
g n = f n − ∑ i = 1 ⌊ n 2 ⌋ f i f n − i g_n=f_n-\sum_{i=1}^{\lfloor\frac{n}{2}\rfloor}f_if_{n-i} gn=fni=12nfifni
n n n为偶数,则一棵树有两个重心,同一棵树有两种方法拼出来,需要特殊处理一下。
n n n为偶数时,
g n = f n − ( ∑ i = 1 n 2 − 1 f i f n − i ) − f n 2 ( f n 2 − 1 ) 2 = f n − ( ∑ i = 1 n 2 f i f n − i ) + f n 2 ( f n 2 + 1 ) 2 \begin{aligned} g_n&=f_n-(\sum_{i=1}^{\frac{n}{2}-1}f_if_{n-i})-\frac{f_{\frac{n}{2}}(f_{\frac{n}{2}}-1)}{2}\\ &=f_n-(\sum_{i=1}^{\frac{n}{2}}f_if_{n-i})+\frac{f_{\frac{n}{2}}(f_{\frac{n}{2}}+1)}{2} \end{aligned} gn=fn(i=12n1fifni)2f2n(f2n1)=fn(i=12nfifni)+2f2n(f2n+1)

简单代码

for(int i=1;i<=n;i++){	
	if(i==1){
		f[i]=1;
	}else{
		for(int j=1;j<i;j++){
			f[i]=(f[i]+1LL*f[j]*g[i-j])%mod;
		}
		f[i]=1LL*f[i]*fastpow(i-1,mod-2)%mod;
	}
	int tmp=1LL*i*f[i]%mod;
	for(int j=i;j<=n;j+=i){
		g[j]=(g[j]+tmp)%mod;
	}
}

待填坑:时间复杂度为 n l o g 2 n nlog_2n nlog2n的多项式 ln ⁡ , exp ⁡ \ln,\exp ln,exp牛顿迭代做法。

n l o g 2 2 n nlog_2^2n nlog22n分治做法

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=530005,mod=998244353;
int n,len,res,rev[N],f[N],g[N],a[N],b[N];
inline int add(int a,int b){
	a+=b;
	return a<mod?a:a-mod;
}
inline int dec(int a,int b){
	a-=b;
	return a<0?a+mod:a;
}
inline int mul(int a,int b){
	return 1LL*a*b%mod;
}
int fastpow(int a,int x){
	int res=1;
	while(x){
		if(x&1){
			res=mul(res,a);
		}
		x>>=1;
		a=mul(a,a);
	}
	return res;
}
void ntt(int a[],int len,int dft){
	for(int i=0;i<len;i++){
		rev[i]=(rev[i>>1]>>1)|((i&1)*(len>>1));
		if(i<rev[i]){
			swap(a[i],a[rev[i]]);
		}
	}
	for(int i=1;i<len;i<<=1){
		int wn=fastpow(3,(mod-1)/i/2);
		if(dft==-1){
			wn=fastpow(wn,mod-2);
		}
		for(int j=0;j<len;j+=(i<<1)){
			int w=1,x,y;
			for(int k=j;k<j+i;k++,w=mul(w,wn)){
				x=a[k];
				y=mul(w,a[k+i]);
				a[k]=add(x,y);
				a[k+i]=dec(x,y);
			}
		}
	}
	if(dft==-1){
		int inv=fastpow(len,mod-2);
		for(int i=0;i<len;i++){
			a[i]=mul(a[i],inv);
		}
	}
}
void solve(int l,int r,int len){
	if(l==r){
		f[l]+=(l==1);
		if(l>1){
			f[l]=mul(f[l],fastpow(l-1,mod-2));
		}
		int t=mul(f[l],l);
		for(int i=l;i<=n&&l;i+=l){
			g[i]=add(g[i],t);
		}
		return;
	}
	solve(l,l+(len>>1)-1,len>>1);
	for(int i=0;i<len;i++){
		a[i]=(i<(len>>1))?f[l+i]:0;
		b[i]=(i<l+(len>>1))?g[i]:0;
	}
	ntt(a,len,1);
	ntt(b,len,1);
	for(int i=0;i<len;i++){
		a[i]=mul(a[i],b[i]);
	}
	ntt(a,len,-1);
	for(int i=(len>>1);i<len;i++){
		f[l+i]=add(f[l+i],a[i]);
	}
	for(int i=0;i<len;i++){
		a[i]=(i<(len>>1))?g[l+i]:0;
		b[i]=(i<l)?f[i]:0;
	}
	ntt(a,len,1);
	ntt(b,len,1);
	for(int i=0;i<len;i++){
		a[i]=mul(a[i],b[i]);
	}
	ntt(a,len,-1);
	for(int i=(len>>1);i<len;i++){
		f[l+i]=add(f[l+i],a[i]);
	}
	solve(l+(len>>1),r,len>>1);
}
int main(){
	scanf("%d",&n);
	for(len=1;len<=n;len<<=1);
	solve(0,len-1,len);
	res=f[n];
	for(int i=1;(i<<1)<=n;i++){
		res=dec(res,mul(f[i],f[n-i]));
	}
	if(~n&1){
		res=add(res,1LL*f[n>>1]*(f[n>>1]+1)/2%mod);
	}
	printf("%d\n",res);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值