感觉关于树/图计数是一门博大精深的学问,不知道这辈子有没有搞到足够明白的机会了啊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)=x∑i>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} ∑j≥0ifkxki,但这还是错的,理由同上。
正确的姿势是,贡献应当是: ( ∑ i ≥ 0 z i k ) f k \left(\sum_{i\geq0}z^{ik}\right)^{f_k} (∑i≥0zik)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
k≥n+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∏(i≥0∑xik)fk=[xn+1]k>0∏(1−xk1)fk=[xn+1]k>0∏(1−xk)−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∏(1−xk)−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∏(1−xk)−fk)=lnx+k>0∑ln((1−xk)−fk)=lnx−k>0∑fkln(1−xk)
然后两边同时求导:
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>0∑fk1−xkkxk−1
化简这个式子,可以得到:
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>0∑fkk1−xkxk
比较其第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>0∑fik>0∑fkk([xn−i]1−xkxk)
我们知道:
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
1−xkxk=∑i=1n−1xik=∑i>0[k∣i]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=1∑n−1fik>0∑fkk[k∣n−i]=fn+i=1∑n−1fik∣n−i∑fkk
最后
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=n−1∑i=1n−1fi∑k∣n−ifkk
后面那一项显然可以边算边处理,这样直接算是
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=fn−k=⌊2n⌋+1∑n−1fkfn−k
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=fn−⎝⎛k=2n+1∑n−1fkfn−k⎠⎞−(2f2n)
这样可以在
O
(
n
)
O(n)
O(n)的求出某一项,也可以做一个卷积……
写了个 O ( n 2 ) O\left(n^2\right) O(n2),去 O E I S OEIS OEIS了一波确实没挂。
#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");
}