线性基学习笔记
概念
线性基是向量空间的一组基,通常可以解决有关异或的一些题目。
性质
线性基有三大性质
\(1\)、原序列里面的任意一个数都可以由线性基里面的一些数异或得到
\(2\)、线性基里面的任意一些数异或起来都不能得到 \(0\)
\(3\)、线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
线性基的题目一般都是对这三大性质的考察
要想了解性质,我们首先要知道线性基是如何构造的
构造
对原集合的每个数 \(p\) 转为二进制,从高位向低位扫,对于第 \(x\) 位是 1 的,如果 \(a_x\) 不存在,那么令 \(a_x=p\) 并结束扫描,如果存在,令 \(p=p\ xor\ a_x\)。
代码
void ad(long long now){
for(rg int i=60;i>=0;i--){
if(now==0) return;
if(now&1LL<<i){
if(d[i]){
now^=d[i];
} else {
d[i]=now;
return;
}
}
}
}
证明
对于性质一,分两种情况讨论
如果 \(x\) 没有插入成功,那么 \(x\) 必定在异或一些数后变成了 \(0\)
就有 \(d[i]\ xor\ d[j]\ xor\ d[k]...xor\ x=0\)
也就是 \(d[i]\ xor\ d[j]\ xor\ d[k]...=x\)
如果 \(x\) 插入成功
则 \(d[i]\ xor\ d[j]...xor\ x=d[k]\)
变一下就是 \(d[i]\ xor\ d[j]\ xor\ d[k]...=x\)
对于性质二
如果 \(d[i]\ xor\ d[j]\ xor\ d[k]=0\)
那么较晚插入的那一个一定会被异或成 \(0\),不会插入线性基
对于性质三
目前不会证,可以看一下这篇博客
用途
\(1\)、查询 \(n\) 个数异或的最大值。
从高位到低位贪心地选,如果异或当前位的线性基后答案变得更大就异或上,否则就不选
代码
void cx(){
long long ans=0;
for(rg int i=60;i>=0;i--){
if((ans^d[i])>ans) ans^=d[i];
}
printf("%lld\n",ans);
}
\(2\)、查询 \(n\) 个数异或的最小值。
就是线性基中最小的值
如果有插入不成功的情况,答案就是 \(0\)
\(3\)、查询某个数是否能被异或得到。
看一下这个数能否成功插入线性基
如果可以,就能被异或得到,否则不能
\(4\)、查询 \(n\) 个数异或的 \(k\) 小值
首先,要对这个序列的线性基处理一下,对于每一个 \(d[i]\) ,
枚举 \(j=i \to 1\),如果 \(d[i]_{(2)}\) 的第 \(j\) 位为 \(1\)
那么 \(d[i]\ xor\ d[j-1]\)
其实就是让每一个线性基提供自己最高位上的一
这样,我们只需要把 \(k\) 进行二进制分解,贪心去选即可
异或之后得到的数组仍然是一个线性基
也就是说一个序列的线性基不唯一,只是元素数量唯一而已
题目
利用了性质三,贪心地选择,既然数的个数是唯一的那么我们就可以按照魔法值的从大到小来选
同样利用了性质三
线性基可以支持 \(RMQ\),巧妙地利用倍增可以做到两个 \(log\)