zoukankan      html  css  js  c++  java
  • 化学数数:烷基计数、烷烃计数、烯烃计数

    Burnside 引理

    [|G/X|=frac1{|G|}sum_{gin G}|X^g| ]

    烷(van)基计数

    求基团 (-C_xH_{2x+1}) 的结构有多少种(不考虑空间异构,能否稳定存在等情况)。

    等价问题:求所有节点满足孩子 (leq 3) 的有根树的数目。

    (F(x)) 表示答案的生成函数,显然

    [F(x)=xF^3(x)+1 ]

    是不正确的,因为对于一个节点,1号儿子与2号儿子完全相同,和2号儿子与3号儿子完全相同,仅仅贡献一次。我们要 无标号有根树 的计数。任意重排列儿子的顺序得到的若干种树之间无差别。这里就要用 Burnside引理 来求解。

    对应式子,我们可以写出

    [F(x)=xfrac{F^3(x)+3F(x)F(x^2)+2F(x^3)}6+1 ]

    分治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;
    }
    

    [G(F(x))=xfrac{F^3(x)+3F(x)F(x^2)+2F(x^3)}6-F(x)+1 ]

    则相当于解

    [G(F(x))equiv 0pmod{x^n} ]

    由泰勒公式:

    [G(F(x))equiv G(F_0(x))+G'(F_0(x))(F(x)-F_0(x))pmod{x^{2n}} ]

    其中

    [F(x)equiv F_0(x)pmod{x^n} ]

    高次项在后面被略去。

    整理上面的式子,得到

    [F(x)equiv F_0(x)-frac{G(F_0(x))}{G'(F_0(x))}pmod{x^{2n}} ]

    发现 (F_0(x^2))(F_0(x^3))(mod x^{2n}) 下已知,故可以当作 常数。令 (A=F_0(x^2))(B=F_0(x^3)),代入求导得到

    [F(x)equiv F_0(x)-frac{x(F_0^3(x)+3AF_0(x)+2B)-6F_0(x)+6}{x(3F_0^2(x)+3A)-6} ]

    // 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) 表示这棵树是否有两个重心,并且当重心间的边切除后,两棵子树是否同构,则

    [p-q+s=1 ]

    设答案多项式为 (A(x)),两边 (sum) 起来就有:

    [P(x)-Q(x)+S(x)=A(x) ]

    根据 Burnside引理

    [P(x)=xfrac{F^4(x)+8F(x^3)F(x)+6F(x^2)F^2(x)+3F^2(x^2)+6F(x^4)}{24} ]

    [Q(x)=frac{(F(x)-1)^2+F(x^2)-1}2 ]

    [S(x)=F(x^2) ]

    注意 (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)=xfrac{F^2(x)+F(x^2)}2 ]

    显然 (G(x)) 不能 (+1)

    设答案的生成函数为 (A(x)),则

    [A(x)=frac{G^2(x)+G(x^2)}2 ]

    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;
    }
    
  • 相关阅读:
    前端 一——介绍
    python知识点拾遗
    python 五——自定义线程池
    python 四——线程、进程、协程
    python 三——列表、字典、元祖、字符串、set
    动态规划法求解0-1背包
    贪心法求解背包问题
    学生成绩管理系统
    [C语言练习]学生学籍管理系统
    [C语言练习]万年历加强版
  • 原文地址:https://www.cnblogs.com/ac-evil/p/14529250.html
Copyright © 2011-2022 走看看