算法之线性基
1 概述
线性基,是线性代数中的概念,在信息学竞赛中,前缀线性基是线性基的扩展,他们主要用于处理有关异或和的极值问题。
一组线性无关的向量即可作为一组基底,张起一个线性的向量空间,这个基底即称为线性基,利用线性基的基底进行线性运算,可表示向量空间内的所有向量,换句话说,所有向量都可以拆成基底的线性组合。
根据异或的原理,将一个数字拆成他的二进制形式,将二进制形式用向量来表示,由于一组线性无关的向量可以张起一个向量空间,因此可以考虑构造这样一组数字的二进制形式组成的线性基,在这个线性基中,通过基底的线性组合、异或运算,从而可以表达所有的异或结果。
准确来讲就是通过一些方法一组由正整数组成的集合A转化为由线性基组成的集合B,转化之后,集合B中的任意一个元素都可以集合A中的若干个元素通过异或运算得到,反之亦然。
通过方法将 一组由正整数组成的集合A转化的线性基集合B不唯一,不同的方法转化的结果不一定相同,但是它们具备线性基的性质相同
2 线性基的性质
- 性质一、原数列中的任意一个数,均可由线性基中的一些数异或得到,反之亦然
- 性质二、在满足性质一的情况下,线性基中的元素个数最小
- 性质三、线性基中各元素的二进制最高位所在位置互不相同
- 性质四、线性基中的任意部分元素进行异或的结果都可以通过原始数组的部分元素异或得到,反之亦然
3 线性基的构造
将一组由正整数组成的集合A转化为线性基组成的集合B由一般由两种方法:
3.1 贪心法
贪心法求一组线性基的步骤:
- 申请一个长度为64的数组LB,用存放所有的线性基的元素值,LB[i] 表示 该线性基元素值的二进制最高位1所在的位置(从零开始)一定为i。初始化的时候可将将LB全设置为零,表示当前线性基中没有任何元素。
- 依次插入集合A中的元素p,每次找到p的二进制最高位1所在的位置i,判断LB[i]是否存在,如果不存在,则LB[i] = p;如果存在则令p = p ^ LB[i] ,继续循环执行操作,直至LB[i]不存在或者p为0
- 将集合A中的元素执行插入操作后,如果线性基中LB[i]不等于零,LB[i]为线性基中的一个元素
代码实现:
class LinearBasis:def __init__(self, nums=[]):self.lb = [0] * 64for v in nums:self.insert(v)def insert(self, v):"""if return True meas insert success else insert failed"""for i in range(63, -1, -1):if not v:return Falsenif (v >> i) & 1:if self.lb[i]:v ^= self.lb[i]else:self.lb[i] = vreturn Trueelse:continuereturn Falseif __name__ == '__main__':nums = [1, 2, 3, 4, 5, 6, 7]lb = LinearBasis(nums)print(lb.lb)
解释一下该种方法为啥能满足性质1。2,3
性质1:因为LB中的元素都是通过集合A中的部分元素经过异或得到的,故反之也能成立,根据异或的可逆性,及a ^ b = c能得到 c ^ b = a或者c ^ a = b
性质2:假设存在一个线性基它的元素个数更少,它的元素个数为n - 1,,那么当插入第n个元素的时候一定会插入失败,因为第n个元素可以通过前面n - 1个线性基中的部分通过异或运算得到,显然不成立。
性质3:显然由贪心算法的原理决定的
3.2,高斯消元法
这种方法可以参考线性代数中的方法或者见:高斯消元法
3.3 应用
- 判断一个数是否可以被一个序列异或出来
将其他元素依次插入到线性基中,最后插入该元素,如果该元素能够插入成功,说明该元素无法被其他元素异或得到,然后可以得到 - 求一个序列的若干个元素异或的最大值
假如现在的ans从高位向低位异或,异或到了第i位,分为两种情况- 1 如果ans的二进制第i位不为1:
那么ans ^ p[i] > ans,显而易见ans异或完之后第i位为1,一定变得更大。 - 如果ans的二进制第i位为1:
那么ans ^ p[i] < ans,显而易见ans异或完之后第i位为0,一定变得更小。
- 1 如果ans的二进制第i位不为1:
- 求一个序列的若干个元素异或的最小值
显而易见答案为0或p[0]。
因为线性基异或不出0,所以如果原序列可以异或出0,那ans就是0,否则就是p[0],因为p[0]异或任何线性基中的元素都会变大 - 求一个序列的若干个元素异或的第k小值
二进制原理 ,先预先处理线性基数组,保证p[i]已经是这一位在线性基里面最小的,只要让p[i]的每一位异或上对应的位置,即可使其变得更小。
即,对于每一个p[i],枚举j=i-1到j=0,如果p[i]的第j位为1,那么p[i]异或上p[j]。
然后将k转化成2进制,再用2进制的1、0的位置异或即可。