- 求 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=1∑n(i−1n−1)fign−i
将
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=1∑n(i−1n−1)fi2(2n−i)
将组合数
(
n
−
1
i
−
1
)
\binom{n-1}{i-1}
(i−1n−1) 拆开得
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)(n−1)!2(2n)(n−1)!2(2n)=i=1∑n(i−1)!(n−i)!(n−1)!fi2(2n−i)=i=1∑n(i−1)!(n−i)!fi2(2n−i)=i=1∑n(i−1)!fi⋅(n−i)!2(2n−i)(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∑+∞(n−1)!f(n)xnG(x)=n=1∑+∞(n−1)!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})
F≡G×H−1(mod xn+1)。只要将
H
H
H 求逆后和
G
G
G 卷积就可以得到
F
F
F 了。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
#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)。
#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 ∀i≡j(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]xn−i
将
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)。
#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;
}
这篇博客探讨了如何计算无标号无向连通图的数目,提出了两种解法,分别是通过组合数学公式推导和利用生成函数进行多项式运算。第一种方法基于图的分解,时间复杂度为O(nlogn),第二种方法通过求解多项式ln的麦克劳林级数,同样达到O(nlogn)的时间复杂度。
4498

被折叠的 条评论
为什么被折叠?



