感觉关于树/图计数是一门博大精深的学问,不知道这辈子有没有搞到足够明白的机会了啊QwQ
一、无标号有根树计数:
基本上是这篇的详细版本。
考虑令 表示n个点的无标号有根树数量,其生成函数为 ;考虑如何计数 ,可以这么认为:等价于找n个点的森林,每颗树都是一个子问题;然后新增一个点当这些树的根。
一个直接的想法是:是不是可以枚举树的个数,然后的到: ?然而这显然是错的,因为这样得到的答案中有重复计数,即如果我有两个完全相同的儿子,那么其二者的顺序应当是不计的;
因此考虑大小为k的树的选择情况,然后可能又会认为大小为k的树对 那一项的贡献是: ,但这还是错的,理由同上。
正确的姿势是,贡献应当是: ,也就是 个相同的等比数列的乘积。一一对应的关系:假设第 个等比数列选择的那一项是 ,那么最终对应的一种方案是,假如我们给这 个大小为k的无标号有根树编号为1到 ,那么在考虑对 的贡献中,第 种选了 个。
最后我们把所有k的情况乘起来(下面即使
也不要紧):
把那个 去掉:
显然两边取对数:
然后两边同时求导:
化简这个式子,可以得到:
比较其第n项系数,可知(后面最后一项视为 和若干多项式的乘积的和):
我们知道:
因此:
最后
后面那一项显然可以边算边处理,这样直接算是 的,可以分治NTT做到 。
二、无标号无根树计数
其实做完上面的部分这一部分也就基本完成了:令
表示n个点的无标号无根树,我们用有根树的情况减去当根不为重心的情况:
1)当n为奇数,此时重心只有一个,如果根不是重心,那么其一定有恰好一颗子树,其大小超过
,枚举这个子树的大小并且扣去:
2)和奇数稍有不同的是,此时有可能有两个重心(只是有可能)。如果根不是重心,还是像奇数一样减去那颗大小最大的,然后如果重心是在边e的两端,那么要从总数减去断开e后两颗树不一样(此时在 中会被计数两次)的情况:
这样可以在 的求出某一项,也可以做一个卷积……
写了个 ,去 了一波确实没挂。
#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");
}