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

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

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

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

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

因此考虑大小为k的树的选择情况,然后可能又会认为大小为k的树对 f n + 1 那一项的贡献是: j 0 i f k x k i ,但这还是错的,理由同上。

正确的姿势是,贡献应当是: ( i 0 z i k ) f k ,也就是 f k 个相同的等比数列的乘积。一一对应的关系:假设第 j 个等比数列选择的那一项是 i j k ,那么最终对应的一种方案是,假如我们给这 f k 个大小为k的无标号有根树编号为1到 f k ,那么在考虑对 f n + 1 的贡献中,第 j 种选了 i j 个。

最后我们把所有k的情况乘起来(下面即使 k n + 1 也不要紧):

[ x n + 1 ] F ( x ) = [ x n + 1 ] x k > 0 ( i 0 x i k ) f k = k > 0 ( 1 1 x k ) f k = k > 0 ( 1 x k ) f k

把那个 [ x n + 1 ] 去掉:
F ( x ) = x k > 0 ( 1 x k ) f k

显然两边取对数:
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 )

然后两边同时求导:
F ( x ) F ( x ) = 1 x + k > 0 f k k x k 1 x k

化简这个式子,可以得到:
x F ( x ) = F ( x ) + F ( x ) k > 0 f k k x k 1 x k

比较其第n项系数,可知(后面最后一项视为 F ( x ) 和若干多项式的乘积的和):
n f n = f n + i > 0 f i k > 0 f k k ( [ x n i ] x k 1 x k )

我们知道: x k 1 x k = i = 1 n 1 x i k = i > 0 [ k | i ] x i
因此:
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

最后
f n = i = 1 n 1 f i k | n i f k k n 1
后面那一项显然可以边算边处理,这样直接算是 O ( n 2 ) 的,可以分治NTT做到 O ( n lg 2 n )

二、无标号无根树计数
其实做完上面的部分这一部分也就基本完成了:令 h n 表示n个点的无标号无根树,我们用有根树的情况减去当根不为重心的情况:
1)当n为奇数,此时重心只有一个,如果根不是重心,那么其一定有恰好一颗子树,其大小超过 n 2 ,枚举这个子树的大小并且扣去:

h n = f n k = n 2 + 1 n 1 f k f n k

2)和奇数稍有不同的是,此时有可能有两个重心(只是有可能)。如果根不是重心,还是像奇数一样减去那颗大小最大的,然后如果重心是在边e的两端,那么要从总数减去断开e后两颗树不一样(此时在 f n 中会被计数两次)的情况:
h n = f n ( k = n 2 + 1 n 1 f k f n k ) ( f n 2 2 )

这样可以在 O ( n ) 的求出某一项,也可以做一个卷积……

写了个 O ( n 2 ) ,去 O E I S 了一波确实没挂。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#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");
}

猜你喜欢

转载自blog.csdn.net/Mys_C_K/article/details/82585457