「学习笔记」无标号生成树计数总结

感觉关于树/图计数是一门博大精深的学问,不知道这辈子有没有搞到足够明白的机会了啊QwQ

一、无标号有根树计数:
基本上是这篇的详细版本。

考虑令 f n f_n fn表示n个点的无标号有根树数量,其生成函数为 F ( x ) = ∑ i > 0 f i x i F(x)=\sum_{i>0}f_ix^i F(x)=i>0fixi;考虑如何计数 f n + 1 f_{n+1} fn+1,可以这么认为:等价于找n个点的森林,每颗树都是一个子问题;然后新增一个点当这些树的根。

一个直接的想法是:是不是可以枚举树的个数,然后的到: F ( x ) = x ∑ i > 0 F i ( x ) F(x)=x\sum_{i>0}F^i(x) F(x)=xi>0Fi(x)?然而这显然是错的,因为这样得到的答案中有重复计数,即如果我有两个完全相同的儿子,那么其二者的顺序应当是不计的;

因此考虑大小为k的树的选择情况,然后可能又会认为大小为k的树对 f n + 1 f_{n+1} fn+1那一项的贡献是: ∑ j ≥ 0 i f k x k i \sum_{j\geq0}i^{f_{k}}x^{ki} j0ifkxki,但这还是错的,理由同上。

正确的姿势是,贡献应当是: ( ∑ i ≥ 0 z i k ) f k \left(\sum_{i\geq0}z^{ik}\right)^{f_k} (i0zik)fk,也就是 f k f_k fk个相同的等比数列的乘积。一一对应的关系:假设第 j j j个等比数列选择的那一项是 i j k i_jk ijk,那么最终对应的一种方案是,假如我们给这 f k f_k fk个大小为k的无标号有根树编号为1到 f k f_k fk,那么在考虑对 f n + 1 f_{n+1} fn+1的贡献中,第 j j j种选了 i j i_j ij个。

