线性基学习笔记
1.线性基的本质
线性基的本质就是空间上的一组向量可以用线性变换表示出所有向量。
OI 中常见的主要是异或线性基,就是用若干个数表示一组数的异或和的空间。
2. 异或线性基
2.1 插入
线性基的构建本质上类似高斯消元。
我们设 \(b_i\) 表示主元是 \(i\) 的数,对于一个线性基加入一个数 \(x\),我们从高到低遍历,假设当前是 \(j\):
-
如果 \(b_j\) 暂时没有,我们就让 \(b_j=x\) 并终止。
-
如果有,我们就让 \(b_j \oplus x \to x\),然后继续。
我们发现,这样构建完后线性基大小是 \(O(\log V)\) 的,并且 \(b_i\) 的前 \(i\) 位都是 \(0\)。
其实就是一个上三角的矩阵。
for (int j = 50; j >= 0; j--)
if ((x >> j) & 1)
if (b[j]) //主元确定
x ^= b[j];
else {
b[j] = x;
break;
}
2.2 消元
我们可以将一个线性基所有 \(b_j\) 存在的位置视作变量,剩下的位置都是值。
那么我们现在 \(b_j\) 其实是若干个变量,我们希望每个 \(b_j\) 只有一个变量。
为了方便后续操作,我们从低到高位消元,使得每个 \(b_j\) 的变量只有一个。
for (int i = 0; i <= 50; i++)
for (int j = i + 1; j <= 50; j++)
if ((b[j] >> i) & 1)
b[j] ^= b[i];
2.3 应用
P3812 【模板】线性基
求最大异或和,我们建出线性基并且消元,我们发现答案就是所有线性基中数的异或和。
LOJ114 k 大异或和
我们发现更高位的线性基对于更低位的是压倒性胜利,所以我们可以按照字典序的思路一位一位取。这个过程就相当于取走线性基中所有 \(K\) 二进制位下是 1 的数,注意如果不存在是不计入的。
P4869 albus就是要第一个出场
我们假设总共 \(n\) 个数,线性基中 \(k\) 个数,则每个异或值都出现了 \(2^{n-k}\) 次。
其实不难理解,所有不在线性基中的数都可以被其中的子集表示,那么每次相当于给 \([k]\) 的子集变成复制一份加上一份中每一个都和一个集合 \(S\) 异或一下,显然后面是一个一一映射,所以出现次数是相等的。
P4151 [WC2011] 最大XOR和路径
神题。注意到如果我们走到一半跑去一个环玩一圈再回来,那么贡献只有环的异或值。所以我们只用找到所有环的异或和的线性基,在其中找到一个子集和 \(d_n\) 的异或最大即可。
找与某个数异或最大的子集很简单,每次判断选上当前的会不会更大即可。
P3292 [SCOI2016] 幸运数字
我们用倍增维护线性基,然后查询时把 \(O(\log n)\) 个线性基合并即可。合并两个线性基的时间复杂度是 \(O(\log^V)\) 的,所以时间复杂度 \(O(n \log n \log V + q \log n \log^2 V)\)。
3.前缀线性基
3.1 过期时间
我们假设需要知道一个序列的区间的线性基,我们应该怎么做。
用维护最小生成树的思想,对于每个元素有一个时间戳,如果现在不存在,就直接加入。否则就要在所有异或起来等于其的元素中删去过期时间最早的。
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e5 + 5;
const int B = 22;
struct LB {
int b[B] = {0};
int pos[B] = {0};
LB () {}
void insrt(int x, int i) {
for (int j = 20; j >= 0; j--)
if (x >> j & 1) {
if (!b[j]) {
b[j] = x;
pos[j] = i;
break;
}
else if (pos[j] < i)
swap(pos[j], i), swap(b[j], x);
x ^= b[j];
}
}
int qry(int l) {
int ans = 0;
for (int j = 20; j >= 0; j--)
if (pos[j] >= l)
ans = max(ans, (ans ^ b[j]));
return ans;
}
} a[N];
int n, q;
int c[N] = {0};
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
a[i] = a[i - 1];
a[i].insrt(c[i], i);
}
scanf("%d", &q);
for (int i = 1, l, r; i <= q; i++) {
scanf("%d%d", &l, &r);
printf("%d\n", a[r].qry(l));
}
return 0;
}