线性基学习笔记

\(\text{线性基学习笔记}\)

定义

OI 中线性基一般指异或空间线性基。具体地,对于一个数集 \(S\),我们要找到一个集合 \(T\) 使得 \(S\) 内数的异或集合和 \(T\) 内数的异或集合相同,且 \(T\) 的大小最小。

构建

一般使用贪心法构造线性基。设线性基数组为 \(p\),当前插入的数为 \(x\),那么我们每位从高到低枚举 \(p\),如果当前处在的第 \(i\) 位上 \(x\) 的值为 \(1\),那么 \(x\leftarrow x\oplus p_i\);如果 \(p_i=0\),那么 \(p_i\leftarrow x\)

这样做的正确性基于保证 \(x\) 可以被表示的前提下尽可能低地扩展了二进制位。这里给出构建的代码:

void insert(int x) {
	for (int i = M - 1; ~i; --i)
		if ((x >> i) & 1) {
			if (!p[i]) {
				p[i] = x;
				return;
			}
			x ^= p[i];
		}
}

查询

一般的应用是求最大/最小异或和。贪心地,从高往低尽量选择能使答案变大/小的位即可。需要留意的是,求最小异或和时,如果上文插入过程中有 \(x\) 未被插入,那么显然 \(x\) 本身已经可被表示,也就是实际上原数集有异或和位 \(0\) 的子集,特判一下即可。

给出求最大异或和的代码:

int query() {
	int ans = 0;
	for (int i = M - 1; ~i; --i)
		ans = max(ans, ans ^ p[i]);
	return ans;
}

合并

就是暴力将一个线性基插到另一个线性基里。代码不放了。

求排名

求去重的排名

略微思考一下不难发现对于 \(p_i\),我们实际上只关心其有没有值。如果有,那么意味着 \(i\) 这一位可以选择是否异或来选择成为 \(0/1\),反之选择是固定的。那么我们从小往大枚举每一位,假使当前枚举到第 \(k\) 个有值的线性基,如果此时 \(x\) 同样有值,排名会加上 \(2^{k-1}\) 来表示这一位为 \(0\) 时的情形,反之不予操作即可。

代码:

int nrk(int x) {
	int ans = 0, sum = 1;
	for (int i = 0; i < M; i++) {
		if (!p[i]) continue;
		if ((x >> i) & 1) ans += sum;
		sum <<= 1;
	}
	return ans;
}

求不去重的排名

考虑在线性基中有 \(cnt\) 个数插入失败,这意味着这 \(cnt\) 个数任意组合选择都能和线性基内的一些数异或起来得到 \(0\)。那么任选的方案有 \(2^{cnt}\) 种,于是答案就是 \((rnk-1)\times2^{cnt}+1\)

图上异或问题

对于一些图上异或问题,可以采用线性基解决。

例:求无向图上两点间的异或最长路。

能够发现的性质是任意一条路径可以由一条 \(s\to t\) 的链和若干简单环组合而成。于是把环挂到线性基上求异或最大值即可。

posted @ 2025-03-15 18:28  长安19路  阅读(35)  评论(4)    收藏  举报