一堆参考资料
https://blog.sengxian.com/algorithms/linear-basis
https://oi.men.ci/linear-basis-notes/
线性基这玩意儿是用来解决异或问题一类手段
尽量说的通俗易懂一点好了
以下指的都是异或意义下的线性基,严谨的请看参考文献的第一个
概念
1.异或和
一堆数字$a_1,a_2,a_3...a_n$的异或和为$a_1xora_2xora_3...xora_n$
2.线性相关
若有一组数$a_1,a_2,a_3...a_n$,且存在某一个数是其他数的异或和,则称这一组数为线性相关,否则称其为线性无关
更形象的说,如果存在某一个数能被其他数异或得到,那么这一组数就是线性相关
比如说,$1,2,3$是线性相关,因为$1xor2=3$,但$1,2,4$是线性无关,因为不存在一个数能被其他数异或得到
3.张成
一组数能够异或得到的所有数,成为这一组数的张成,记做$span(S)$(其中$S$是原数的集合)
比方说,集合${1,2,4}$的张成是${1,2,3,4,5,6,7}$
4.线性基
设$B$是$S$的的线性基,则$S$满足两个条件
①$Bin span(S)$,即$B$是$S$的张成的子集,更通俗的讲,就是$B$中的每一个数字都能被$S$中的数异或得到
②$B$是线性无关的
然后线性基具有以下两个性质
①$S$能被$B$张成,也就是说$S$中的每一个数都能被$B$中的数表示出来
②$B$是$S$的极小生成集,也就是说只有$B$可以张成$S$,而$B$的任何真子集都不可以
③$S$中没有其他的线性无关集包含$B$作为真子集
构造
将每一个数$p$从高位到低位扫(二进制意义下),如果第$i$位为$1$,若$a_i=0$,则令$a_i=p$并退出,否则令$p=p xor a$
代码如下
1 inline void insert(ll x){ 2 for(int i=63;i>=0;--i)//逆序枚举二进制位 3 if(x>>i&1){//判断该位是否为1 4 if(!b[i]) return (void)(b[i]=x);//如果b[i]为0,则令b[i]=x 5 x^=b[i];//否则x=x xor b[i] 6 } 7 }
至于原因我就不说了(才不是因为我也不知道呢)
不过需要注意的一点是,线性基的存储方式是矩阵,如果按上面的方法是把这个矩阵消成上三角矩阵,有的题目需要把它消成对角矩阵,到时候再说吧
说人话的话,就是有的题目里这个板子不是这样写(很少),到时候再说好了
最大值
要求一些数字能得到的最大异或和是多少
可以考虑贪心,从高到低枚举每一位代表的线性基,如果异或上该位的线性基后答案增大,则异或
1 inline ll calc(){ 2 ll ans=0; 3 for(int i=63;i>=0;--i) 4 if(ans^b[i]>ans) ans=ans^b[i]; 5 return ans; 6 }
查询
在线性基中可以查询一个数能否被异或得到
具体的过程和插入类似,判断到了最后$x$是否变为0即可
1 inline bool find(ll x){ 2 for(int i=63;i>=0;--i) 3 if(x>>i&1){ 4 if(!b[i]) break; 5 x^=b[i]; 6 } 7 return x!=0; 8 }
第$K$大
线性基可用于判断一组数字异或得到的第$K$大是多少
设$cnt$为线性基内数的个数(也就是$b[i]!=0$的$i$的个数),那么所有能表示出的数字共有$2^{cnt}$个(包括0)
将$K$二进制拆分,如果$K$的第$i$位为1,则令$ans^=b[i]$
注意了,这里得把矩阵给消成对角矩阵,也就是说板子要改一改,这个放到代码里说
消成对角矩阵有一个好处,就是每一位上的1最多只会出现一次
还有一个需要注意的地方,如果线性基里元素的个数等于原来元素的个数,那么说明这一组元素是线性无关的,那么他们无论怎么异或也得不到0。而我们之前的求法是默认能取到0的,所以这种时候得$++K$才行
这里是板子线性基求第$K$大
后记
然后简单的说一下有关线性基的应用之类的
一般关于异或的题目都是要用到线性基的
还有一些不是很明显的,比如有几个操作,两个操作一起搞相当于他们的异或。比如关灯,用一个二进制表示每个操作会关掉哪几盏灯,那么两个操作一起做的话关掉的就是这两个操作异或起来之后为1的那几盏
还有,如果有一堆数,每个数有一个价值,要在满足取的数线性无关的前提下最大化价值,一般都是用贪心,下面的几道题里有。(具体证明我不会)
例题