线性基用于处理"异或空间"的集合压缩。它能把一组数压缩成不超过位数大小的基向量,并保持所有可达异或值不变。
一、核心概念与术语
线性基的目标是:用一组线性无关的向量,表示原集合所有异或组合的结果集合。
- 向量:这里的向量就是一个整数的二进制表示。
- 基向量:在某一最高位上为 1 的代表元素。
- 维度:最多等于整数的位数,比如 64 位以内就是 64。
二、算法流程(How)
2.1 插入一个数
从高位到低位尝试消元,保持每一位最多一个基向量。
插入完成后满足一个关键不变量:若 basis[i] != 0,则 highestBit(basis[i]) = i,也就是 basis[i] 在所有更高位 > i 上都是 0。
- 对当前数
x,从最高位到最低位遍历。 - 若第
i位是 0,跳过。 - 若第
i位是 1:- 若
basis[i]为空,直接令basis[i] = x并结束。 - 若
basis[i]已存在,用x ^= basis[i]消掉第i位,然后继续向更低位处理。根据不变量highestBit(basis[i]) = i,basis[i]的所有更高位> i都是 0,所以这次异或不会影响已经处理过的更高位。
- 若
- 如果
x被消成 0,表示它可以由现有基表示,不入基。
注意:遍历到某一位 i 时如果 x 的第 i 位为 0,仍可能在更低位为 1,因此不能因为"当前位为 0"提前跳出;只有当 x 被消成 0 时才可以提前结束。
2.2 求最大异或值
从高位到低位贪心尝试提升答案。
- 初始化
ans = 0。 - 对每一位
i从高到低:- 若
ans ^ basis[i]更大,则ans ^= basis[i]。
- 若
- 最终
ans即为最大可达异或值。
三、为什么正确(Why)
- 插入过程等价于高斯消元,确保基向量在二进制空间中线性无关。
- 每个数都可以表示成这些基向量的异或组合,集合不变。
- 最大异或查询使用贪心,因为每一位是否置 1 的最优选择不会影响更高位的最优性。
四、复杂度
- 单次插入:O(logC)O(\log C)O(logC),其中 CCC 为数值范围。
- 单次查询最大异或:O(logC)O(\log C)O(logC)。
- 空间:O(logC)O(\log C)O(logC)。
五、例题:最大子集异或和
5.1 题目描述
给定一个数组,选择任意子集,使异或和最大,输出这个最大值。
5.2 解题过程
- 把数组所有元素依次插入线性基。
- 用线性基的最大异或查询得到答案。
5.3 具体例子
数组为 [6, 5, 10],二进制为 0110, 0101, 1010(按位从低到高编号为 0, 1, 2, 3)。
从空基开始,依次插入并记录 basis 的变化:
- 插入
6 (0110):最高位是 2,且basis[2]为空,所以basis[2] = 0110。 - 插入
5 (0101):最高位也是 2,basis[2]已存在。- 先消掉第 2 位:
x = 0101 ^ 0110 = 0011。 - 继续向低位走,
x的最高位变成 1,且basis[1]为空,所以basis[1] = 0011。
- 先消掉第 2 位:
- 插入
10 (1010):最高位是 3,且basis[3]为空,所以basis[3] = 1010。
最终得到:
basis[3] = 1010basis[2] = 0110basis[1] = 0011
然后用 maxXor 从高位到低位贪心:
- 初始
ans = 0000 - 处理第 3 位:
ans ^= basis[3] -> 1010 - 处理第 2 位:
1010 ^ 0110 = 1100更大,令ans = 1100 - 处理第 1 位:
1100 ^ 0011 = 1111更大,令ans = 1111
所以最大子集异或和为 1111 (15)(例如选择子集 {5, 10})。
六、Java 模板
java
class LinearBasis {
private final long[] basis;
LinearBasis(int bit) {
basis = new long[bit + 1];
}
void insert(long x) {
for (int i = basis.length - 1; i >= 0 && x != 0L; i--) {
if (((x >> i) & 1L) == 0L) {
continue;
}
if (basis[i] == 0L) {
basis[i] = x;
return;
}
x ^= basis[i];
}
}
long maxXor() {
long ans = 0L;
for (int i = basis.length - 1; i >= 0; i--) {
if ((ans ^ basis[i]) > ans) {
ans ^= basis[i];
}
}
return ans;
}
}
七、扩展:带位置的线性基(Codeforces 1100F)
7.0 整体思路
对每个询问 [L, R],我们要的是:只用下标在 [L, R] 内的数能拼出的最大异或值。
标准线性基不支持删除元素。关键观察是:如果我们手里有一个"前缀到 R"的线性基,并且每个基向量都带着来源位置 pos,那么查询时忽略 pos < L 的向量,就等价于把 1..L-1 从可选集合里删除。
7.0.1 数据结构
维护长度为 W 的数组(通常 W = 61),每一位至多一个基向量:
b[i]:最高位(pivot)为i的基向量值p[i]:该基向量来源下标
需要维持两个不变量:
- 形态不变量:若
b[i] != 0,则highestBit(b[i]) = i - 位置不变量:在所有能作为第
i位 pivot 的表示里,p[i]尽量大(更靠右优先)。所以插入的时候,如果有冲突,就用"更靠右"的向量交换。
7.1 预处理时的拷贝
basis[R] 只是一份长度为 W 的定长数组(W = 61),保存 (b[i], p[i])。
构建时:
basis[0]全 0。- 对
R = 1..n:先把basis[R-1]的W个槽位复制到basis[R],再把(a[R], R)插入到basis[R]。
拷贝在 Java 里就是对数组切片做 System.arraycopy。
7.2 查询时的限制
对 [L, R]:
- 取
basis[R]。 - 从高位到低位遍历
i。 - 只有当
p[i] >= L时,才允许用b[i]去更新答案。
7.3 正确性要点
- 形态不变量保证贪心不会影响更高位:
b[i]的更高位> i全为 0,因此选择b[i]只影响位<= i。 - 位置不变量保证过滤不会误杀区间内的表达能力:插入冲突时把"更靠右"的向量交换到更高的 pivot 上,使得能用
[L, R]形成的 pivot 尽量满足p[i] >= L。
7.3.1 复杂度
- 预处理:
n次插入,每次O(W),总计O(nW)。 - 查询:每次
O(W),总计O(qW)。 - 空间:存
n份基,每份W个槽位,O(nW)。
7.4 Java 实现(数组扁平化存储)
java
class RangeLinearBasis {
static final int W = 61;
final long[] base;
final int[] pos;
final int n;
RangeLinearBasis(long[] a) {
this.n = a.length - 1;
this.base = new long[(n + 1) * W];
this.pos = new int[(n + 1) * W];
for (int r = 1; r <= n; r++) {
int cur = r * W;
int prev = (r - 1) * W;
System.arraycopy(base, prev, base, cur, W);
System.arraycopy(pos, prev, pos, cur, W);
insert(cur, a[r], r);
}
}
private void insert(int off, long x, int pNew) {
for (int i = W - 1; i >= 0 && x != 0L; i--) {
if (((x >>> i) & 1L) == 0L) {
continue;
}
long bi = base[off + i];
if (bi == 0L) {
base[off + i] = x;
pos[off + i] = pNew;
return;
}
if (pos[off + i] < pNew) {
long tv = base[off + i];
base[off + i] = x;
x = tv;
int tp = pos[off + i];
pos[off + i] = pNew;
pNew = tp;
}
x ^= base[off + i];
}
}
long queryMaxXor(int L, int R) {
int off = R * W;
long ans = 0L;
for (int i = W - 1; i >= 0; i--) {
if (pos[off + i] < L) {
continue;
}
long cand = ans ^ base[off + i];
if (cand > ans) {
ans = cand;
}
}
return ans;
}
}
这段代码里:
System.arraycopy负责basis[R] = basis[R-1]的定长拷贝。queryMaxXor(L, R)里用pos[off+i] < L过滤,等价于只允许使用区间[L, R]的元素。