【线性基】

线性基在我看来是一列数。我们可以通过这列数来解决原序列的一些问题。

对于异或,我们有一些性质:如果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.判断一个数是否能由之前数异或得到。

那就插入线性基吧。能插就不能得到。不能插就能得到。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值