线性基+前缀线性基学习笔记

学习于这个博客

首先,线性基是一个集合,对于任何一个序列一定有至少一个线性基,取线性基中的某些数异或起来一定可以得到原序列中的任何一个数。

线性基三大性质:
1.原序列中的任何一个数都可以由线性基里面的一些数异或得到。
2.线性基中任意数异或起来都得不到0(换句话说,一个数只能由线性基中特固定的几个数异或得到)。
3.线性基中数的个数是唯一的,并且在满足所有性质的条件下个数是最少的。

线性基构造函数:

void add(long long x){
    int i;
    for(i=60;i>=0;i--){
        if(x&(1(long long)<<i)){
            if(d[i])
            x^=d[i];
            else{
                d[i]=x;
                break;
            }
        }
    }
}

d数组有个性质,d[i]如果不为0的话,d[i]的二进制的第(i+1)位是1且为最高位。

性质1证明:
对于对于加入的任何一个数,从最高位向最低位进行异或,而d[i]的(i+1)位一定是1且位最高位,所以x一定是递减的,如果碰到某一位d数组中没有被赋值,则进行赋值然后x变为0,如果碰不到没有赋值的d数组,那x最终也为0,所以根据异或可知最开始添加的数x一定能用d数组组合异或得到。
性质2证明:
假设在d数组中任选一些数,那么最大的d[i]的最高位1没有被异或成0,所以不可能得到0。
性质3证明:(这个证明是借鉴的别人的,非自己证明)
线性基中数的个数本质上就是插入成功的个数,如果x全部都插入成功,那么线性基中数的个数即为x的个数,如果某一个x插入失败,则x=d[a] ^ d[b] ^ d[c],如果想让x插入成功,则为d[a] ^ d[b] ^ x=d[c],这样虽然x插入成功了但是d[c]插入失败,所以x无论插入到哪里,最后一个数一定会插入失败,而且x=d[a] ^ d[b] ^ d[c]是固定唯一的,不可能由其它的数组合异或而成,所以插入成功数肯定是固定的数量,所以线性基个数一定是唯一的,只是数组的值可能会因为插入顺序而改变。
这三个性质都是很重要的,需要熟记,特别是性质二是特别重要且易忽略的,需要重点记住!!!

应用1:
求一个序列某些数的异或和的最大值

long long ans=0;
for(i=60;i>=0;i--){
    if((ans^d[i])>ans){
        ans^=d[i];
    }
}

很明显的贪心策略,但是有一个很重要的问题。
为啥任意d[i]都能被随便选择呢?
这里证明一下,首先假设原序列中有一个数x=d[a] ^ d[b] ^ d[i],d[i]是x插入成功的位置,这样则可以证明a和b都大于i,然后d[a]和d[b]也可以向上推导,最终一定成为了某一位最大的d[j],而这个d[j]一定是原序列中的某一个数(因为没有数把它异或掉一部分),然后把这些数去重异或就能得到任意d[i]了。
应用2:
求一个序列某些数异或和的最小值
思路:先看看有没有插入失败的,如果有就直接为0,否则答案就是d数组中最小的非0数。

应用3:
求一个序列某些数异或和的所有值的第k小
思路:将k转化为二进制,将d数组中0去除然后从小到大排序,如果k&(1<<i)则异或上第i个元素即可。
这个其实就是贪心+组合,d数组中的每个数都可以选择,因此就可以根据二进制搞了。

前缀线性基:
前缀线性基就是具有线性基的所以功能并可以区间查询这些功能。
区间查询(l,r)时,将查询d[r][i]数组即可,然后对于pos[r][i]小于l的直接continue掉即可。
这个前缀线性基有个比较难理解的地方就是swap部分,我一开始总感觉如果进行了x和d[i]的交换,那么就会影响之前的X的插入,但是其实可以这么想,如果X出现的比在d[i]上插入成功的那个原数组的y早,那么明显该有影响而且因为y替换了X所以把影响抵消了,而如果X出现的比y晚,那么X一定已经把y替换掉了,所以不会出现这种情况,因此这种交换是可行的。

