线性基浅谈

线性基通常用于解决一系列关于异或的问题。

定义:线性基支持log插入,用一个数组来存储线性基,其中,第i个线性基就是第i位是1的数。

由此,给出插入线性基的板子。

void insert(int x){
        for(int i=31;i>=0;i--){
            if(!(x&(1<<i)))continue;
            if(!p[i]){
                p[i]=x;
                break;
            }x^=p[i];
        }
    }

其中,(x&(1<<i))是用来取出x的第i位的。

如果是0,则跳出,匹配失败。如果是1,则继续匹配。

记住要有break。

由此,可以得出线性基的三个性质:

1.原序列中的任意数都可以由线性基中的数异或得到。

2.线性基中的数异或起来不能为0.

3.线性基中的数在性质1的条件下,数量是唯一的且最少的。

证明性质1:

当插入失败时,则有x^a[1]^a[2]...=0,即a[1]^a[2]^...=x.

所以,线性基序列中一定可以通过异或得到原序列中的x.

当插入成功时,可得知,x在前面一定异或了一些数,即:

x^a[1]^a[2]....=a[i].

所以,a[1]^a[2]^...^a[i]=x.

综上所述,性质1得证。

证明性质2:

设p[1]^p[2]^p[3]=0,(下标是加入顺序)

则有p[1]^p[2]=p[3],即p[3]不可能被加入线性基。

综上所述,性质2得证。

证明性质3:

首先,如果序列中所有元素都可以被加入线性基,显然性质3成立。

当有些数(x)不可以加入时,则有:

设p[1]^p[2]^p[3]=x,

则有p[1]^p[2]^x=p[3].

也就是说,当你改变插入顺序,只会改变插入的数,数量不会改变。

综上所述,性质3得证。

例1:模板题(点击标题即可进入题目)

题目大意不多说。正常插入,对于每一个数,求出它与其它数的最大异或值即可。

当然要用线性基啦。

代码:

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
ll p[101],a[51];
ll ans,n;
inline void read(ll &x){
    ll s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')w=-1;
        ch=getchar();
    }while(ch>='0'&&ch<='9'){
        s=(s<<1)+(s<<3)+(ch^48);
        ch=getchar();
    }x=s*w;
}inline void get(ll x){
    for(int i=62;i>=0;--i){
        if(!(x>>(ll)i))continue;
        if(!p[i]){
            p[i]=x;
            break;
        }
        x^=p[i];
    }
}int main(){
    read(n);
    for(ll i=1;i<=n;++i){read(a[i]);get(a[i]);}
    for(int i=62;i>=0;i--)
        if((ans^p[i])>ans)
            ans^=p[i];
    printf("%lld\n",ans);
    return 0;
}

 

注意最后的ans,依次比对,记住加括号。

 

例题2:元素(点击标题即可进入题目)

题意就是,每个元素有两个值,一个标号,一个贡献,当且仅当前面选中的物品的标号无法通过异或得到当前物品标号时,当前物品才能选。

那么,考虑贪心

对于初始序列,来一发sort快排,以价值为关键字。之后,遍历序列,插入每个数的标号判断是否合法,可以在插入时判断。插入后,若合法,则记录贡献,否则pass.

本题考虑性质3的理解。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm> 
using namespace std;
typedef long long ll;
ll p[101];
ll ans,n;
inline void read(ll &x){
    ll s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')w=-1;
        ch=getchar();
    }while(ch>='0'&&ch<='9'){
        s=(s<<1)+(s<<3)+(ch^48);
        ch=getchar();
    }x=s*w;
}inline bool get(ll x){
    for(int i=62;i>=0;--i){
        if(!(x>>(ll)i))continue;
        if(!p[i]){
            p[i]=x;
            return true;
        }
        x^=p[i];
    }return false;
}
struct node{
    ll x,y;
}a[500000];
bool cmp(node q,node w){
    return q.y>w.y;
} 
int main(){
    read(n);
    for(ll i=1;i<=n;++i){read(a[i].x);read(a[i].y);}
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){
        if(get(a[i].x))ans+=a[i].y;
    } printf("%d\n",ans);
    return 0;
}

例3:P哥的桶(点击标题即可进入题目)

题目中要求做到区间查询,单点插入(修改),异或和最大。

考虑线段树维护线性基。

对于每一个节点都维护自己区间的线性基。对于插入,线性基插入即可。

对于查询,我们随时开一个外部结构体(同样类型),每次把合法区间的线性基合并,最后在这里查询即可。

(我还是太菜了,涨知识了qwq)

代码:

 

#include<cstdio>
#include<iostream>
#define MAXN 50000
#include<cstring>
using namespace std;
int n,m,opt,k,x;
struct node{
    int p[50];
    void insert(int x){
        for(int i=31;i>=0;i--){
            if(!(x&(1<<i)))continue;
            if(!p[i]){
                p[i]=x;
                break;
            }x^=p[i];
        }
    }
    void Ins(node &n){
        for(int i=31;i>=0;i--)
        if(n.p[i])insert(n.p[i]);
    }
}tr[MAXN<<2],ans;
inline void Insert(int cur,int l,int r,int k,int x){
    tr[cur].insert(x);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(k<=mid)Insert(cur<<1,l,mid,k,x);
    else Insert(cur<<1|1,mid+1,r,k,x);
}
inline void query(int x,int l,int r,int ql,int qr){
    if(ql<=l&&qr>=r){ans.Ins(tr[x]);return;}
    int mid=(l+r)>>1;
    if(qr<=mid)query(x<<1,l,mid,ql,qr);
    else if(mid<ql)query(x<<1|1,mid+1,r,ql,qr);
    else query(x<<1,l,mid,ql,mid),query(x<<1|1,mid+1,r,mid+1,qr);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d%d%d",&opt,&k,&x);
        if(opt==1){
            Insert(1,1,m,k,x);
        }else{
            memset(ans.p,0,sizeof(ans.p));
            query(1,1,m,k,x);
            int maxn=0;
            for(int i=31;i>=0;i--)
                if((maxn^ans.p[i])>maxn)
                    maxn^=ans.p[i];
            printf("%d\n",maxn);
        }
    }
    return 0;
} 

 持续更新中。

posted @ 2019-06-15 14:48  Refined_heart  阅读(263)  评论(0编辑  收藏  举报