[学习笔记]多项式进阶(缓更)

这篇博客探讨了如何计算无标号无向连通图的数目,提出了两种解法,分别是通过组合数学公式推导和利用生成函数进行多项式运算。第一种方法基于图的分解,时间复杂度为O(nlogn),第二种方法通过求解多项式ln的麦克劳林级数,同样达到O(nlogn)的时间复杂度。
  • n n n 个点的简单 (无重边无自环) 有标号无向连通图数目。
链接

城市规划

解法 1 1 1

g n g_n gn n n n 个点的无向图数目,显然 g n = 2 ( n 2 ) ( g_n=2^{\binom{n}{2}}( gn=2(2n)(每条边选 / / /不选 ) ) )

f n f_n fn n n n 个点的无向连通图数目,枚举 1 1 1 号点所在连通块大小 i i i,将图分离成两部分,一部分是 1 1 1 号点所在的连通块,一部分是除连通块外的所有点。
g n = ∑ i = 1 n ( n − 1 i − 1 ) f i g n − i g_n=\sum_{i=1}^{n}\binom{n-1}{i-1}f_ig_{n-i} gn=i=1n(i1n1)figni
g n = 2 ( n 2 ) g_n=2^{\binom{n}{2}} gn=2(2n) 代入式子得
2 ( n 2 ) = ∑ i = 1 n ( n − 1 i − 1 ) f i 2 ( n − i 2 ) 2^{\binom{n}{2}}=\sum_{i=1}^{n}\binom{n-1}{i-1}f_i{2^\binom{n-i}{2}} 2(2n)=i=1n(i1n1)fi2(2ni)
将组合数 ( n − 1 i − 1 ) \binom{n-1}{i-1} (i1n1) 拆开得
2 ( n 2 ) = ∑ i = 1 n ( n − 1 ) ! f i 2 ( n − i 2 ) ( i − 1 ) ! ( n − i ) ! 2 ( n 2 ) ( n − 1 ) ! = ∑ i = 1 n f i 2 ( n − i 2 ) ( i − 1 ) ! ( n − i ) ! 2 ( n 2 ) ( n − 1 ) ! = ∑ i = 1 n f i ( i − 1 ) ! ⋅ 2 ( n − i 2 ) ( n − i ) ! ( 1 ) \begin{aligned}{2^{\binom{n}{2}}}&=\sum_{i=1}^{n}\frac{(n-1)!f_i{2^\binom{n-i}{2}}}{(i-1)!(n-i)!}\\\frac{{2^{\binom{n}{2}}}}{(n-1)!}&=\sum_{i=1}^{n}\frac{f_i{2^\binom{n-i}{2}}}{(i-1)!(n-i)!}\\\frac{{2^{\binom{n}{2}}}}{(n-1)!}&=\sum_{i=1}^{n}\frac{f_i}{(i-1)!}\cdot\frac{2^\binom{n-i}{2}}{(n-i)!}\qquad(1)\\\end{aligned} 2(2n)(n1)!2(2n)(n1)!2(2n)=i=1n(i1)!(ni)!(n1)!fi2(2ni)=i=1n(i1)!(ni)!fi2(2ni)=i=1n(i1)!fi(ni)!2(2ni)(1)
考虑生成函数,设
F ( x ) = ∑ n = 1 + ∞ f ( n ) ( n − 1 ) ! x n G ( x ) = ∑ n = 1 + ∞ 2 ( n 2 ) ( n − 1 ) ! x n H ( x ) = ∑ n = 0 + ∞ 2 ( n 2 ) n ! x n F(x)=\sum\limits_{n=1}^{+\infty}\frac{f(n)}{(n-1)!}x^n\\G(x)=\sum\limits_{n=1}^{+\infty}\frac{2^\binom{n}{2}}{(n-1)!}x^n\\H(x)=\sum_{n=0}^{+\infty}\frac{2^{\binom{n}{2}}}{n!}x^n F(x)=n=1+(n1)!f(n)xnG(x)=n=1+(n1)!2(2n)xnH(x)=n=0+n!2(2n)xn
根据 ( 1 ) (1) (1) F ≡ G × H − 1 ( mod  x n + 1 ) F\equiv G\times H^{-1}\quad(\text{mod}\ x^{n+1}) FG×H1(mod xn+1)。只要将 H H H 求逆后和 G G G 卷积就可以得到 F F F 了。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code \text{code} code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5, mod = 1004535809, G = 3, Gi = 334845270;
int n, m, lim, L, r[N]; ll a[N], b[N], pw[N], ans[N];
inline ll qpow(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, a = a * a % mod) if (b & 1) ans = ans * a % mod; return ans; }
namespace polynomial {
	ll C[N], D[N], inv[N], Inv[N], fac[N], f[N], g[N];
	inline void NTT(ll *A, int type) { 
		for (int i = 0; i < lim; ++ i) if (i < r[i]) swap(A[i], A[r[i]]);
		for (int mid = 1; mid < lim; mid <<= 1) {
			ll Wn = qpow(type == 1 ? G : Gi, (mod - 1) / (mid << 1));
			for (int j = 0; j < lim; j += (mid << 1)) {
				ll w = 1;
				for (int k = 0; k < mid; ++ k, w = w * Wn % mod) {
					int x = A[j + k], y = w * A[j + k + mid] % mod;
					A[j + k] = (x + y) % mod, A[j + k + mid] = (x - y + mod) % mod;
				}
			}
		}
		if (type == 1) return ;
		ll invs = qpow(lim, mod - 2);
		for (int i = 0; i < lim; ++ i) A[i] = A[i] * invs % mod;
	}
	inline void init(int len) {
		lim = 1, L = 0; while (lim <= len) lim <<= 1, ++ L;
		for (int i = 0; i < lim; ++ i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (L - 1));
	}
	inline void polytimes(ll *A, ll *B, int n, int m) {
		init(n + m), NTT(A, 1), NTT(B, 1);
		for (int i = 0; i < lim; ++ i) A[i] = A[i] * B[i] % mod;
		NTT(A, -1);
	}
	inline void polyinv(ll *A, ll *B, int n) {
		if (n == 1) { B[0] = qpow(A[0], mod - 2); return ; }
		polyinv(A, B, n + 1 >> 1), init(n + n);
		for (int i = 0; i < n; ++ i) C[i] = A[i];
		for (int i = n; i < lim; ++ i) C[i] = 0;
		NTT(C, 1), NTT(B, 1);
		for (int i = 0; i < lim; ++ i) B[i] = (2 - B[i] * C[i] % mod + mod) % mod * B[i] % mod;
		NTT(B, -1);
		for (int i = n; i < lim; ++ i) B[i] = 0;
	}
} using namespace polynomial;
int main() {
	scanf("%d", &n), fac[0] = pw[0] = b[0] = a[1] = 1;
	for (int i = 1; i <= n; ++ i) fac[i] = fac[i - 1] * i % mod; Inv[n] = qpow(fac[n], mod - 2);
	for (int i = n - 1; ~i; -- i) Inv[i] = Inv[i + 1] * (i + 1) % mod;
	for (int i = 1; i <= n; ++ i) pw[i] = pw[i - 1] * 2 % mod;
	for (int i = 2; i <= n; ++ i) a[i] = a[i - 1] * pw[i - 1] % mod;
	for (int i = 1; i <= n; ++ i) a[i] = a[i] * Inv[i - 1] % mod;
	for (int i = 1; i <= n; ++ i) b[i] = b[i - 1] * pw[i - 1] % mod;
	for (int i = 1; i <= n; ++ i) b[i] = b[i] * Inv[i] % mod;
	return polyinv(b, ans, n), polytimes(a, ans, n, n), printf("%lld\n", a[n] * fac[n - 1] % mod), 0;
}
解法 2 2 2

