线性基
定义:给定一组向量,构成一个线性空间,如果我们能够找到一组线性无关的向量,使得这些向量的线性组合能够构成整个线性空间,则这一组向量成为该向量空间的线性基。
算法:计算线性基我们可以把一组向量排成矩阵,然后在矩阵上跑高斯消元法,消出来的非零行向量即为线性基,非零行向量数目为矩阵的行秩。
以下给出对于常规线性基的高斯消元代码:
void gauss() {
int dim = 0; // 行秩
for (int i = 1; i <= n; i++) {
int piv = dim + 1;
for (int j = dim + 1; j <= m; j++)
if (abs(a[j][i]) > abs(a[piv][i]))
= j;
piv if (abs(a[piv][i]) < EPS) continue; // 自由元
++;
dimfor (int j = 1; j <= n; j++)
(a[dim][j], a[piv][j]);
swapfor (int j = 1; j <= m; j++) {
if (j == dim) continue;
if (abs(a[j][i]) < EPS) continue;
double r = a[j][i] / a[dim][i];
for (int k = 1; k <= n; k++)
[j][k] -= r * a[dim][k];
a}
}
}
异或
简称 XOR,数学记号为 $$,C++ 中为 ^
,异或本质上是在模 \(2\) 意义下的加法运算。因此线性代数的很多算法也可以应用到异或这样的有限域上,异或的一个很重要的性质是相消性,即一个数异或两次等于没异或!(等同于乘 \(2\),在模 \(2\) 下就肯定没意义啦)。
异或的相消性很重要的一个应用是在图上使用 DFS 计算环上边的异或,我们只需要计算从 DFS 树根节点出发各个点到根上路径的异或和(记作 \(dis(u)\))。如果我们在 DFS 上找到返祖边 \(e = (u, v)\),环的异或就是 \(dis(u) \oplus dis(v) \oplus w(e)\)。我们可以看到这个运算中 \(u, v\) LCA 以上的部分都被抵消掉了,因此最后得到的就是整个环的异或和。如果求图上路径异或和的话我们可以用这样的方法搞出神奇的效果。
异或线性基
如何求出异或意义下的线性基呢?朴素的高斯消元肯定是可以的,但是对于异或我们首先可以压位减少常数,然后对于异或线性基我们还有一种求法,对于一个数 \(x\),我们可以这样把 \(x\) 插入一组线性基:
- 找到 \(x\) 的最高二进制位,记作 \(i\)。
- 查看第 \(i\) 位的线性基 \(p[i]\) 是否被插入。
- 如果没有被插入,插入第 \(i\) 位的线性基,结束。
- 如果已经被插入了,令 \(x = x \oplus p[i]\)。
正确性证明:如果当前位没被插入则插入操作显然正确,如果被插入,则 \(x \oplus p[i]\) 不仅消除了第 \(i\) 位,同时可以证明如果 \(x \oplus p[i]\) 可以被线性基表出,则 \(x\) 一定也可以被线性基表出。
一个二进制数在被插入线性基的过程中如果失败了,那么它就会变成 \(0\),暗示它可以由我们现有的线性基表出。
代码:
bool insert(int x) {
for (int i = 30; i >= 0; i--) { // 最高位数
if ((x >> i) & 1) {
if (!p[i]) {
[i] = x;
preturn true;
} else
^= p[i];
x }
}
return false;
}