1. 前言
线性基,是线性代数中的一个板块,专门处理异或问题。
注意作者是个 OIer,因此并不会涉及(或者是极少的)线性代数知识。
注意本文所讲线性基跟向量无关。
前置知识:二进制,位运算。
2. 详解
2.1 定义与性质
线性基的定义如下:
给出一个数列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,若一个序列 d 1 , d 2 , . . . , d k d_1,d_2,...,d_k d1,d2,...,dk 满足以下性质:
- 所有原数列中的元素能够异或出来的值 d d d 中的数也能异或出来。
- 满足性质 1 的前提下, d d d 中的数不能异或出 0。
- 满足性质 2 的前提下, d d d 中的数最少。
那么就称 d d d 是原序列 a a a 的一组线性基。
注意上述性质中的异或指的是从数列中选出一些数一起异或。
实际上任意数列 a a a 都有至少一组线性基。
2.2 构造线性基
这个方法需要用到一个性质: a ⊕ b = c ⇔ a ⊕ c = b a \oplus b=c \Leftrightarrow a \oplus c=b a⊕b=c⇔a⊕c=b。
首先考虑将所有 a i a_i ai 转成二进制,然后从二进制最高位开始扫,对于第 x x x 位,如果 d x d_x dx 不存在,那么 d x = a i d_x=a_i dx=ai,否则 a i ← a i ⊕ d x a_i \gets a_i \oplus d_x ai←ai⊕dx,一个一个插入即可。
Code:
void add(LL x)
{
for (int i = 50; i >= 0; --i)
{
if (x & (1ll << i))
{
if (d[i] & x) x ^= d[i];
else { d[i] = x; break ; }
}
}
}
那么这份代码如何满足上述 3 个性质呢?
性质 1:原序列能够异或出来的数线性基也能够异或出来。
性质 2:线性基内的数不能异或出 0。
这两条性质都是根据 a ⊕ b = c ⇔ a ⊕ c = b a \oplus b=c \Leftrightarrow a \oplus c=b a⊕b=c⇔a⊕c=b 来证明的。
由于 x ⊕ d i = d j ⇔ d i ⊕ d j = x x \oplus d_i = d_j \Leftrightarrow d_i \oplus d_j = x x⊕di=dj⇔di⊕dj=x,因此性质 1 得证。
性质 2 采用反证法:假设 d i ⊕ d j ⊕ d k = 0 d_i \oplus d_j \oplus d_k=0 di⊕dj⊕dk=0, d k d_k dk 最晚插入。
这里只讨论 3 个数的理由是实际上你可以将 d i d_i di 看成多个数异或。
上式等价于 d i ⊕ d j = d k d_i \oplus d_j= d_k di⊕dj=dk。
于是根据代码, d k d_k dk 不应该被插入线性基,矛盾。
性质 3 的证明:
显然对于所有存在的 d i d_i di 而言,第 i i i 位二进制位一定是 1。
既然如此,如果删去任何一个数,都会导致有一位二进制位空缺,因此这些数是必要的,同时又是最少的。
3. 应用
3.1 最大值问题
该问题的详述描述如下:给出数列 { a i } \{a_i\} {ai},从中选一些数使其异或结果最大。
根据线性基的性质 1,我们可以构造线性基。
构造之后直接暴力按照 d i d_i di 从大到小枚举,然后看一下跟答案异或能否使答案变大,能就异或。
实际上就是一个贪心的思想。
为什么这样贪心是对的呢?
如果第 i + 1 i+1 i+1 位通过异或变成了 1,即使 1 − i 1-i 1−i 位通过异或都变成了 0,答案也一定是增大的。
因此贪心是正确的。
3.2 最小值问题
这个问题有两类:
第一类问题:问线性基能异或出的最小值。
这个直接就是最小的 d i d_i di,因为最小的 d i d_i di 无论异或谁都会变大。
第二类问题:问原序列能异或出的最小值。
这个还需要看一下有没有元素插入失败,如果有就是 0,否则还是最小的 d i d_i di。
3.3 第 k 小问题
该问题的详细描述如下:给出序列 { a i } \{a_i\} {ai},取出任意数进行异或,问能异或出的所有值的第 k k k 小。
首先我们需要对线性基做一个处理。
对于每一个 d i d_i di,如果 d i d_i di 二进制表示第 j j j 位为 1,那么 d i ← d j − 1 ⊕ d i d_i \gets d_{j-1} \oplus d_i di←dj−1⊕di。
然后对 k k k 做二进制拆分,如果第 i i i 位为 1,那么答案异或上 d i d_i di。
这样做的正确性就是考虑到 d i d_i di 实质作用是提供最高位的 1,因此只要所有 1 能够跟 k k k 对应答案就是第 k k k 小。
实际上转换后的线性基还是线性基。
3.4 异或和问题
详细描述:给出序列 { a n } \{a_n\} {an},选若干个数异或,问可能的结果的和。
首先得出线性基,设大小为 s s s,枚举每一个二进制位 d d d,如果存在一个线性基中的数,该位为 1,那么这位的贡献是 2 d × 2 s − 1 2^d \times 2^{s-1} 2d×2s−1,所有贡献加起来即可。
正确性证明: 2 d 2^d 2d 是这一位的值, 2 s − 1 2^{s-1} 2s−1 是别的位能异或出的结果种数,保证结果种数互不相同是因为保证个数最小,此时就保证了不会出现两组不同的数,其异或值相同。
4. 总结
线性基的三大性质:
- 所有原数列中的元素能够异或出来的值线性基中的数也能异或出来。
- 线性基中的数不能异或出 0。
- 线性基中的数最少。
构造方式:首先考虑将所有 a i a_i ai 转成二进制,然后从二进制最高位开始扫,对于第 x x x 位,如果 d x d_x dx 不存在,那么 d x = a i d_x=a_i dx=ai,否则 a i ← a i ⊕ d x a_i \gets a_i \oplus d_x ai←ai⊕dx,一个一个插入即可。
最大值问题:贪心从大到小求解。
最小值问题:最小 d i d_i di 或者是 0。
第 k k k 小问题:转换线性基后直接二进制拆分。
异或和问题:枚举二进制位后算贡献 2 d × 2 s − 1 2^{d} \times 2^{s-1} 2d×2s−1。