线性基
【定义】
知识:向量组、线性空间、线性相关性。
-
向量组:一组向量,各个维数相等。
-
线性表示:如果一个向量能被一个向量组中若干个向量乘上系数后相加得到,称这个向量可被此向量组线性表示。
(例如向量组 \(s=(v1,v2)\),\(tmp=2v1+3v2\),则 \(tmp\) 可被 \(s\) 线性表示)
-
线性无关:设向量组 \(v\),如果 \(v\) 中任意一个向量都不能被 \(v\) 中其他向量线性表示,则称 \(v\) 是线性无关的。
线性相关:向量组 \(v\) 不线性无关,就线性相关。
-
线性空间:记一个向量组 \(v=(v1,v2,\dots,vk)\),任取 \(k\) 个系数 \(a1\sim ak\in \mathbb{R}\),则 \(v\) 的 “生成子空间” 为 \(S=\{a1v1+a2v2+\dots+akvk\}\)。
说人话,用一个向量组能线性表示的所有向量,构成这个向量组的子空间。
而对于一个线性空间 \(V\) 的 “极大,线性无关,的向量组” 为 \(V\) 的线性基,简称基。
可以看出,一组基可以理解为一个线性空间的简化表示。
【性质】
对于一个维度为 \(n\) 的线性空间 \(V\):
-
\(V\) 中任意 \(n+1\) 个向量构成的向量组必然线性相关。
很好理解,类比有 \(n+1\) 个方程,必然有一个冗余。
-
\(V\) 中任意 \(n\) 个线性无关的向量是一组基。
因为 \(n+1\) 个已经不符合线性无关的要求了,所以 \(n\) 个线性无关已经是极大的了。
-
若 \(V\) 中任意向量可被向量组 \(a=(a1\sim an)\) 表示,则 \(a\) 是 \(V\) 的一组基。
感性理解:\(n\) 维向量,至少需要 \(n\) 个才能单独解开每一维,所以基至少需要 \(n\) 个向量;由上可知不可有 \(n+1\) 个向量。所以 \(n\) 个线性无关的向量必为基。
-
\(V\) 中任意取小于 \(n\) 个向量,一定可以再从 \(V\) 中选足够的向量,一起构成一组基。
【求法】
当给定一个向量组,想要求出它的子空间是很容易的:给每个向量乘一个系数加起来就行。
但是给定一个向量集合,求出它的基,貌似并不容易。
于是就产生了一个问题:给定一个线性空间,如何求出它的基?
(因为线性空间是无穷的,所以通过给出一个生成它的向量组表示)
【高斯消元】
考虑一下,线性基的本质就是去除了所有冗余向量。而这和消元有异曲同工之妙,我们可以使用高斯消元法来解。
要先保证高斯消元的操作:交换两行和消元是不会导致信息损失的,换而言之,交换两个向量的位置,和两个向量求和/差再替换,不会使线性空间变化。
而这是显然的,向量加减不会使线性空间变化,交换更不会。
下面进入正题。
先把向量组写成一个矩阵的形式,每一行代表一个向量,行数是向量组的大小。
对这个矩阵进行高斯消元,得到一个上三角矩阵:从左上角开始的一条斜对角线上都是 \(1\),其他的都是 \(0\)。(其实本来对角线上方的可以不是 \(0\),但是通过消元也可以除了对角线全部改成 \(0\))
(不明白的回这里看)
其实这条对角线上有可能是 \(0\)。但是如果是 \(0\),代表所有向量这一维都是 \(0\),那根本就没有存在的必要。如果不是 \(1\),可以用除法。
现在得到了一个上三角矩阵,这个矩阵的前 \(n\) 行就是一组基。
很巧妙吧?为什么呢?
设前 \(n\) 行的向量集合为 \(S\)。考虑 \(S\) 中任意多个向量 \(v1\sim vx\),不妨让它们按照在矩阵中行数大小从小到大排序。
此时 \(v1\) 在矩阵中是最靠上的,按照上三角矩阵的定义,有一维 \(v1\) 是 \(1\),\(v2\sim vx\) 都是 \(0\)。那 \(v1\) 必不可能通过 \(v2\sim vx\) 线性表示,证毕。
【贪心法求解】
初始线性基为空,考虑在不断加入新元素的过程中维护线性基。
每加入一个向量 \(vec\),枚举每一维。我们只需要判断 \(vec\) 是否存在无法被表示出来。
这里异或线性基和实数线性基有一点点不同。但都需要额外开一个数组 \(b\),初始 \(b\) 为空。
-
异或线性基。每加入一个数 \(x\),将其二进制分解从高位到低位扫。如果第 \(p\) 位是 \(1\),看一下 \(b[p]\) 是否存在:如果是,让 \(x\) 整个数异或上 \(b[p]\);否则,令 \(b[p]=x\)。
这个过程感性理解,每找到一个数,就用之前确定的线性基尽可能从高到低消位。实在消不了了,才把消完的加入。
因为消完的前面一定是连续若干个位被线性基消了,而且至少消掉了原来(线性基个数)个位,所以这么做一直是线性无关的。
void insert(unsigned long long x) { for (int i = 63; ~i; --i) { if (!(x >> i)) continue; if (!b[i]) { b[i] = x; break; } x ^= b[i]; } }
-
实数线性基。\(b\) 数组要记录高斯消元的对角线元素。
每加入一个向量 \(v\),循环它的每一维。如果这一维 \(b\) 上不是零,用贡献了这一维 \(b\) 的向量把 \(v\) 的这一维消成零(注意同时 \(v\) 这一维后面的维度也可能被一起消掉),类似高斯消元。如果 \(b\) 这一维是 \(0\),把现在剩下的 \(v\) 加入线性基。
for (int i = 1; i <= n; ++i) { //插入 v[i] for (int j = 1; j <= m; ++j) { if (v[i].a[j] == 0) continue; if (b[j] == 0) { //当前线性基消不了 b[j] = i; //把剩下的v[i]加入 break; } double mul = v[i].a[j] / v[b[j]].a[j]; for (int k = j; k <= m; ++k) v[i].a[k] -= mul * v[b[j]].a[k]; //类高斯消元 } }
【复杂度】
\(O(nm)\),\(n\) 为向量维数,\(m\) 为初始向量组大小。
【用途】
求出这个玩意,有什么用?
基很重要的一个性质,就是它能表示整个线性空间。而且它只有维度 \(n\) 个向量。
一般初始给出的向量组会很多(一般 \(1e5,5e5,1e6\)),但一般维度比较少(不超过 \(100\))。如果我们求出了线性基,就可以大大简化。(如果维度只有 \(20\),甚至可以 \(2^{20}\) 的搜索)
简化了向量组,无论是什么操作,都会好搞很多。
一个经典的例题:给定一些数,选若干个使异或和最大。
首先需要明确的一点是:异或可以看作不进位加法,它同时具有交换律和结合律,所以才能使用高斯消元搞线性基。不是什么向量运算都能搞线性基的。
把每一个数看作二进制数,每一位单独看作向量的一个维度,则题目可以看作 \(1e5\) 个 \(50\) 维向量。
对这个向量求线性基。最后会剩下不到 \(50\) 个 \(50\) 维向量。我们只需要用不到 \(50\) 个数就能表示出原来 \(1e5\) 个数表示的集合!!!
求完线性基,现在就是求异或最大值了。由于 \(2^t>2^{t-1}+\dots+1\) 的特殊性,一种想法是按照二进制最高位从大到小枚举,如果异或了可以让这一位更大就异或。
但是有上面的性质:除了对角线,其他都是 \(0\),(对角线可能为 \(0\) 但是不重要)直接把线性基全部异或起来,就是异或最大值了。
同时线性基还有一些同样经典的操作:
判断一个数是否能被异或出来:
求完线性基后把这个数做类似贪心法求解过程中的插入,如果这个数最后被消成 \(0\) 说明可以凑出来,否则说明不行。
求异或第 \(k\) 小:
求出线性基后,从高到低枚举每个线性基。因为线性基一定是对角线都是 \(1\),其余都是 \(0\),所以如果当前有 \(cnt\) 个元素,就有 \(2^{cnt}\) 种不同的异或值。
当我们枚举到第 \(p\) 个元素,判断一下 \(k\) 是否大于 \(2^{n-p}\),如果是,让 \(k-=2^{n-p}\),同时选择异或上这个数;如果不是,不异或这个数。枚举完所有元素,就得到了异或第 \(k\) 小。(有点像对 \(k\) 二进制拆位,如果这一位有就异或对应的数)