P10591 BZOJ4671 异或图 题解

\(\text{P10591 BZOJ4671 异或图 题解}\)

这个题有点屌。

考虑直接去计算连通图的个数是困难的,倘若能够钦定一些不连通的边就会好算一些。首先我们先设出 \(f_i\) 表示恰好形成 \(i\) 个连通块的方案数,则 \(ans=f_1\)。于是可以设出 \(g_i\) 表示图至少被分割为 \(i\) 个连通块的方案数。这里至少的含义是一定能够钦定有 \(i\) 个连通块是不连通的,而不去管每个连通块内部是否真正连通,换句话说,我们并不关心每个连通块内部的连边情形。现在需要找到 \(f\)\(g\) 之间的关系。容易得到的是 \(g_i=\sum_{j=i}^n{j\brace i}f_j\),那么由斯特林反演可以得到 \(f_i=\sum_{j=i}^n{j\brack i}(-1)^{j-i}g_j\)。那么问题转化为了求 \(g_j\)

那么我们暴力搜索集合的构成,容易观察得出每一个状态的的情形呈现出一个异或线性方程组的形式,暴力高斯消元显然是可求的。然而我们事实上只需要求出这个方程组自由元的个数,那么用线性基去维护就可以了。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 12, M = 66;
int n, s;

struct FUCK {
	int p[M];
	void clr() {
		memset(p, 0, sizeof p);
	}
	int ins(int x) {
		for (int i = M - 1; ~i; --i)
			if ((x >> i) & 1) {
				if (!p[i]) {
					p[i] = x;
					return 0;
				}
				x ^= p[i];
			}
		return 1;
	}
} FK;

int bg[M];
int c[N], ans[M];
void dfs(int x, int num) {
	if (x > n) {
		int sta = 0, t = 0;
		for (int i = 1; i <= n; i++)
			for (int j = i + 1; j <= n; j++) {
				if (c[i] != c[j]) sta |= (1ll << t);
				++t;
			}
		FK.clr();
		int cnt = 0;
		for (int i = 1; i <= s; i++) cnt += FK.ins(bg[i] & sta);
		ans[num] += (1ll << cnt);
		return;
	}
	for (int i = 1; i <= num; i++) c[x] = i, dfs(x + 1, num);
	c[x] = num + 1;
	dfs(x + 1, num + 1);
}

int fac[M];
signed main() {
	fac[0] = 1;
	for (int i = 1; i < M; i++) fac[i] = fac[i - 1] * i;
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> s;
	for (int x = 1; x <= s; x++) {
		string t;
		cin >> t;
		int l = t.size();
		if (!n) {
			for (int i = 1; i * (i - 1) / 2 <= l; i++) n = i;
		}
		int p = 0;
		for (int i = 1; i <= n; i++)
			for (int j = i + 1; j <= n; j++) {
				if (t[p] == '1') bg[x] |= (1ll << p);
				++p;
			}
	}
	dfs(1, 0);
	int res = 0;
	for (int i = 1; i <= n; i++)
		res += ((i & 1) ? 1 : -1) * fac[i - 1] * ans[i];
	cout << res << '\n';
	return 0;
}

题目的关键是想到把连通图转化为钦定边不连通的形式来处理。

posted @ 2025-04-13 17:54  长安19路  阅读(32)  评论(0)    收藏  举报