int cnt=0;
void add(long long x){
    int i;
    cnt++;
    for(i=0;i<=60;i++){
        d[cnt][i]=d[cnt-1][i];
        pos[cnt][i]=pos[cnt-1][i];
    }
    int P=cnt;
    for(i=60;i>=0;i--){
        if(x&(1(long long)<<i)){
            if(d[cnt][i]){
                if(pos[cnt][i]<P){
                    swap(pos[cnt][i],P);
                    swap(d[cnt][i],x);
                } 
                x^=d[cnt][i];
            }
            else{
                d[cnt][i]=x;
                pos[cnt][i]=P;
                break;
            }
        }
    }
}

HDU6579 前缀线性基模板题
cf- Ivan and Burgers 前缀线性基模板题

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=501000;
int d[MAX_N][32],pos[MAX_N][32];
int a[MAX_N];
int cnt=0;
void add(int x){
    int i;
    cnt++;
    for(i=0;i<=31;i++){
        d[cnt][i]=d[cnt-1][i];
        pos[cnt][i]=pos[cnt-1][i];
    }
    int P=cnt;
    for(i=31;i>=0;i--){
        if(x&(1<<i)){
            if(d[cnt][i]){
                if(pos[cnt][i]<P){
                    swap(pos[cnt][i],P);
                    swap(d[cnt][i],x);
                }
                x^=d[cnt][i];
            }
            else{
                d[cnt][i]=x;
                pos[cnt][i]=P;
                break;
            }
        }
    }
}
int ask(int l,int r){
    int ans=0,i;
    for(i=31;i>=0;i--){
        if(pos[r][i]<l)
        continue;
        if((ans^d[r][i])>ans)
        ans^=d[r][i];
    }
    return ans;
}
int main(void){
    int T,n,m,i,op,l,r,x;
    cin>>T;
    while(T--){
        scanf("%d%d",&n,&m);
        cnt=0;
        for(i=1;i<=n;i++){
            scanf("%d",&a[i]);
            add(a[i]);
        }
        int lastans=0;
        for(i=1;i<=m;i++){
            scanf("%d",&op);
            if(op==0){
                scanf("%d%d",&l,&r);
                l=(l^lastans)%n+1;
                r=(r^lastans)%n+1;
                if(l>r)
                swap(l,r);
                //cout<<l<<" "<<r<<" !!!!\n";
                lastans=ask(l,r);
                printf("%d\n",lastans);
            }
            else{
                scanf("%d",&x);
                x=x^lastans;
                n++;
                add(x);
            }
        }
    }
    return 0;
}

线性基思维题 P3857 [TJOI2008]彩灯

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=55;
const long long MOD=2008;
char s[MAX_N];
long long d[MAX_N];
void add(long long x){
    int i;
    for(i=60;i>=0;i--){
        if(x&((long long)1<<i)){
            if(d[i])
            x^=d[i];
            else{
                d[i]=x;
                break;
            }
        }
    }
}
int main(void){
    int n,m,i,j;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%s",s+1);
        long long x=0;
        for(j=1;j<=n;j++){
            if(s[j]=='O')
            x=x*2+1;
            else
            x=x*2;
        }
        add(x);
    }
    long long ans=1;
    for(i=0;i<=60;i++){
        if(d[i])
        ans=ans*2%MOD;
    }
    printf("%lld\n",ans);
    return 0;
}

P4570 [BJWC2011]元素
按权值排序,然后贪心如果能插入答案就加上这种矿石的权值。
证明:线性基中成功插入的个数是固定不变的,因此当然要插入成功的那些数的权值越大越好,因此就可以按权值排序。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N=1010;
struct skt{
    long long x,y;
}a[MAX_N];
long long d[MAX_N];
bool add(long long x){
    int i;
    for(i=60;i>=0;i--){
        if(x&((long long)1<<i)){
            if(d[i])
            x^=d[i];
            else{
                d[i]=x;
                return true;
                //break;
            }
        }
    }
    return false;
}
bool cmp(skt a,skt b){
    return a.y>b.y;
}
int main(void){
    int n,i,j;
    scanf("%d",&n);
    long long ans=0;
    for(i=1;i<=n;i++)
    scanf("%lld%lld",&a[i].x,&a[i].y);
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;i++){
        if(add(a[i].x))
        ans+=a[i].y;
    }
    printf("%lld\n",ans);
    return 0;
}

P3292 [SCOI2016]幸运数字
这个题倍增线性基,就是直接把线性基倍增,然后合并的时候暴力合并logn*logn复杂度,有点东西,但是不想写。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值