Burnside 引理
烷(van)基计数
求基团 (-C_xH_{2x+1}) 的结构有多少种(不考虑空间异构,能否稳定存在等情况)。
等价问题:求所有节点满足孩子 (leq 3) 的有根树的数目。
令 (F(x)) 表示答案的生成函数,显然
是不正确的,因为对于一个节点,1号儿子与2号儿子完全相同,和2号儿子与3号儿子完全相同,仅仅贡献一次。我们要 无标号有根树 的计数。任意重排列儿子的顺序得到的若干种树之间无差别。这里就要用 Burnside引理
来求解。
对应式子,我们可以写出
分治fft
即可,复杂度 (mathcal O(nlog^2 n))。如果用 牛顿迭代
,可以做到 (mathcal O(nlog n))。
// 分治fft
#include <bits/stdc++.h>
using std::swap; using std::reverse;
const int N = 100005, P = 998244353, inv2 = (P+1)/2, inv3 = (P+1)/3, inv6 = (P+1)/6;
int n, F[N];
int inc(int a, int b) { return (a += b) >= P ? a - P : a; }
int qpow(int a, int b) {
int t = 1;
for (; b; b >>= 1, a = 1LL * a * a % P)
if (b & 1) t = 1LL * t * a % P;
return t;
}
int W[N * 4];
void prework(int n) {
for (int i = 1; i < n; i <<= 1)
for (int j = W[i] = 1, Wn = qpow(3, (P-1)/i>>1); j < i; j++)
W[i + j] = 1LL * W[i + j - 1] * Wn % P;
}
void fft(int *a, int n, int op) {
static int rev[N * 4] = {0};
for (int i = 1; i < n; i++)
if ((rev[i] = rev[i>>1]>>1|(i&1?n>>1:0)) > i) swap(a[i], a[rev[i]]);
for (int q = 1; q < n; q <<= 1)
for (int p = 0; p < n; p += q << 1)
for (int i = 0, t; i < q; i++)
t = 1LL * W[q+i] * a[p+q+i] % P, a[p+q+i] = inc(a[p+i], P-t), a[p+i] = inc(a[p+i], t);
if (op) return;
for (int i = 0, inv = qpow(n, P - 2); i < n; i++) a[i] = 1LL * a[i] * inv % P;
reverse(a + 1, a + n);
}
int getsz(int n) { int x = 1; while (x < n) x <<= 1; return x; }
void solve(int l, int r) {
if (l == r) {
if (l % 3 == 1) F[l] = (F[l] + 1LL * F[l / 3] * inv3) % P;
return;
}
int mid = l+r>>1;
solve(l, mid);
static int x[N * 4], y[N * 4], z[N * 4];
int len = getsz(2 * (r - l));
for (int i = 0; i < len; i++)
x[i] = i <= mid - l ? F[l + i] : 0,
y[i] = i < r - l ? F[i] : 0,
z[i] = i < r - l ? (i & 1 ? 0 : F[i / 2]) : 0;
fft(x, len, 1), fft(y, len, 1), fft(z, len, 1);
for (int i = 0; i < len; i++)
x[i] = (1LL * x[i] * y[i] % P * y[i] % P * (l ? inv2 : inv6) + 1LL * x[i] * z[i] % P * inv2) % P;
// 当l!=0时,贡献要*3,表示 [l,mid],[0,..)[0,..), [0,..)[l,mid][0,..), [0,..)[0,..)[l,mid]。后两种贡献在l!=0时会漏
fft(x, len, 0);
for (int i = mid + 1; i <= r; i++) F[i] = inc(F[i], x[i - l - 1]);
solve(mid + 1, r);
}
int main() {
scanf("%d", &n); prework((n + 1) * 2);
F[0] = 1;
solve(0, n);
printf("%d", F[n]);
return 0;
}
令
则相当于解
由泰勒公式:
其中
高次项在后面被略去。
整理上面的式子,得到
发现 (F_0(x^2)) 与 (F_0(x^3)) 在 (mod x^{2n}) 下已知,故可以当作 常数。令 (A=F_0(x^2)),(B=F_0(x^3)),代入求导得到
// Newton迭代
#include <bits/stdc++.h>
using std::vector; using std::swap; using std::reverse; using std::max; using std::min;
typedef vector<int> poly;
const int N = 100005, P = 998244353;
int n;
int inc(int a, int b) { return (a += b) >= P ? a - P : a; }
int qpow(int a, int b) {
int t = 1;
for (; b; b >>= 1, a = 1LL * a * a % P)
if (b & 1) t = 1LL * t * a % P;
return t;
}
int W[N * 4];
void prework(int n) {
for (int i = 1; i < n; i <<= 1)
for (int j = W[i] = 1, Wn = qpow(3, (P-1)/i>>1); j < i; j++)
W[i + j] = 1LL * W[i + j - 1] * Wn % P;
}
void fft(poly &a, int n, int op) {
a.resize(n);
static int rev[N * 4] = {0};
for (int i = 1; i < n; i++)
if ((rev[i] = rev[i>>1]>>1|(i&1?n>>1:0)) > i) swap(a[i], a[rev[i]]);
for (int q = 1; q < n; q <<= 1)
for (int p = 0; p < n; p += q << 1)
for (int i = 0, t; i < q; i++)
t = 1LL * W[q+i] * a[p+q+i] % P, a[p+q+i] = inc(a[p+i], P-t), a[p+i] = inc(a[p+i], t);
if (op) return;
for (int i = 0, inv = qpow(n, P-2); i < n; i++) a[i] = 1LL * a[i] * inv % P;
reverse(a.begin() + 1, a.end());
}
poly operator ~ (poly A) {
poly B(1, qpow(A[0], P - 2)), C;
for (int L = 1; L < A.size(); L <<= 1) {
(C = A).resize(L * 2); fft(B, L * 4, 1), fft(C, L * 4, 1);
for (int i = 0; i < L * 4; i++) B[i] = (B[i] * 2 - 1LL * B[i] * B[i] % P * C[i] % P + P) % P;
fft(B, L * 4, 0); B.resize(L * 2);
}
return B.resize(A.size()), B;
}
void fix(poly &A) { int x = A.size(); while (x > 1 && !A[x - 1]) x--; A.resize(x); }
int getsz(int n) { int x = 1; while (x < n) x <<= 1; return x; }
poly operator + (poly A, poly B) {
A.resize(max(A.size(), B.size()));
for (int i = 0; i < B.size(); i++) A[i] = inc(A[i], B[i]);
return fix(A), A;
}
poly operator - (poly A, poly B) {
A.resize(max(A.size(), B.size()));
for (int i = 0; i < B.size(); i++) A[i] = inc(A[i], P - B[i]);
return fix(A), A;
}
poly operator * (poly A, poly B) {
int L = getsz(A.size() + B.size() - 1);
fft(A, L, 1), fft(B, L, 1);
for (int i = 0; i < L; i++) A[i] = 1LL * A[i] * B[i] % P;
return fft(A, L, 0), fix(A), A;
}
poly operator * (int k, poly A) {
for (int i = 0; i < A.size(); i++) A[i] = 1LL * k * A[i] % P;
return A;
}
poly F;
poly newton(int n) {
poly F = poly(1, 1), x = poly(2); x[1] = 1;
for (int L = 1; L < n; L <<= 1) {
poly A, B; A.resize(L * 2), B.resize(L * 2);
for (int i = 0; i < L * 2; i++)
A[i] = i & 1 ? 0 : F[i / 2],
B[i] = i % 3 ? 0 : F[i / 3];
poly C = (3 * F * F + 3 * A) * x - poly(1, 6), D = (F * F * F + 3 * A * F + 2 * B) * x - 6 * F + poly(1, 6);
C.resize(L * 2), D.resize(L * 2);
F = F - ~C * D; F.resize(L * 2);
}
return F.resize(n), F;
}
int main() {
scanf("%d", &n); prework((n + 1) * 2);
F = newton(n + 1);
printf("%d", F[n]);
return 0;
}
烷(van)烃计数
求化学式 (C_xH_{2x+2}) 的结构有多少种。
对于上述问题,发现有两点不同:
- 根可以有4个儿子
- 有根树 ( ightarrow) 无根树
定理:设 (p) 是一棵无根树的 点等价类数(即把这棵无根树的所有点拿出来做为根,不同构的树的数目),(q) 是一棵无根树的 边等价类数(即把这棵无根树的所有边拿出来做为类似根的特殊边,不同构的树的数目),(s) 表示这棵树是否有两个重心,并且当重心间的边切除后,两棵子树是否同构,则
设答案多项式为 (A(x)),两边 (sum) 起来就有:
根据 Burnside引理
:
注意 (Q(x)) 减一是由于 边存在必须两端都有点。
直接求解即可。
int main() {
scanf("%d", &T);
for (int i = 1; i <= T; i++)
scanf("%d", &a[i]), n = max(n, a[i]);
x = poly(2); x[1] = 1;
prework((n + 1) * 2);
F = newton(n + 1);
poly F2, A(n + 1), B(n + 1), C(n + 1);
F2 = F * F; F2.resize(n + 1);
for (int i = 0; i <= n; i++)
A[i] = i & 1 ? 0 : F[i / 2], B[i] = i % 3 ? 0 : F[i / 3], C[i] = i & 3 ? 0 : F[i / 4];
G = qpow(24, P - 2) * x * (F2 * F2 + 8 * B * F + 6 * A * F2 + 3 * A * A + 6 * C); G.resize(n + 1);
Q = (P+1)/2 * ((F - poly(1, 1)) * (F - poly(1, 1)) + A - poly(1, 1)); Q.resize(n + 1);
R = G - Q + A;
for (int i = 1; i <= T; i++)
printf("%d
", R[a[i]]);
return 0;
}
烯烃计数
求化学式为 (C_xH_{2x}) 的烯烃的结构数(忽略空间异构和顺反异构)。
考虑枚举碳碳双键的位置,并且尝试断开,发现两端必须是碳,并且这个碳的度数为2。
考虑令 (G(x)) 表示两端形态的方案数,显然
显然 (G(x)) 不能 (+1)。
设答案的生成函数为 (A(x)),则
int main() {
x = poly(2); x[1] = 1;
scanf("%d", &n); prework((n + 1) * 2);
F = newton(n + 1);
poly F2(n + 1);
for (int i = 0; i <= n; i++)
F2[i] = i & 1 ? 0 : F[i / 2];
G = (P+1)/2 * x * (F * F + F2); G.resize(n + 1);
poly G2(n + 1);
for (int i = 0; i <= n; i++)
G2[i] = i & 1 ? 0 : G[i / 2];
A = (P+1)/2 * (G * G + G2); A.resize(n + 1);
for (int i = 2; i <= n; i++)
printf("%d
", A[i]);
return 0;
}