线性基分成两种,一种是异或线性基,一种是实数线性基。
异或线性基
定义
线性基有着一个奇怪的名字,异或线性基也有着奇怪的定义。
那么异或线性基是啥东西呢?
直接搬定义:线性基是一个数的集合(这就不只是异或线性基了!),并且每一个序列都至少拥有一个线性基。
而且对于异或线性基最重要的:取异或线性基中若干个数异或起来可以得到原序列中的任何一个数。这个条件不是什么时候都可以满足,于是我们在之后会详细讲异或线性基的构造方案,以满足这个定义和后面的三大性质。
异或线性基有着一些奇妙的性质,或者不如说是构造出来的线性基必须要满足的条件:
-
原序列里面的任意一个数都可以通过异或线性基中的一些数异或得到(其实这个东西就是它的定义反着说了一下)
-
异或线性基里面的任意一些数异或起来都不能是得到了 0 0 0。
-
线性基里面的数的个数唯一,并且在保持性质 1 1 1 的前提下,数的个数是最少的。
注意,线性基里面的元素可以不是序列中的元素。
很奇怪,不是吗?后面再讲思路的时候,这个奇怪特点就会展露出它的头角。
举个例子,假设有一个序列是 3 , 1 , 7 , 6 , 5 3,1,7,6,5 3,1,7,6,5,显然我们可以使用二进制的结论得到线性基可以为 1 , 2 , 4 1,2,4 1,2,4。
假设我们不会快速计算
xor
\text{xor}
xor,而如果这个时候需要计算
3
xor
7
xor
5
3 \ \text{xor} \ 7 \ \text{xor} \ 5
3 xor 7 xor 5,就可以通过代入来得到答案为
(
1
xor
2
)
xor
(
1
xor
2
xor
4
)
xor
(
1
xor
4
)
(1 \ \text{xor} \ 2) \ \text{xor} \ (1 \ \text{xor} \ 2 \ \text{xor}\ 4) \ \text{xor} \ (1 \ \text{xor} \ 4)
(1 xor 2) xor (1 xor 2 xor 4) xor (1 xor 4),两两抵消得到了
1
1
1。
思路
我们先来做一个引入题目(也是异或线性基的板子题):给你一些数,问用它们选一些异或起来,问最大值是多少。
很容易想到使用 01Trie,因为 01trie 解决这种问题没有更好的选择了。但是 01trie 只能支持两个数的异或最大,多个数的显然没法做。
于是就需要请出我们的异或线性基了。这道题就是线性基的板子题目,所以会在讲代码的时候一起把它讲掉。
由于这东西实在是比较难讲,所以这里采用对着模板的代码来讲解。
线性基的每一位维护的都是二进制的最高位是这一位的数。很容易想到为了使线性基的大小最小,以一个二进制位为最高位的数最多只有一个。
构造
先上代码:
void add(int x) {
for (int i = 60; i >= 0; i--) {
if ((x & (1ll << i)) == 0)
continue;
//如果该位没有 1,则不会成为对应线性基
else if (basis[i] > 0)
x ^= basis[i];
//如果该位有基,那么去掉 x 对应的 1
else {
basis[i] = x;
break;
}
//如果该位没有基,那么 x 成为基
}
}//在原序列中加入一个新的元素,并考虑是否会有新的基
如代码所示, a d d add add 函数的用途顾名思义,就是尝试往线性基里面加一个数。其中 x x x 表示需要使用线性基表示出来的数,但是因为线性基里面可以出现不是序列中的元素的值,所以再途中需要对 x x x 进行一下改动。
代码很简单,感性理解一下,这样应该线性基的大小是最优的。所以性质三是很容易证明的,但是为了严谨性,我们后面还会给出完整性证明。
为什么要倒序循环呢??因为我们枚举的是最高位,所以需要从大到小地枚举。
梳理一下代码的思路:
-
如果 x x x 在 k k k 位上没有 1 1 1,它肯定不能出现在 最高位 1 1 1 在第 k k k 位 的位置上。
-
如果 x x x 在 k k k 位上有 1 1 1,但是 最高位 1 1 1 在第 k k k 位 的位置上已经有了值 y y y,则 y y y 拥有的 1 1 1 x x x 都不需要拥有,则 x^=y。
-
如果 x x x 在 k k k 位上有 1 1 1,但是 最高位 1 1 1 在第 k k k 位 的位置上没有值,所以就没有办法表示 x x x,不得不填入 x x x。
如果最后的 x x x 变成了 0 0 0,就意味着 x x x 可以被原来的线性基表示。
于是我们便可以发现这里面的代码还有改良的空间,至少码量可以稍微减少。
void add(int w) {
for (auto x : basis)
w = min(w, w ^ x);
for (auto &x : basis)
x = min(x, x ^ w);
if (w > 0)
basis.push_back(w);
}
这是一种较巧妙地写法,读者可以自行理解。
为什么是从 60 开始循环起呢?
考虑证明如下结论:对于一个二进制不超过 m m m 位的集合,线性基的规模不会超过 m m m。
证明:如果超过了 m m m 个,则根据抽屉原理,一定有两个值最高位 1 1 1 的位置相同,那么可以使用一个值异或另一个值来消除掉。
而每一个数不可能超过 long long,所以 60,足矣!
证明在这个里面:click,我不会说是我不想写了。
运用
这东西很奇怪,但是我们得运用是不是?
0.判断 w w w 能不能被集合异或表示
直接考虑 w w w 能否插入到线性基中即可。
1.得到子集异或最大值
可以直接贪心:从高位到低位暴力考虑,如果 ans^x > ans,则 ans^=x。
因为线性基里面任意一些数异或起来一定是原序列的数,所以最终得到的答案一定在原序列里面。
2.得到子集异或最小值
这个时候可不能直接根据最大值反过来搞。是需要分类讨论的。
-
如果线性基规模 < 集合规模:这意味着这个原本的集合里面有一个数可以被其他数通过异或表示出来,即 a i = a b 1 xor ⋯ xor a b x a_i = a_{b_1} \ \text{xor} \ \cdots \ \text{xor} \ a_{b_x} ai=ab1 xor ⋯ xor abx,而最终答案就可以是右边再异或一下左边,得到答案为 0 0 0。
-
如果线性基规模 = 集合规模:则答案就是线性基中最小的数。
-
如果线性基规模 > 集合规模:显然这种情况并不存在。
3.得到第 k k k 个异或最小值
将 k k k 二进制拆分即可。
当 k = 1 k=1 k=1 时,所以我们求的是最小值。如果线性基规模 < 集合规模,则答案为 0 0 0。
如果 k ≠ 1 k \not = 1 k=1 但是还是线性基规模 < 集合规模,则 k − − k-- k−−,因为后面我们算出来的答案不会有 0 0 0(还记得我们的性质 2 2 2 吗),所以要减去 0 0 0 的情况。
然后从大到小枚举即可。