比较综合的一道图计数题目。
本文中,以大写字母表示对应小写字母数列的普通生成函数(OGF)。
前置知识
多项式全家桶(乘法,求逆,ln,exp)
欧拉变换
可以这样理解其组合意义:假设有很多种数量无穷多的物品,每种物品有一个大小,大小为
Burnside 引理 / Polya 定理
即对于置换群
无标号圆根圆方树(有根仙人掌)计数
与无标号树计数相同,我们先考虑有根的情况,稍后再转化为无根。
我们知道每个仙人掌对应着一棵圆方树。而圆方树与普通树有一些共性,也有一些不同的地方,比如:
- 圆点和方点本质不同,且只有圆点占据体积。
- 圆点与圆点,方点与方点不能相邻。
- 方点的度数至少为
2 。 - 圆点的儿子是无序的,而方点的儿子构成的是一个环,因此是有序的。但是对称同构将视为同一种情况,因为环可以翻转。
由后两条性质可以看出圆点为根与方点为根是不一样的。因为圆点为根时不需要考虑环的问题,所以我们考虑先求出这种情况的答案。
设
为了求出
容易看出
而
前面的
(我第一次推的时候没有把
那么圆点为根的情况与普通树计数相同,是一堆有父亲方根子树的无序排列,即
而
然而牛顿迭代里面套 exp 的常数比较大,可以考虑对两边同时取 ln,得
对这个式子求导时注意到
无标号方根圆方树计数
方点做根的情况还要难办一些。前面已经提到了有父亲和无父亲情况的不同,因为此时并没有一个独特的子树,所以它的所有圆根子树构成的环既可以旋转也可以翻转。这是一个无标号环同构问题,首先要考虑的就是 Burnside 引理。
假设根节点有
如果看不太明白也没有关系,我们来分析一下这个式子。
第一行:为了方便我将
第二行:枚举最大公因数为
第三行:前面交换和式,后面可以提出一个公比为
第四行:我们知道
因为
无标号无根仙人掌计数
有了有根情况的答案,我们接下来仍可以参考无标号树计数的套路,选出一个具有重心性质的点来代表这个仙人掌,并用总方案数减去不合法方案。那么这个特殊的点是哪一个呢?
关注圆方树的一个性质:对于任意一棵圆方树,有且仅有一个节点,使得如果它是圆点则所有子树中圆点个数都
(这个性质是我玩样例的时候乱猜的,实际上是对的,但我现在还不会证 qwq)
那么我们枚举每个节点,用所有以它为根的方案减去它不是重心的方案,得
两部分巧妙的拼合在了一起,变成了只需要一个卷积就能处理的问题。所以复杂度也是
无标号荒漠计数
一个无标号荒漠由若干个无标号仙人掌无序组合构成,也就是对
下面是我的代码,但看懂上面的推理后就不建议看代码了,式子写得很乱。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN=1<<19;
const int P=998244353;
const int G=3, IG=332748118;
const int I2=499122177;
int qpow(int n, int k)
{
int r=1;
while (k)
{
if (k&1) r=1ll*r*n%P;
n=1ll*n*n%P, k>>=1;
}
return r;
}
/* poly templates begin */
int l, r[MAXN];
void getl(int);
void NTT(int*, int);
void poly_inv(int*, int*, int);
void poly_diff(int*, int*, int);
void poly_int(int*, int*, int);
void poly_ln(int*, int*, int);
void poly_exp(int*, int*, int);
/* poly templates end */
int f[MAXN], g[MAXN], h[MAXN], t1[MAXN], t2[MAXN];
int a[MAXN], b[MAXN];
void solve(int n)
{
if (n==2) return a[0]=0, a[1]=1, void();
solve(n/2+1);
for (int i=0; i<n; i++) t1[i]=a[i+1];
poly_ln(t1, h, n);
for (int i=0; i<n; i++)
f[i]=a[i], g[i]=i&1?0:a[i>>1];
getl(5*n);
for (int i=n; i<l; i++) f[i]=g[i]=0;
NTT(f, 1), NTT(g, 1);
for (int i=0; i<l; i++)
t1[i]=2ll*(1-f[i]+P)*(g[i]-1+P)%P;
NTT(t1, -1);
poly_inv(t1, t2, n);
getl(5*n);
for (int i=n; i<l; i++) h[i]=t2[i]=0;
NTT(t2, 1);
for (int i=0; i<l; i++)
b[i]=(((1ll*f[i]+2*g[i]-2)%P*f[i]-g[i])%P*t2[i]%P+P)%P;
NTT(b, -1);
for (int i=1; i<n; i++)
{
int ii=(P-qpow(i, P-2))%P;
for (int j=1; i*j<n; j++)
h[i*j]=(h[i*j]+1ll*b[j]*ii)%P;
}
NTT(h, 1);
for (int i=0; i<l; i++)
t1[i]=((((1ll*f[i]+2*g[i]-4)%P*f[i]-5ll*g[i]+6)%P*f[i]+2*g[i]-2)%P+P)%P;
NTT(t1, -1);
poly_inv(t1, t2, n);
getl(5*n);
for (int i=n; i<l; i++) t2[i]=0;
NTT(t2, 1);
for (int i=0; i<l; i++)
f[i]=2ll*f[i]%P*(f[i]-1)%P*(f[i]-1)%P*(g[i]-1+P)%P*h[i]%P*t2[i]%P;
NTT(f, -1);
for (int i=0; i<n; i++) a[i]=(a[i]-f[i]+P)%P;
}
int pri[MAXN], phi[MAXN];
bool mark[MAXN];
int main()
{
// freopen("cactus.in", "r", stdin);
// freopen("cactus.out", "w", stdout);
int n;
scanf("%d", &n);
phi[1]=1;
for (int i=2, cnt=0; i<=n; i++)
{
if (!mark[i]) pri[++cnt]=i, phi[i]=i-1;
for (int j=1; j<=cnt&&i*pri[j]<=n; j++)
{
mark[i*pri[j]]=1;
if (i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
solve(++n);
for (int i=0; i<n; i++)
f[i]=a[i], g[i]=i&1?0:a[i>>1];
getl(3*n);
for (int i=n; i<l; i++) f[i]=g[i]=0;
NTT(f, 1), NTT(g, 1);
for (int i=0; i<l; i++)
t1[i]=2ll*(1-f[i]+P)*(g[i]-1+P)%P;
NTT(t1, -1);
poly_inv(t1, t2, n);
getl(3*n);
for (int i=n; i<l; i++) h[i]=t2[i]=0;
NTT(t2, 1);
for (int i=0; i<l; i++)
b[i]=(((1ll*f[i]+2*g[i]-2)%P*f[i]-g[i])%P*t2[i]%P+P)%P;
NTT(b, -1);
for (int i=n; i<l; i++) b[i]=0;
for (int i=0; i<n; i++)
t1[i]=i&1?0:2*(P-a[i>>1])%P;
t1[0]=(t1[0]+2)%P;
poly_inv(t1, t2, n);
getl(3*n);
for (int i=0; i<n; i++) t1[i]=a[i];
for (int i=n; i<l; i++) t1[i]=t2[i]=0;
NTT(t1, 1), NTT(t2, 1);
for (int i=0; i<l; i++)
f[i]=1ll*(t1[i]+1)*(t1[i]+1)%P*t2[i]%P;
NTT(f, -1);
f[0]=0;
for (int i=0; i<n; i++) t1[i]=(P-a[i])%P;
t1[0]=(t1[0]+1)%P;
poly_ln(t1, t2, n);
for (int i=1; i<n; i++)
{
int ii=1ll*(P-phi[i])*qpow(i, P-2)%P;
for (int j=1; i*j<n; j++)
f[i*j]=(f[i*j]+1ll*t2[j]*ii)%P;
}
getl(2*n);
for (int i=n; i<l; i++) b[i]=0;
NTT(a, 1), NTT(b, 1);
for (int i=0; i<l; i++) a[i]=1ll*a[i]*b[i]%P;
NTT(a, -1);
for (int i=0; i<n; i++) a[i]=(1ll*f[i]*I2-a[i]+P)%P;
for (int i=0; i<n; i++) b[i]=0;
for (int i=1; i<n; i++)
{
int ii=qpow(i, P-2);
for (int j=1; i*j<n; j++)
b[i*j]=(b[i*j]+1ll*a[j]*ii)%P;
}
poly_exp(b, a, n+1);
for (int i=1; i<n; i++) printf("%d\n", a[i]);
return 0;
}