数学狗都不学
线性代数
线性基
-
线性空间(人话):一组向量通过线性组合得到的向量所形成的空间。
-
线性无关(人话):在一组线性无关的向量中,不能用多个向量通过线性组合表示出另一个向量。
-
极大线性无关组:在一组向量中选一个最大的子集,这个子集中的向量线性无关。
-
基:称线性空间 \(V\) 的一个极大线性无关组为 \(V\) 的一组 线性基,简称基。
OI 中常用的基是基于 Xor 运算的,首先一个数 \(a_i\) 可以看做一个值为 \(0/1\) 的列向量 \(\overrightarrow{a_i} = \begin{bmatrix}b_{i,1} \\\vdots \\b_{i,k}\end{bmatrix}\),那么一组数字的异或和就可以表示为一组列向量的线性组合:
所以最终异或和向量 \(\overrightarrow{S}\) 一定位于这些向量所张成的线性空间中,也就是由这一组向量的基的线性组合而构成。
问题转化为找一组向量的基。
加入:法一 高斯消元
对这组向量构成矩阵的转置矩阵 \(A^T\) 进行消元,最后得到的上三角矩阵转置回来就是一组基。
加入:法二 增量法
加入一个数 = 在高斯消元中加入一个行向量,从上往下暴力枚举行向量,如果新向量在这一行的主元列有值,接下来如果当前行没有主元,它就成为主元,反之就把两个向量 Xor 一下,消元。
单次插入 \(\mathcal{O}(\log n)\),代码如下:
点击查看代码
inline void add(int x) {
for (int i = 29; i >= 0; --i) {
if (!(x & (1 << i))) continue;
if (!basic[i]) return void(basic[i] = x);
else x ^= basic[i];
}
}
删除操作
三种做法:
法一 线段树分治
- 优点:不动脑子。
- 缺点:多个 \(\log\)。
法二 离线时间戳
类比 LCT 维护无向图连通性:贪心选择时间戳靠后的替换时间戳靠前的,这样子删除的时候一定是真正删除,不会出现有不在基中的数替代它成为新的基的情况。
每一个基维护一个加入时异或过它的基的集合,找到其中最后的一个,用它向上异或,抵消掉当前删除的基的影响即可。
- 优点:快,好写。
- 缺点:离线。
合并操作
合并就启发式合并就可以了,将小线性基往大线性基中暴力插入,\(O(\log^2 V)\)。
事实上,由于线性基大小为 \(O(\log V)\),不用启发式也是可以的。
应用
异或最大值
按位贪心,当前这一位异或后会更大就异或。
第 \(k\) 大异或和
引入一个技巧,类比高斯约当消元法,将基消元为每一个主元列都只有主元行有值的矩阵。
此时 \(k\) 大异或和,按位处理,对于第 \(i\) 位,如果当前位 \(k\) 的值为 \(1\),则对应当前主元选择,反之对应当前主元不选择。这显然是正确的。
事实上,不用消元按位讨论也是可以的。
给定异或和排名
每一个异或和都会出现 \(2^{n-|S|}\) 次,\(|S|\) 是线性基大小。
按位处理,如果当前位有主元而且查询异或和 \(x\) 在当前位值为 \(1\),也就意味着如果这一位选择小的情况,那么这一位往后的所有有主元的位任意选择都为比 \(x\) 小,产生 \(2^{rnk_i-1}\) 的贡献,代码如下:
REP(i, 0, 29) pre[i] = pre[i - 1] + (basic[i] != 0);
PER(i, 29, 0) if (basic[i] && (Q & (1 << i))) (Ans += qpow(2ll, pre[i] - 1)) %= P;
【例】LG4869 albus就是要第一个出场
区间线性基
使用线段树维护,启发式合并即可。
重点提一个技巧:当你有区间异或操作的时候,线性基不支持整体异或一个数,考虑差分转单修。
此时设差分数组为 \(b_i = a_i \oplus a_{i-1}\),显然有:\(a_i = b_1 \oplus b_2 \dots b_i\)。
那么对于一组 \(a_{p_1} \oplus a_{p_2} \oplus \dots \oplus a_{p_n}\),我们把每个 \(a_i\) 都展开成 \(a_l \oplus b_{[l+1,i]}\) 的形式,暴力相消,最后一定等价于在 \(a_l\) 和 \(b_{[l+1,r]}\) 中选择一组数的异或和。此时可以使用线段树维护线性基,快速查询。
【例】LG11620 [Ynoi Easy Round 2025] TEST_34