下文中所说的集合除特殊说明,均指“无符号整数集”。
设 \(T\subseteq S\),所有这样的子集 \(T\) 的异或和组成的集合称为 \(S\) 的张成,记作 \(\text{span}(S)\)。即在 \(S\) 中选出任意多个数,其异或和的所有可能的结果组成的集合。
对于一个集合 \(S\),如果存在一个元素 \(S_j\),使得 \(S\) 的在除去这个元素的集合 \(S'\) 的张成 \(\text{span}(S')\) 中包含 \(S_j\),那么就说 \(S\) 线性相关。
相反的,如果集合 \(S\) 中不存在这样一个元素,那么就称 \(S\) 线性无关。
说的简单一些,就是如果 \(S\) 线性相关,那么存在一个元素可以由其他元素异或得到。
由此得到一个性质:对于一个线性相关的集合 \(S\),去除掉可以由其他元素异或得到的元素后,集合的张成不变。
线性基一般采用动态构造的方式,即从一个空的线性基开始,逐个插入某个数 \(t\)。
设 \(S\) 中用二进制表示下有 \(L\) 位,那么我们用一个长为 \(L\) 的数组 \(a\) 来保存该集合的线性基。
对于每一个 \(i\),\(a_i\) 只有以下两种可能:
注意,“整个 \(a\) 数组中只有 \(a_i\) 的第 \(i\) 个二进制位上为 \(1\)”这个性质是这个构造方案所特有的,并不是线性基必须具有的性质。
那么构造方案据显而易见了:对于一个原数组中的数 \(t\),从 \(t\) 最高位的 \(1\) 开始考虑,假设这是第 \(j\) 位,且 \(a_j\ne0\),那么就将 \(t\) 异或上 \(a_j\),也就是将 \(t\) 的第 \(j\) 个二进制位上的 \(1\) 消掉,直到找到一个二进制位 \(i\),满足当前 \(t\) 的最高位 \(1\) 为第 \(i\) 位,且 \(a_i=0\),那么我们就可以将 \(t\) 插入到 \(a_i\) 这个位置,插入时需要满足:
那么不难写出上面构造的代码。
void insert(ll t) { for (int i = 51; i >= 0; i --) { if (!(t & (1ll << i))) continue; if (a[i]) t ^= a[i]; else { /*注意,这里两个循环的顺序不能交换*/ for (int j = 0; j < i; j ++) if (t & (1ll << j)) t ^= a[j]; for (int j = 51; j > i; j --) if (a[j] & (1ll << i)) a[j] ^= t; a[i] = t; break; } } }
时间复杂度为 \(O(\log n)\) 的。
注意到上面的构造方式,对任意的 \(a_i\ne0\),\(a_i\) 都是与线性基中其他的数或原集合中的数异或得来,而异或这个操作显然是可逆的,所以线性基中的任意一个数都是可以由原集合中的数异或得来,同样可以用 \(a\) 中的数异或得到原集合中的数。
按上面的构造方案,直接将所有的 \(a\) 异或得到的值便是整个数组的最大异或值。
这一点并不难理解,因为在上面我们保证了对于任意的 \(a_i\ne0\),仅有 \(a_i\) 的第 \(i\) 个二进制位不为 \(0\),所以可以保证将每异或一个不为零的 \(a_k\),二进制下第 \(k\) 位会变为 \(1\),且永远不会再变回 \(0\);
至于 \(a_k=0\) 的情况,意味着不存在一种方案使得第 \(k\) 位可以在不被 \(a_j(j>k)\) 控制的情况下单独为 \(1\),如果强行让第 \(j\) 位为 \(1\),得到的答案不会更优,因为可能会导致更高位的 \(1\) 消失。
显然,线性基中最小的、不为零的 \(a_k\) 即为答案。
对于一个数 \(x\),我们想要知道它能否由 \(S\) 中的数异或得到,我们可以先构建出 \(S\) 的线性基 \(a\),再去尝试将 \(x\) 插入 \(a\),如果最终 \(x\) 没有被插入,即被消为 \(0\),那么意味着可以被异或得到。相反则不能。
我们还有另一种构造方式,但是会失去“整个 \(a\) 数组中只有 \(a_i\) 的第 \(i\) 个二进制位上为 \(1\)”这个特殊性质,但相对来说构造要更简洁一些。与上面的构造方案唯一的区别就是不需要维护该性质,这里不多赘述。
void insert(ll t) { for (int i = 51; i >= 0; i --) { if (!(t & (1ll << i))) continue; if (a[i]) t ^= a[i]; else {a[i] = t; break;} } }
要注意,对于这种构造方式,若我们要求最大异或值,在做异或时需要判断异或后得到的值是否更大。
两个集合的线性基合并后得到的线性基为两个集合的并的线性基。我们只需要将其中一个线性基中的数全部插入另一个线性基即可。
[1] 线性基学习笔记 - Menci
[2] 线性基小记 - command_block
[3] 线性基 - OI Wiki