线性基是用来处理异或和的一大利器,对于一个数集(S),它的线性基是一个最小的集合(S_1),使得(S)内元素异或得到的值的值域与(S_1)内元素的值的异或得到的值的值域相同
很明显一个序列的线性基具有如下的性质:集合内元素相互异或得到的值不可能为0
证明:反证法,假设存在(a_1,a_2,cdots,a_p)使得(a1 xor a2 xor cdots xor a_p=0)
由异或的性质就可以得到(a_1=a_2 xor cdots xor a_p)
对于某一个数(x=a_1 xor y),如果(y)能被该线性基用异或表示,那么将(a_1)与(y)异或在一起来表示(x)的话,无论怎么样都不会用到(a_1)
即存在一个更小的线性基([a_2,a_3,cdots,a_n]),(a_1)是无用的,这与线性基的定义相矛盾,原命题得证
由上面的性质你可以得到如下推论:如果某个数(x)可以被这个线性基表示出来,那么这个表达方式唯一(证明也可以使用反证法)
构造
对于一个给定的序列,如何构造它的线性基呢?
我们记原来的序列为(a_1,a_2,cdots,a_n),它的线性基为(p_1,p_2,cdots,p_m)(其中(m)为(a_i)中的数在二进制表示下的最大位数)
同时我们规定,(p_i)的二进制表示中最高位是第(i)位且这一位为1
也就是说,对于一个(p_i)只会有
1)若(p_i=0),则只有可能在满足(j>i)的(p_j)的二进制表示的第(i)上出现1
2)若(p_i ot=0),那么(p_i)中比第(i)位更高的位上一定是0,但比第(i)为更低的位上有可能出现1;并且对于所有的(j>i)中(p_j)中的第(i)位上一定是0
流程的话我们考虑每一次(insert)一个数
假设当前(insert)的数是(x),我们从高位向低位枚举,如果当前(x)的最高位对应的线性基(p_i)为0,那么就令(p_i=x)并且结束;否则就令(x=x xor p_i)并且继续枚举
这样做显然不会破坏之前出现的值域,并且当我们考虑由这个数与其他数组成的新的异或值,由上面的操作我们知道(x)是可以被线性基中的某些数得到的,所以我也可以用这些数与其他数组合来得到新的异或值(即由操作过程我们是有(x=p_{i_1} xor p_{i_2} xor cdots xor p_{i_k})的)
合并
合并操作类似于插入操作,将线性基(A)中的所有元素依次插入线性基(B)中即可
查询
一般的查询就是询问一个数(x)能否被当前的线性基表示出来
这个问题与插入十分相似,对(x)的最高位逐步考虑即可
从线性基的最高位开始往下遍历,如果当前位(L)是(x)的最高非0二进制位,则令(x=x xor p_L),一直到遍历完成,如果此时(x)为0则该数可以被表示出来,反之则不可(其实本质就是贪心,考虑必须要将当前的最高位消去即可证明)
应用
比较经典的就是查询某个数集内的最大(小)异或和
建出线性基之后考虑贪心,从高到低遍历线性基,如果当前取这一位对答案有贡献就取(可以从当前线性基的最高位对答案影响开始考虑,如果取当前的线性基的话,有构造可知对后续的线性基是否选取也会有影响)
最小异或和的话直接取最小的非(0)的(p_i)即可(妈耶还是贪心)