先设 g n g_n gn n n n 个点的有标号无向图个数, f n f_n fn n n n 个点的无向连通图个数, g n ′ g'_n gn n n n 个点的无标号无向图个数。显然 g n ′ = g n n ! g'_n=\frac{g_n}{n!} gn=n!gn

由于一张无向图是由若干个无向连通图组成的,考虑枚举连通块的个数 i i i,那么对个数的贡献为 f i i ! \frac{f^{i}}{i!} i!fi

所以 g ′ = ∑ i = 0 + ∞ f i i ! g'=\sum\limits_{i=0}^{+\infty}\frac{f^i}{i!} g=i=0+i!fi,可以发现等式右边就是 e f e^{f} ef 的麦克劳林级数,可得 g ′ = e f g'=e^{f} g=ef,所以 f = ln ⁡ g ′ f=\ln g' f=lng

使用多项式 ln ⁡ \ln ln,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code \text{code} code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5, mod = 1004535809, G = 3, Gi = 334845270;
int n, m, lim, L, r[N]; ll a[N], pw[N], ans[N];
namespace polynomial {
	int last; ll C[N], D[N], inv[N], Inv[N], fac[N], f[N], g[N];
	inline ll qpow(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, a = a * a % mod) if (b & 1) ans = ans * a % mod; return ans; }
	inline void precalc(int n) {
		if (!last) inv[0] = inv[1] = fac[0] = fac[1] = Inv[0] = Inv[1] = 1, last = 1;
		if (last < n) {
			for (int i = last + 1; i <= n; ++ i) {
				inv[i] = (mod - mod / i) * inv[mod % i] % mod;
				Inv[i] = inv[i] * Inv[i - 1] % mod;
				fac[i] = fac[i - 1] * i % mod;
			}
			last = n;
		}
	}
	inline void NTT(ll *A, int type) { 
		for (int i = 0; i < lim; ++ i) if (i < r[i]) swap(A[i], A[r[i]]);
		for (int mid = 1; mid < lim; mid <<= 1) {
			ll Wn = qpow(type == 1 ? G : Gi, (mod - 1) / (mid << 1));
			for (int j = 0; j < lim; j += (mid << 1)) {
				ll w = 1;
				for (int k = 0; k < mid; ++ k, w = w * Wn % mod) {
					int x = A[j + k], y = w * A[j + k + mid] % mod;
					A[j + k] = (x + y) % mod, A[j + k + mid] = (x - y + mod) % mod;
				}
			}
		}
		if (type == 1) return ;
		ll invs = qpow(lim, mod - 2);
		for (int i = 0; i < lim; ++ i) A[i] = A[i] * invs % mod;
	}
	inline void init(int len) {
		lim = 1, L = 0; while (lim <= len) lim <<= 1, ++ L;
		for (int i = 0; i < lim; ++ i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (L - 1));
	}
	inline void polytimes(ll *A, ll *B, int n, int m) {
		init(n + m), NTT(A, 1), NTT(B, 1);
		for (int i = 0; i < lim; ++ i) A[i] = A[i] * B[i] % mod;
		NTT(A, -1);
	}
	inline void polyinv(ll *A, ll *B, int n) {
		if (n == 1) { B[0] = qpow(A[0], mod - 2); return ; }
		polyinv(A, B, n + 1 >> 1), init(n + n);
		for (int i = 0; i < n; ++ i) C[i] = A[i];
		for (int i = n; i < lim; ++ i) C[i] = 0;
		NTT(C, 1), NTT(B, 1);
		for (int i = 0; i < lim; ++ i) B[i] = (2 - B[i] * C[i] % mod + mod) % mod * B[i] % mod;
		NTT(B, -1);
		for (int i = n; i < lim; ++ i) B[i] = 0;
	}
	inline void polyderi(ll *A, ll *B, int n) {
		for (int i = 1; i < n; ++ i) B[i - 1] = A[i] * i % mod;
		B[n - 1] = 0;
	}
	inline void polyint(ll *A, ll *B, int n) {
		precalc(n);
		for (int i = n; i; -- i) B[i] = A[i - 1] * inv[i] % mod;
		B[0] = 0;
	}
	inline void polyln(ll *A, ll *B, int n) {
		memset(g, 0, sizeof g);
		polyderi(A, f, n), polyinv(A, g, n), polytimes(f, g, n, n), polyint(f, B, n);
	}
} using namespace polynomial;
int main() {
	scanf("%d", &n), precalc(n), pw[0] = 1, a[0] = 1;
	for (int i = 1; i <= n; ++ i) pw[i] = pw[i - 1] * 2 % mod;
	for (int i = 1; i <= n; ++ i) a[i] = a[i - 1] * pw[i - 1] % mod;
	for (int i = 0; i <= n; ++ i) (a[i] *= Inv[i]) %= mod;
	return polyln(a, ans, n + 1), printf("%lld\n", ans[n] * fac[n] % mod), 0;
}

