【学习笔记】线性基
线性基学习笔记
本文大量借鉴了 Menci 大佬的笔记 -- 线性基学习笔记
异或及其线性空间
对于异或运算,我们规定某正整数有限集 \(S\) 的 异或和 为:
特别地,规定 \(\mathrm{XOR}(\{a\}) = a\).
规定 \(S\) 张成 的线性空间为 \(S\) 的所有非空子集的异或和构成的集合。记
注意 \(S\) 本身也属于其张成的线性空间.
如果 \(S\) 的任意一个子集的异或和都不为 \(0\),则称 \(S\) 是 线性无关;反之,是 线性相关 的。等价地,\(S\) 线性无关当且仅当其任意一个子集的异或和都不与任意一个集合中的元素相等(等价是因为 $a \ \mathrm{xor} \ b = c \Leftrightarrow a \ \mathrm{xor} \ b \ \mathrm{xor} \ c = 0 $).
如果一个 线性无关 集 \(B\) 张成的线性空间 \(\mathrm{span} (B)\) 包含另一个线性空间 \(V\),且 \(B\) 是满足这个性质中 大小最小 的,则称 \(B\) 为 \(V\) 的一组 线性基 ,简称 基.
形式化地,\(B\) 是 \(V\) 的一组 线性基,当且仅当:
- \(V \subseteq \mathrm{span}(B)\);
- \(\forall T \subseteq B, \ \mathrm{XOR}(T) \ne 0\);
- \(\forall D\) 满足性质 1,2,\(|B| \geqslant |D|\).
对于已知的某个集合 \(V\),通过求解其线性基,我们能够得知:
- 其所有子集异或和的最大值;
- 其左右子集异或和的第 k 大、第 k 小;
- 判断某个数能否表示为其某个子集的异或和;
- 求某个数在其所有子集异或和中的排名;
- 合并不同集合,动态维护集合并的上述内容。
构造线性基
在实际过程中,我们并不要求线性基的大小是最小的,只需要让他足够小即可。所以我们考虑按位构造线性基,这样对于
一般的 64 位整数,线性基的的大小不会大于 64,体现在时间复杂度上就是 \(O(\log N)\) 的。
让我们感性的理解一下:尽管集合 \(V\) 中有大量的元素,但他们的任意组合异或和不会超过一个 64 位整数,所以我们很有可能找到一组轶不大的基,使得满足能生成,且 仅能生成 \(V\) 的张成空间的元素。
// 线性基的构造
typedef long long ll; // 之后简记 ll 为 long long
struct LinearBasis {
ll ai[65];
LinearBasis()
{
fill(ai, ai + 63, 0);
}
ll operator [](const int &x) const {
return ai[x];
}
void insert(ll x);
void build(ll *V, int n) // 建立线性基
ll qmax(); // 查询最大值
ll qkth(ll k); // 查询第 k 大
ll qin(ll x); // 查询能否被表示
void mergeTO(const LinearBasis &M); // 被合并
LinearBasis merge(const LinearBasis &M, const LinearBasis &N); // 合并两者
}
现在我们已知数集 \(V\),要求解其线性基。
对此,我们构造 \(B = \{a_1, a_2, \cdots, a_L\}\),其中 \(L\) 为已知数中二进制的最高位位数,即 \(\lfloor \log _2 \max \{V\} \rfloor\). 我们对任意 \(a_i\) 提出以下要求:
- \(a_i = 0\),当且仅当 \(V\) 中任意元素的二进制第 \(i\) 位上均为 \(0\)。
- \(a_i \ne 0\),
- \(a_i\) 的第 \(i\) 位为 \(1\);
- \(a_i\) 的高于 \(i\) 位都为 \(0\);
- \(\forall j < i\) 且 \(a_j \ne 0\),\(a_i\) 的第 \(j\) 位上为 \(0\)。即如果某个元素最高位是 \(1\),那么他就控制着其他元素在这一位上都为 \(0\)。下文我们称其满足这个性质的位为 控制位。
具体地,我们逐个考虑 \(x \in V\),每次从高位到低位逐位考察 \(x\),对于其中的第 \(j\) 位:
- \(x\) 的 \(j\) 位为 \(0\):该位无需考虑。
- \(x\) 的 \(j\) 位为 \(0\):
- \(a_j \ne 0\),则这一位已由 \(a_j\) 控制,只需消去它的影响。具体地,令 \(x \leftarrow a_j\);
- \(a_j = 0\),则直接插入 \(x\),即令 \(a_j \leftarrow x\)。但还需要稍加改动以使其满足上述要求 2:
- 对于 \(\forall k \in [0, j),\ a_k \ne 0\),要保证 \(a_j\) 在这些被控制的低位上均为 \(0\)。具体地,令 \(a_j \leftarrow a_j\ \mathrm{xor}\ a_k\);
- 对于 $\forall k \in (j, L] $ 且 \(a_k\) 在第 \(j\) 位上为 \(1\), 要保证 \(a_k\) 在被 \(a_j\) 控制的第 \(j\) 位上为 \(0\)。具体地,令 \(a_k \leftarrow a_k\ \mathrm{xor}\ a_j\)。
接下来我们分析正确性:
观察: 将 \(S\) 中的某个元素 \(x\) 异或上集合中若干其他元素后,\(\mathrm{span}(S)\) 不变. 换言之,集合内部相互异或不会改变其异或和张成的空间。这是因为依据 \(a\ \mathrm{xor}\ b\ \mathrm{xor}\ b = a\),内部异或皆可被消去。
- 对于 \(V\) 中元素能否都被 \(B\) 中子集异或和表示:
- 如果 \(x \in V\) 最后未被插入,则其一定被某些 \(a_i\) 异或削减为了 \(0\),故一定能表示为若干 \(a_i\) 的异或和(根据上述过程)。
- 如果 \(x\) 被插入了,那么即使经过多次的内部异或,根据上面的观察,不会改变张成的空间,即 \(\mathrm{span}(V)\)
- 对于是否可能异或为 \(0\):
任意 \(a_i\) 都有自己的独有控制位,这些位异或必定为 \(1\),不可能等于 \(0\)。
// 线性基的插入与构造操作(构造其实就是逐个插入元素)
void LinearBasis::insert(ll x)
{
for(int j = 63; j >= 0; j--) {
if(!(x & (1ll << j))) continue;
if(ai[j])
x ^= ai[j];
else {
ai[j] = x;
for(int k = 0; k < j; k++)
if(ai[j] & (1ll << k)) ai[j] ^= ai[k];
for(int k = j + 1; k <= 63; k++)
if(ai[k] & (1ll << j)) ai[k] ^= ai[j];
return;
}
}
}
void LinearBasis::build(ll *V, int n)
{
for(int i = 1; i <= n; i++)
insert(V[i]);
}
还有一种构造更加简单的线性基,性质较其差些(即插入时省去了更新其他位的操作),但在求第 k 位的操作时还需要重构这个线性基,使其符合上面的性质。下文我们还是讨论上文所说的线性基。
线性基的相关操作
最大值
问题: 给出一个集合 \(V\),求出其自给异或和的最大值。
为了展示上述线性基良好性质,我们展示一个例子:
容易发现,保证所有控制位(满足 \(a_i\) 的第 \(i\) 位为 \(1\) 的)都异或起来是最优的。假设有一个控制位没有取,那么最多只能额外做出其更低的非控制位的贡献,这显然是不如高位控制位做出的贡献的,所以没有必要。
代码只需将所有基向量(即基中的元素)异或起来即可。
// 取子集异或最大值
ll LinearBasis::qmax()
{
ll ret = 0;
for(int i = 0; i <= 63; i++)
ret ^= ai[i];
return ret;
}
第 k 大
问题: 给出一个集合 \(V\), 求出其子集异或和中第 k 小的数。
对于那些线性基中为 0 的元素,我们不需要考虑。而对于那些不为 0 的元素,我们接续上述取最大值的思考方式,会发现非控制位是对于排名没有贡献的。当我们去掉所有的非控制位和 0 向量,我们会发现剩余的部分恰是 2 的不同次幂,那么排名只需按照二进制取基向量即可。
// 取子集异或第 k 小
ll LinearBasis::qkth(ll k)
{
ll ans = 0;
for(int i = 0; i <= 63; i++)
{
if(!ai[i]) continue;
if(k & 1) ans ^= ai[i];
k >>= 1;
}
return ans;
}
合并
问题: 给出两个线性基,要合并他们。
只需要依次插入线性基的元素即可。
void LinearBasis::mergeTO(const LinearBasis &M)
{
for(int i = 0; i <= 63; i++)
insert(M[i]);
}
LinearBasis LinearBasis::merge(const LinearBasis &M, const LinearBasis &N)
{
LinearBasis ret = M;
for(int i = 0; i <= 63; i++) {
ret.insert(M[i]);
ret.insert(N[i]);
}
return ret;
}