线性基在我看来是一列数。我们可以通过这列数来解决原序列的一些问题。
对于异或,我们有一些性质:如果a^b=c,则a^c=b;如果a^b^c=0;则a^b=c;
线性基性质
我们定义数组d表示数组a的线性基,且d从0开始标号。d[i]要么为0(未使用),要么二进制下i+1位为1(即1<<i),且最高位就是i+1位。
线性基的如下性质:
1.a序列中数均可以由d中数异或得到。
2.d序列中任何数异或起来不会为0.
3.个数唯一,在满足性质1的前提下,个数最少。
首先我们看构造线性基的代码。
for(int i=50;~i;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else{
d[i]=x;break;
}
}
}
从最高位按位枚举,在插入数x该位为1的前提下,如果该位已有表示,则直接异或去掉这一位,反之说明当前线性基还不能表示x的这一位,所以直接插入x。故else执行意味着该数插入成功。
根据这个代码,我们试着来证明一下上面的三个性质。
对于性质1
分类讨论:①该数成功插入线性基。②该数未能成功插入线性基。
对于①
根据代码,我们假设插入时d[i]=x';(此时x可能已经异或了一些d)
即x^d[q1]^d[q2]^d[q3]^……=x'=d[i];
即d[q1]^d[q2]^d[q3]^……^d[i]=x;
所以此时x可以由线性基异或得到。
对于②
未能插入,说明该数异或了一些d后变成了0,则
d[q1]^d[q2]^……^d[qn]=x;
所以已经有数异或得到x。
对于性质2
反证法。
假设有d[a]^d[b]^d[c]=0;
则d[a]^d[b]=d[c];那么在插入d[c]的时候,这个数会因为性质1而无法插入。所以假设不成立。
对于性质3
我懒得证因为我不会。
线性基应用
1.求原序列最大异或和
因为任何数都能靠线性基异或得到,那么这些数异或也可以由线性基异或得到。
根据按位贪心,我们从最高位开始,如果最高位能使答案变大,那就异或。
管它后面洪水滔天,第一位坚挺我就是最大。
long long ans=0;
for(int i=50;~i;i--){
if(ans^d[i]>ans)ans^=d[i];
}
2.求原序列最小异或和
那就是最小的d[i]啊,因为d[i]的最高位i+1为1,所以任何别的d与其异或都会使其变大。最小值即可。
3.求原序列第k小异或和
线性基并不唯一,所以我们可以在不违背线性基性质的情况下对线性基进行魔改。
大概可以这么理解。从d[1]开始枚举,对于某个d[i],从中最低位开始枚举,消除d[i]的当前为1位。这样构造出来的线性基可以进行第k大。
修改完的线性基满足将d数组是否参与异或从小到大表示成二进制,则这个二进制就是k。
那么我们用k的二进制来将d参与异或即可。
void get(){
for(int i=1;i<=60;i++){
for(int j=1;j<=i;j++){
if(d[i]&(1<<(j-1)))d[i]^=d[j-1];
}
}
}
void gu(long long k){
if(k==1&&tot<n)return 0;//如果原序列可以异或出0且求第一小,则直接返回。
if(tot<n)k--;//线性基不能异或出0,所以排开0的影响。
get();
long long ans=0;
for(int i=0;i<=60;i++){
if(d[i]){
if(k%2)ans^=d[i];
}k>>=1;
}
}
4.判断一个数是否能由之前数异或得到。
那就插入线性基吧。能插就不能得到。不能插就能得到。