最后我们把所有k的情况乘起来(下面即使 k ≥ n + 1 k\geq n+1 kn+1也不要紧):
[ x n + 1 ] F ( x ) = [ x n + 1 ] x ∏ k > 0 ( ∑ i ≥ 0 x i k ) f k = [ x n + 1 ] ∏ k > 0 ( 1 1 − x k ) f k = [ x n + 1 ] ∏ k > 0 ( 1 − x k ) − f k [x^{n+1}]F(x)=[x^{n+1}]x\prod_{k>0}\left(\sum_{i\geq0}x^{ik}\right)^{f_k}\\=[x_{n+1}]\prod_{k>0}\left(\frac{1}{1-x^k}\right)^{f_k}=[x_{n+1}]\prod_{k>0}\left(1-x^k\right)^{-f_k} [xn+1]F(x)=[xn+1]xk>0(i0xik)fk=[xn+1]k>0(1xk1)fk=[xn+1]k>0(1xk)fk
把那个 [ x n + 1 ] [x_{n+1}] [xn+1]去掉:
F ( x ) = x ∏ k > 0 ( 1 − x k ) − f k F(x)=x\prod_{k>0}\left(1-x^k\right)^{-f_k} F(x)=xk>0(1xk)fk
显然两边取对数:
ln ⁡ F ( x ) = ln ⁡ ( x ∏ k > 0 ( 1 − x k ) − f k ) = ln ⁡ x + ∑ k > 0 ln ⁡ ( ( 1 − x k ) − f k ) = ln ⁡ x − ∑ k > 0 f k ln ⁡ ( 1 − x k ) \ln F(x)=\ln\left(x\prod_{k>0}\left(1-x^k\right)^{-f_k}\right)\\=\ln x+\sum_{k>0}\ln \left(\left(1-x^k\right)^{-f_k}\right)\\=\ln x-\sum_{k>0}f_k\ln\left(1-x^k\right) lnF(x)=ln(xk>0(1xk)fk)=lnx+k>0ln((1xk)fk)=lnxk>0fkln(1xk)
然后两边同时求导:
F ′ ( x ) F ( x ) = 1 x + ∑ k > 0 f k k x k − 1 1 − x k \frac{F'(x)}{F(x)}=\frac 1x+\sum_{k>0}f_k\frac{kx^{k-1}}{1-x^k} F(x)F(x)=x1+k>0fk1xkkxk1
化简这个式子,可以得到:
x F ′ ( x ) = F ( x ) + F ( x ) ∑ k > 0 f k k x k 1 − x k xF'(x)=F(x)+F(x)\sum_{k>0}f_kk\frac{x^k}{1-x^k} xF(x)=F(x)+F(x)k>0fkk1xkxk
比较其第n项系数,可知(后面最后一项视为 F ( x ) F(x) F(x)和若干多项式的乘积的和):
n f n = f n + ∑ i > 0 f i ∑ k > 0 f k k ( [ x n − i ] x k 1 − x k ) nf_n=f_n+\sum_{i>0}f_i\sum_{k>0}f_kk\left([x^{n-i}]\frac{x^k}{1-x^k}\right) nfn=fn+i>0fik>0fkk([xni]1xkxk)
我们知道: x k 1 − x k = ∑ i = 1 n − 1 x i k = ∑ i > 0 [ k ∣ i ] x i \frac{x^k}{1-x^k}=\sum_{i=1}^{n-1}x^{ik}=\sum_{i>0}[k|i]x^i 1xkxk=i=1n1xik=i>0[ki]xi
因此:
n f n = f n + ∑ i = 1 n − 1 f i ∑ k > 0 f k k [ k ∣ n − i ] = f n + ∑ i = 1 n − 1 f i ∑ k ∣ n − i f k k nf_n=f_n+\sum_{i=1}^{n-1}f_i\sum_{k>0}f_kk[k|n-i]=f_n+\sum_{i=1}^{n-1}f_i\sum_{k|n-i}f_kk nfn=fn+i=1n1fik>0fkk[kni]=fn+i=1n1fiknifkk
最后
f n = ∑ i = 1 n − 1 f i ∑ k ∣ n − i f k k n − 1 f_n=\frac{\sum_{i=1}^{n-1}f_i\sum_{k|n-i}f_kk}{n-1} fn=n1i=1n1fiknifkk
后面那一项显然可以边算边处理,这样直接算是 O ( n 2 ) O\left(n^2\right) O(n2)的,可以分治NTT做到 O ( n lg ⁡ 2 n ) O\left(n\lg^2n\right) O(nlg2n)

二、无标号无根树计数
其实做完上面的部分这一部分也就基本完成了:令 h n h_n hn表示n个点的无标号无根树,我们用有根树的情况减去当根不为重心的情况:
1)当n为奇数,此时重心只有一个,如果根不是重心,那么其一定有恰好一颗子树,其大小超过 ⌊ n 2 ⌋ \left\lfloor\frac{n}{2}\right\rfloor 2n,枚举这个子树的大小并且扣去:
h n = f n − ∑ k = ⌊ n 2 ⌋ + 1 n − 1 f k f n − k h_n=f_n-\sum_{k=\left\lfloor\frac{n}{2}\right\rfloor+1}^{n-1}f_kf_{n-k} hn=fnk=2n+1n1fkfnk
2)和奇数稍有不同的是,此时有可能有两个重心(只是有可能)。如果根不是重心,还是像奇数一样减去那颗大小最大的,然后如果重心是在边e的两端,那么要从总数减去断开e后两颗树不一样(此时在 f n f_n fn中会被计数两次)的情况:
h n = f n − ( ∑ k = n 2 + 1 n − 1 f k f n − k ) − ( f n 2 2 ) h_n=f_n-\left(\sum_{k=\frac{n}{2}+1}^{n-1}f_kf_{n-k}\right)-\binom{f_{\frac n2}}2 hn=fnk=2n+1n1fkfnk(2f2n)
这样可以在 O ( n ) O(n) O(n)的求出某一项,也可以做一个卷积……

写了个 O ( n 2 ) O\left(n^2\right) O(n2),去 O E I S OEIS OEIS了一波确实没挂。

#include
#include
#include
#include
#define lint long long
#define p 998244353
#define inv2 499122177
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 5010
using namespace std;
int f[N],g[N],h[N];
inline int fast_pow(int x,int k,int ans=1)
{	for(;k;k>>=1,x=(lint)x*x%p) if(k&1) ans=(lint)ans*x%p;return ans;	}
int main()
{
	int n;scanf("%d",&n),f[1]=h[1]=1;rep(i,1,n) g[i]=1;
	rep(i,2,n)
	{
		rep(j,1,i-1) f[i]+=(lint)f[j]*g[i-j]%p,(f[i]>=p?f[i]-=p:0);
		f[i]=(lint)f[i]*fast_pow(i-1,p-2)%p;int t=(lint)i*f[i]%p;
		for(int j=i;j<=n;j+=i) g[j]+=t,(g[j]>=p?g[j]-=p:0);
	}
	rep(i,2,n)
	{
		rep(j,i/2+1,i-1) h[i]+=(lint)f[j]*f[i-j]%p,(h[i]>=p?h[i]-=p:0);
		if(i%2==0) h[i]+=f[i/2]*(f[i/2]-1ll)%p*inv2%p,(h[i]>=p?h[i]-=p:0);
		h[i]=f[i]-h[i]+p,(h[i]>=p?h[i]-=p:0);
	}
	rep(i,1,n) printf("%d ",f[i]);printf("\n");
	rep(i,1,n) printf("%d ",h[i]);return !printf("\n");
}

你可能感兴趣的:(学习笔记,组合计数)