线性基
线性基
1 定义
- 线性基是一个数的集合,每个序列至少有一个线性基,若干个线性基异或起来可以得到原序列的任何一个。
2 性质
- 原序列里面的任何一个数都可以由若干个线性基异或得到。
- 线性基里面任意一些数异或起来都不是0。
- 线性基里面数的个数是唯一的,并且保持性质一的前提下,数的个数是最少的。
3 线性基的构造
-
设数组d表示序列a的线性基,下标从0开始算。为了方便理解,我们设\(X_{2}\)为\(X\)的二进制数。
-
代码
void add(ll x) { for(int i=60;i>=0;i--) { if(x&(1LL<<i)) { if(d[i]) x^=d[i]; else { d[i]=x; break; } } } }
-
所以,我们可以得到关于d数组的性质,如果\(d[i]\)不为0,则\(d[i]_{2}\)的最高位一定为1。
-
异或有个小性质,即\(a\oplus b=c,a\oplus c=b\),这就是为什么当\(d[i]\)存在的时候,我们让x变成x^=d[i]。
4 性质1证明
- 因为x和d[i]的效果和d[i]与x^d[i]的效果是一样的。所以我们每次最后放入到d[i]里面的x,虽然在中间过程中可能发生了改变,但是效果是一样的。所以原序列里面的任意一个数都可以由线性基里面的一些数异或得到。
5 性质2证明
- 还是因为上面的等价关系,只要原序列没有0,那么新的d序列也是没有0的。
6 性质3证明
- 因为这里每个d[i]都最多都只有一个数。
- 充分性,因为最终的d[i]和原序列是等价的,所以d[i]表示表示原序列的所有。
- 必要性,因为每个\(d[i]\)只有一个,为了能够表示原序列,那么每个\(d[i]\)都至少需要一个(存在的前提下)。
7 如何求最大值
- 原序列中异或的最大值和线性基里面异或的最大值是一样,所以我们只要求线性基最大值。
- 线性基的最大值很容易求,我们从高位开始,如果当前的答案第i位和和新异或的数的第i位都是1,那么我们明显是不要异或这个新数的(会使结果变小),否则我们肯定要异或。
- 这样\(O(n)\)就可以求解。
8 如何求最小值
- 注意这个最小值分为两种情况,一种是原序列的最小值,一种是线性基的最小值。
- 当原序列中有一个数不能插入到线性基时,说明原序列的最小异或值就是0,否则答案就是线性基里面最小的数。
9 如何求第k小的异或值
- 这里的第k小,我们不考虑重复的值。
- 如果k里面有\(2^i\),则第k小的异或结果包含\(d[i]\),但是这样有点问题,比如d[2]=5,d[1]=2,d[0]=1,如果k=4,那么我们就会选择d[2],但是5并不似乎第4小的数,4才是第4小的数。于是我们可以把d[2]=d[2]^d[0],这样d[2]就变成了4,仍旧是原序列的线性基。
- 总结一下,对于每个线性基里面的d[i],我们可以枚举比他小的d[j],如果d[i]里面含有\(2^j\),就把d[i]异或一下d[j]。
- 单求第k小的值,时间复杂度是\(O(\log_{2}{k})\)。
10 如何判断一个值可否有线性基里面的数异或得到
- 方法1: 尝试把这个数插入到线性基里面,如果不能插入,说明可以表示。
- 方法2: 对于当前的数,如果里面有\(2^i\),那么我们就一定把d[i]异或进去,这样逐步构造就可以了,看最后是否0。
11 删除线性基
- 有3种操作,第一是把序列里面插入一个数,第二是序列里面删除一个数,第三是维护这个序列的线性基。