感觉关于树/图计数是一门博大精深的学问,不知道这辈子有没有搞到足够明白的机会了啊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
#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");
}