图计数
有标号无向连通图计数
设 \(f_n\) 表示大小为 \(n\) 的 有标号无向连通图 方案数,\(g_n\) 表示大小为 \(n\) 的 有标号无向图 方案数,显然有 \(g_n = 2^{\binom{n}{2}}\),通过钦定 \(1\) 号节点所在联通块的大小为 \(i\),它们之间具有关系
也就是说
而
移项可得
不难看出右式是一个卷积形式,这告诉我们
设 \(F(x) = \sum\limits_{i = 0}^{n - 1}\frac{2^{\binom{i}{2}}}{i!}x^i\),\(G(x) = \sum\limits_{i = 1}^n\frac{f_i}{(i - 1)!}x^i\),\(H(x) = \sum\limits_{i = 1}^{n}\frac{2^{\binom{i}{2}}}{(i - 1)!}x^i\),那么有 \(F(x)G(x) = H(x)\),求逆即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define PII pair<ll, ll>
#define pb push_back
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define rev(f, n) reverse(f, f + (n))
const int N = 4e5 + 10, _G = 3, mod = 1004535809;
ll qpow(ll a, ll k = mod - 2) {
ll res = 1;
while (k) {
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
const int invG = qpow(_G);
int rev[N << 1], rev_len;
void rev_init(int n) {
if (rev_len == n) return;
for (int i = 0; i < n; i ++ ) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
rev_len = n;
}
void NTT(int *g, int op, int n) {
rev_init(n);
static ull f[N << 1], Gk[N << 1] = {1};
for (int i = 0; i < n; i ++ ) f[i] = g[rev[i]];
for (int k = 1; k < n; k <<= 1) {
int G1 = qpow(~op ? _G : invG, (mod - 1) / (k << 1));
for (int i = 1; i < k; i ++ ) Gk[i] = Gk[i - 1] * G1 % mod;
for (int i = 0; i < n; i += k << 1) {
for (int j = 0; j < k; j ++ ) {
int tmp = Gk[j] * f[i | j | k] % mod;
f[i | j | k] = f[i | j] + mod - tmp;
f[i | j] += tmp;
}
}
if (k == (1 << 10)) for (int i = 0; i < n; i ++ ) f[i] %= mod;
}
if (~op) for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod;
else {
int invn = qpow(n);
for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod * invn % mod;
}
}
void px(int *f, int *g, int n) {
for (int i = 0; i < n; i ++ ) f[i] = 1ll * f[i] * g[i] % mod;
}
void covolution(int *f, int *g, int len, int lim) {
static int sav[N << 1];
int n; for (n = 1; n < len << 1; n <<= 1);
clr(sav, n); cpy(sav, g, n);
NTT(sav, 1, n); NTT(f, 1, n);
px(f, sav, n); NTT(f, -1, n);
clr(f + lim, n - lim), clr(sav, n);
}
void Poly_inv(int *f, int m) {
static int g1[N << 1], g2[N << 1], sav[N << 1];
int n; for (n = 1; n < m; n <<= 1);
g1[0] = qpow(f[0]);
for (int len = 2; len <= n; len <<= 1) {
cpy(g2, g1, len >> 1), cpy(sav, f, len);
NTT(g2, 1, len), NTT(sav, 1, len);
px(g2, sav, len); NTT(g2, -1, len);
clr(g2, len >> 1); cpy(sav, g1, len);
NTT(sav, 1, len); NTT(g2, 1, len);
px(g2, sav, len); NTT(g2, -1, len);
for (int i = len >> 1; i < len; i ++ ) g1[i] = (2ll * g1[i] - g2[i] + mod) % mod;
}
cpy(f, g1, m), clr(g1, n), clr(g2, n), clr(sav, n);
}
int n, m, F[N], G[N];
int pow2[N], frac[N], inv[N];
void solve() {
cin >> n;
frac[0] = inv[0] = 1;
for (int i = 1; i <= n; i ++ ) frac[i] = 1ll * frac[i - 1] * i % mod;
inv[n] = qpow(frac[n]);
for (int i = n - 1; i; i -- ) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
for (int i = 0; i <= n; i ++ ) pow2[i] = qpow(2, 1ll * i * (i - 1) / 2);
for (int i = 0; i < n; i ++ ) F[i] = 1ll * pow2[i] * inv[i] % mod;
for (int i = 1; i <= n; i ++ ) G[i] = 1ll * pow2[i] * inv[i - 1] % mod;
Poly_inv(F, n); covolution(F, G, n + 1, n + 1);
cout << 1ll * F[n] * frac[n - 1] % mod << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}
有标号DAG计数
考虑设 \(f_i\) 为 \(i\) 点带标号 DAG 的数量,我们考虑 钦定 有 \(j\) 个点的 入度 为 \(0\),那么意味着我们可以删掉这些点得到较少部分点的方案数递推。
考虑设 \(f_i\) 中恰好有 \(j\) 个点的入度为 \(0\) 的方案数为 \(g_{i, \, j}\),至少有 \(j\) 个点的入度为 \(0\) 的方案数为 \(f_{i, \, j}\),容易得出:
由二项式反演有:
而 \(f_i\) 可以表示为:
考虑前面 \(j\) 个度为 \(0\) 的点可以向后任意连边,即:
于是我们可以这样化简:
考虑 \(ij = \binom{i + j}{2} - \binom{i}{2} - \binom{j}{2}\),则:
于是,我们移项可以得到:
设 \(F(x) = \sum\limits_{i = 1}^{n}\frac{(-1)^{i - 1}}{i!2^{\binom{i}{2}}}\),\(G(x) = \sum\limits_{i = 0}^{n}\frac{f_i}{i!2^{\binom{i}{2}}}\),那么容易看出:
也就是 \(G(x) = \frac{1}{1 - F(x)}\),使用多项式求逆可以得到答案。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define PII pair<ll, ll>
#define PPI pair<ll, PII>
#define pb push_back
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define rev(f, n) reverse(f, f + (n))
const int _G = 3, _i = 86583718, mod = 998244353;
const int N = 1.5e5 + 10, INF = 1e9;
ll qpow(ll a, ll k = mod - 2, ll p = mod) {
ll res = 1;
while (k) {
if (k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
const int invG = qpow(_G), inv2 = qpow(2);
int rev[N << 1], rev_len;
void rev_init(int n) {
if (rev_len == n) return;
for (int i = 0; i < n; i ++ ) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
rev_len = n;
}
void NTT(int *g, int op, int n) {
rev_init(n);
static ull f[N << 1], Gk[N << 1] = {1};
for (int i = 0; i < n; i ++ ) f[i] = g[rev[i]];
for (int k = 1; k < n; k <<= 1) {
int G1 = qpow(~op ? _G : invG, (mod - 1) / (k << 1));
for (int i = 1; i < k; i ++ ) Gk[i] = Gk[i - 1] * G1 % mod;
for (int i = 0; i < n; i += k << 1) {
for (int j = 0; j < k; j ++ ) {
int tmp = Gk[j] * f[i | j | k] % mod;
f[i | j | k] = f[i | j] + mod - tmp;
f[i | j] += tmp;
}
}
if (k == (1 << 10)) for (int i = 0; i < n; i ++ ) f[i] %= mod;
}
if (~op) for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod;
else {
int invn = qpow(n);
for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod * invn % mod;
}
}
void px(int *f, int *g, int n) {
for (int i = 0; i < n; i ++ ) f[i] = 1ll * f[i] * g[i] % mod;
}
void covolution(int *f, int *g, int len, int lim) {
static int sav[N << 1];
int n; for (n = 1; n < len << 1; n <<= 1);
clr(sav, n); cpy(sav, g, n);
NTT(sav, 1, n); NTT(f, 1, n);
px(f, sav, n); NTT(f, -1, n);
clr(f + lim, n - lim), clr(sav, n);
}
void Poly_inv(int *f, int m) {
static int b1[N << 1], b2[N << 1], sav[N << 1];
int n; for (n = 1; n < m; n <<= 1);
b1[0] = qpow(f[0]);
for (int len = 2; len <= n; len <<= 1) {
cpy(b2, b1, len >> 1), cpy(sav, f, len);
NTT(b2, 1, len), NTT(sav, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
clr(b2, len >> 1); cpy(sav, b1, len);
NTT(sav, 1, len); NTT(b2, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
for (int i = len >> 1; i < len; i ++ ) b1[i] = (2ll * b1[i] - b2[i] + mod) % mod;
}
cpy(f, b1, m), clr(b1, n), clr(b2, n), clr(sav, n);
}
int n, F[N << 1], frac[N], pw2[N];
void solve() {
cin >> n;
F[0] = frac[0] = 1;
for (int i = 1; i <= n; i ++ ) frac[i] = 1ll * frac[i - 1] * i % mod, pw2[i] = qpow(2, 1ll * i * (i - 1) >> 1, mod - 1);
for (int i = 1; i <= n; i ++ ) {
F[i] = pw2[i] * frac[i] % mod;
if (i & 1) F[i] = (mod - F[i]) % mod;
F[i] = qpow(F[i]);
}
Poly_inv(F, n + 1);
for (int i = 1; i <= n; i ++ ) cout << 1ll * F[i] * pw2[i] % mod * frac[i] % mod << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}
有标号弱连通 DAG 计数
这个题考虑了 DAG 是弱连通的,我们考虑这个 DAG 总共被分成了 \(k\) 个联通块,设 \(H(x)\) 为弱连通 DAG 的 EGF,\(G_k(x)\) 为 DAG 有 \(k\) 个联通块的 EGF,那么可以得到:
考虑麦克劳林级数,可以得到:
这告诉我们 \(G(x) = e^{H(x)}\),即 \(H(x) = \ln{G(x)}\)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define PII pair<ll, ll>
#define PPI pair<ll, PII>
#define pb push_back
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define rev(f, n) reverse(f, f + (n))
const int _G = 3, _i = 86583718, mod = 998244353;
const int N = 1.5e5 + 10, INF = 1e9;
ll qpow(ll a, ll k = mod - 2) {
ll res = 1;
while (k) {
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
const int invG = qpow(_G), inv2 = qpow(2);
int rev[N << 1], rev_len;
void rev_init(int n) {
if (rev_len == n) return;
for (int i = 0; i < n; i ++ ) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
rev_len = n;
}
void NTT(int *g, int op, int n) {
rev_init(n);
static ull f[N << 1], Gk[N << 1] = {1};
for (int i = 0; i < n; i ++ ) f[i] = g[rev[i]];
for (int k = 1; k < n; k <<= 1) {
int G1 = qpow(~op ? _G : invG, (mod - 1) / (k << 1));
for (int i = 1; i < k; i ++ ) Gk[i] = Gk[i - 1] * G1 % mod;
for (int i = 0; i < n; i += k << 1) {
for (int j = 0; j < k; j ++ ) {
int tmp = Gk[j] * f[i | j | k] % mod;
f[i | j | k] = f[i | j] + mod - tmp;
f[i | j] += tmp;
}
}
if (k == (1 << 10)) for (int i = 0; i < n; i ++ ) f[i] %= mod;
}
if (~op) for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod;
else {
int invn = qpow(n);
for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod * invn % mod;
}
}
void px(int *f, int *g, int n) {
for (int i = 0; i < n; i ++ ) f[i] = 1ll * f[i] * g[i] % mod;
}
void covolution(int *f, int *g, int len, int lim) {
static int sav[N << 1];
int n; for (n = 1; n < len << 1; n <<= 1);
clr(sav, n); cpy(sav, g, n);
NTT(sav, 1, n); NTT(f, 1, n);
px(f, sav, n); NTT(f, -1, n);
clr(f + lim, n - lim), clr(sav, n);
}
int inv[N << 1], inv_len;
void inv_init(int n) {
if (n <= inv_len) return;
if (!inv_len) inv[0] = inv[1] = 1, inv_len = 1;
for (int i = inv_len + 1; i <= n; i ++ ) inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
inv_len = n;
}
void Poly_d(int *f, int n) {
for (int i = 1; i < n; i ++ ) f[i - 1] = 1ll * f[i] * i % mod;
f[n - 1] = 0;
}
void Poly_int(int *f, int n) {
for (int i = n; i; i -- ) f[i] = 1ll * f[i - 1] * inv[i] % mod;
f[0] = 0;
}
void Poly_inv(int *f, int m) {
static int b1[N << 1], b2[N << 1], sav[N << 1];
int n; for (n = 1; n < m; n <<= 1);
b1[0] = qpow(f[0]);
for (int len = 2; len <= n; len <<= 1) {
cpy(b2, b1, len >> 1), cpy(sav, f, len);
NTT(b2, 1, len), NTT(sav, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
clr(b2, len >> 1); cpy(sav, b1, len);
NTT(sav, 1, len); NTT(b2, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
for (int i = len >> 1; i < len; i ++ ) b1[i] = (2ll * b1[i] - b2[i] + mod) % mod;
}
cpy(f, b1, m), clr(b1, n), clr(b2, n), clr(sav, n);
}
void Poly_ln(int *f, int n) {
static int sav[N << 1];
inv_init(n); cpy(sav, f, n);
Poly_d(sav, n); Poly_inv(f, n);
covolution(f, sav, n, n); Poly_int(f, n - 1);
clr(sav, n);
}
int n, F[N << 1];
int frac[N], pw2[N];
void solve() {
cin >> n;
F[0] = frac[0] = 1;
for (int i = 1; i <= n; i ++ ) frac[i] = 1ll * frac[i - 1] * i % mod, pw2[i] = qpow(2, 1ll * i * (i - 1) >> 1);
for (int i = 1; i <= n; i ++ ) {
F[i] = qpow(1ll * pw2[i] * frac[i] % mod);
if (i & 1) F[i] = (mod - F[i]) % mod;
}
Poly_inv(F, n + 1);
for (int i = 1; i <= n; i ++ ) F[i] = 1ll * F[i] * pw2[i] % mod;
Poly_ln(F, n + 1);
for (int i = 1; i <= n; i ++ ) cout << 1ll * F[i] * frac[i] % mod << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}
还有一种不用 \(\exp\) 的方法,我们考虑我们已经算出了 DAG 的方案数,弱连通 DAG 的方案数相当于总的方案数减去不连通的方案数,设 \(g_i\) 为弱连通 DAG 的数量,\(f_i\) 为任意 DAG 的数量,我们考虑枚举 \(1\) 号点所在的联通块大小:
那么我们可以移项得到:
把 \((i - 1)!\) 除过去可以得到:
设 \(F(x) = \sum\limits_{i = 1}^{n}\frac{f_i}{i!}\),\(G(x) = \sum\limits_{i = 1}^{n}\frac{f_i}{(i - 1)!}\),\(H(x) = \sum\limits_{i = 1}^{n}\frac{g_i}{(i - 1)!}\),易知 \(G(x) - H(x) = F(x)H(x)\),则 \(H(x) = \frac{G(x)}{F(x) + 1}\),多项式求逆可以解决。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define PII pair<ll, ll>
#define PPI pair<ll, PII>
#define pb push_back
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define rev(f, n) reverse(f, f + (n))
const int _G = 3, _i = 86583718, mod = 998244353;
const int N = 1.5e5 + 10, INF = 1e9;
ll qpow(ll a, ll k = mod - 2) {
ll res = 1;
while (k) {
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
const int invG = qpow(_G), inv2 = qpow(2);
int rev[N << 1], rev_len;
void rev_init(int n) {
if (rev_len == n) return;
for (int i = 0; i < n; i ++ ) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
rev_len = n;
}
void NTT(int *g, int op, int n) {
rev_init(n);
static ull f[N << 1], Gk[N << 1] = {1};
for (int i = 0; i < n; i ++ ) f[i] = g[rev[i]];
for (int k = 1; k < n; k <<= 1) {
int G1 = qpow(~op ? _G : invG, (mod - 1) / (k << 1));
for (int i = 1; i < k; i ++ ) Gk[i] = Gk[i - 1] * G1 % mod;
for (int i = 0; i < n; i += k << 1) {
for (int j = 0; j < k; j ++ ) {
int tmp = Gk[j] * f[i | j | k] % mod;
f[i | j | k] = f[i | j] + mod - tmp;
f[i | j] += tmp;
}
}
if (k == (1 << 10)) for (int i = 0; i < n; i ++ ) f[i] %= mod;
}
if (~op) for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod;
else {
int invn = qpow(n);
for (int i = 0; i < n; i ++ ) g[i] = f[i] % mod * invn % mod;
}
}
void px(int *f, int *g, int n) {
for (int i = 0; i < n; i ++ ) f[i] = 1ll * f[i] * g[i] % mod;
}
void covolution(int *f, int *g, int len, int lim) {
static int sav[N << 1];
int n; for (n = 1; n < len << 1; n <<= 1);
clr(sav, n); cpy(sav, g, n);
NTT(sav, 1, n); NTT(f, 1, n);
px(f, sav, n); NTT(f, -1, n);
clr(f + lim, n - lim), clr(sav, n);
}
int inv[N << 1], inv_len;
void inv_init(int n) {
if (n <= inv_len) return;
if (!inv_len) inv[0] = inv[1] = 1, inv_len = 1;
for (int i = inv_len + 1; i <= n; i ++ ) inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
inv_len = n;
}
void Poly_d(int *f, int n) {
for (int i = 1; i < n; i ++ ) f[i - 1] = 1ll * f[i] * i % mod;
f[n - 1] = 0;
}
void Poly_int(int *f, int n) {
for (int i = n; i; i -- ) f[i] = 1ll * f[i - 1] * inv[i] % mod;
f[0] = 0;
}
void Poly_inv(int *f, int m) {
static int b1[N << 1], b2[N << 1], sav[N << 1];
int n; for (n = 1; n < m; n <<= 1);
b1[0] = qpow(f[0]);
for (int len = 2; len <= n; len <<= 1) {
cpy(b2, b1, len >> 1), cpy(sav, f, len);
NTT(b2, 1, len), NTT(sav, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
clr(b2, len >> 1); cpy(sav, b1, len);
NTT(sav, 1, len); NTT(b2, 1, len);
px(b2, sav, len); NTT(b2, -1, len);
for (int i = len >> 1; i < len; i ++ ) b1[i] = (2ll * b1[i] - b2[i] + mod) % mod;
}
cpy(f, b1, m), clr(b1, n), clr(b2, n), clr(sav, n);
}
void Poly_ln(int *f, int n) {
static int sav[N << 1];
inv_init(n); cpy(sav, f, n);
Poly_d(sav, n); Poly_inv(f, n);
covolution(f, sav, n, n); Poly_int(f, n - 1);
clr(sav, n);
}
int n, F[N << 1], G[N << 1];
int frac[N], pw2[N], inv_frac[N];
void solve() {
cin >> n;
F[0] = frac[0] = inv_frac[0] = 1;
for (int i = 1; i <= n; i ++ ) frac[i] = 1ll * frac[i - 1] * i % mod, pw2[i] = qpow(2, 1ll * i * (i - 1) >> 1);
inv_frac[n] = qpow(frac[n]);
for (int i = n - 1; i; i -- ) inv_frac[i] = 1ll * inv_frac[i + 1] * (i + 1) % mod;
for (int i = 1; i <= n; i ++ ) {
F[i] = qpow(1ll * pw2[i] * frac[i] % mod);
if (i & 1) F[i] = (mod - F[i]) % mod;
}
Poly_inv(F, n + 1);
for (int i = 1; i <= n; i ++ ) F[i] = 1ll * F[i] * pw2[i] % mod;
for (int i = 1; i <= n; i ++ ) G[i] = 1ll * F[i] * i % mod;
Poly_inv(F, n + 1);
covolution(F, G, n + 1, n + 1);
for (int i = 1; i <= n; i ++ ) cout << 1ll * F[i] * frac[i - 1] % mod << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}

浙公网安备 33010602011771号