BZOJ4671 异或图
设 \(f_i\) 为钦定 \(i\) 个集合两两无边的方案数(即钦定有 \(i\) 个连通块的方案数),设 \(g_i\) 为恰好有 \(i\) 个连通块的方案数,则:
\[f_i = \sum\limits_{j = i}^n {j \brace i} g_j
\]
根据斯特林反演,得:
\[g_i = \sum\limits_{j = i}^n (-1)^{j - i} \begin{bmatrix} j \\ i \end{bmatrix} f_j
\]
所以:
\[ans = g_1 = \sum\limits_{i = 1}^n (-1)^{i - 1} (i - 1)! f_i
\]
问题转化为求 \(f_i\)。
发现 \(n\) 很小,考虑直接枚举哪些点被分到了一个集合(这里的枚举量是贝尔数级别的),设有 \(m\) 个集合。那么每一条两端所属集合不同的边都必须被选偶数次。
设编号为 \(b_{i, 1}, b_{i, 2}, \ldots, b_{i, k}\) 的图包含第 \(i\) 条两端所属集合不同的边,\(a_i\) 为第 \(i\) 个图是否在子集中,那么会得到一个形如 \(\forall i, a_{b_{i, 1}} \oplus a_{b_{i, 2}} \oplus \cdots \oplus a_{b_{i, k}} = 0\) 的异或方程组,高斯消元求其自由元个数 \(c\),那么这种划分方案对 \(f_m\) 有 \(2^c\) 的贡献。
总时间复杂度 \(O(B_n n^2 (s + n^2))\)。
code
// Problem: P10591 BZOJ4671 异或图
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P10591
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
ll n, m, K, id[99], f[99], b[59], tot;
bool a[99][19][19];
char s[999];
inline ll calc() {
ll cnt = m;
for (int i = 1, cur = 1; i <= tot && cur <= m; ++cur) {
for (int j = i; j <= tot; ++j) {
if (b[j] & (1LL << cur)) {
swap(b[i], b[j]);
break;
}
}
if ((~b[i]) & (1LL << cur)) {
continue;
}
--cnt;
for (int j = i + 1; j <= tot; ++j) {
if (b[j] & (1LL << cur)) {
b[j] ^= b[i];
}
}
++i;
}
return cnt;
}
void dfs(int d) {
if (d > n) {
tot = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (id[i] == id[j]) {
continue;
}
b[++tot] = 0;
for (int k = 1; k <= m; ++k) {
if (a[k][i][j]) {
b[tot] |= (1LL << k);
}
}
}
}
f[K] += (1LL << calc());
return;
}
id[d] = ++K;
dfs(d + 1);
--K;
for (int i = 1; i <= K; ++i) {
id[d] = i;
dfs(d + 1);
}
}
void solve() {
scanf("%lld", &m);
for (int i = 1; i <= m; ++i) {
scanf("%s", s + 1);
int len = strlen(s + 1);
for (int j = 1;; ++j) {
if (j * (j - 1) / 2 == len) {
n = j;
break;
}
}
int tot = 0;
for (int j = 1; j <= n; ++j) {
for (int k = j + 1; k <= n; ++k) {
a[i][j][k] = s[++tot] - '0';
}
}
}
dfs(1);
ll ans = 0, p = 1;
for (int i = 1; i <= n; ++i) {
ans += ((i & 1) ? 1 : -1) * p * f[i];
p *= i;
}
printf("%lld\n", ans);
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}