首先考虑一个合法的答案 x x x x x x 需要满足 ∀ i ≡ j ( mod  x ) , [ s i = ? ] + [ s j = ? ] + [ s i = s j ] ≥ 1 \forall i\equiv j\quad(\text{mod}\ x),[s_i=?]+[s_j=?]+[s_i=s_j]\geq 1 ij(mod x),[si=?]+[sj=?]+[si=sj]1

考虑构造两个生成函数
F ( x ) = ∑ i [ s i = V ] x i G ( x ) = ∑ i [ s i = K ] x n − i F(x)=\sum_{i}[s_i=V]x^i\\G(x)=\sum_{i}[s_i=K]x^{n-i} F(x)=i[si=V]xiG(x)=i[si=K]xni
F , G F,G F,G 相乘,令 H = F × G H=F\times G H=F×G。若 [ x i ] H ( x ) ≠ 0 [x^i]H(x)\neq 0 [xi]H(x)=0,那么说明合法答案不可能为 i i i 以及 i i i 的倍数。

多项式卷积即可,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code \text{code} code

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e6 + 5;
const double pi = acos(-1);
int T, n, L, len, ans, v[N], rev[N]; char s[N];
struct Node {
	double x, y;
	Node(double _x = 0.0, double _y = 0.0) : x(_x), y(_y){}
	friend Node operator + (Node a, Node b) { return Node(a.x + b.x, a.y + b.y); }
	friend Node operator - (Node a, Node b) { return Node(a.x - b.x, a.y - b.y); }
	friend Node operator * (Node a, Node b) { return Node(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); }
} a[N], b[N];
inline void FFT(Node *A, int len, int k) {
	for (int i = 0; i < len; ++ i) if (i < rev[i]) swap(A[i], A[rev[i]]);
	for (int mid = 2; mid <= len; mid <<= 1) {
		Node Wn(cos(2 * pi / mid), sin(2 * pi / mid) * k);
		for (int j = 0; j < len; j += mid) {
			Node w(1, 0);
			for (int k = 0; k < mid / 2; ++ k, w = w * Wn) {
				Node x = A[j + k], y = A[j + k + mid / 2] * w;
				A[j + k] = x + y, A[j + k + mid / 2] = x - y;
			}
		}
	}
	if (k == -1) for (int i = 0; i < len; ++ i) A[i].x = round(A[i].x / len);
}
int main() {
	for (scanf("%d", &T); T --; ) {
		scanf("%d%s", &n, s + 1);
		for (int i = 1; i <= n; ++ i) a[i - 1] = Node(s[i] == 'V', 0), b[n - i] = Node(s[i] == 'K', 0);
		for (len = 1, L = -1; len < 2 * n; len <<= 1) ++ L;
		for (int i = 0; i < len; ++ i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << L);
		for (int i = n; i < len; ++ i) a[i] = b[i] = Node(0, 0);
		FFT(a, len, 1), FFT(b, len, 1);
		for (int i = 0; i < len; ++ i) a[i] = a[i] * b[i];
		FFT(a, len, -1);
		for (int i = 0; i <= n; ++ i) v[i] = 0;
		for (int i = 0; i < 2 * n; ++ i) v[abs(i - n + 1)] |= (a[i].x != 0);
		for (int i = 1; i <= n; ++ i) for (int j = i; j <= n; j += i) v[i] |= v[j];
		ans = 0;
		for (int i = 1; i <= n; ++ i) ans += 1 - v[i];
		printf("%d\n", ans);
		for (int i = 1; i <= n; ++ i) if (!v[i]) printf("%d ", i);
		puts("");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值