定义

对于正整数x,将其拆成二进制表示,并且将二进制的每一位都看做坐标的每一位.就可以得到一个向量.然后在这个向量空间上定义加法运算为对应坐标进行异或运算,也就是等效于模2意义下的加法运算.

不难证明,这时整个空间成为了一个线性空间.因此,可以在这个空间上讨论线性相关,空间的基等一系列概念.对于给定的一个非负整数集合A生成的空间V,其在这种定义下的基向量组就称为线性基.

因为在研究线性基时没有必要将所有的非负整数都转化成坐标形式.所以,下面都用整数形式而非坐标形式讨论性质.

性质

  1. 对于集合中的两个数x,y.可以将x,y替换为x,x xor y.这时生成的空间不会发生变化.也就是线性基的大小不变.

证明:

假设一个数需要用y来表示,即

p=y \ xor \ t

根据异或运算的交换律和基本性质:

x \ xor \ x =0

则有

p=y \ xor \ t=x \ xor (x \ xor \ y) \ t

也就是p仍然能被表示出来,结论成立.根据这个性质,可以使用如下代码构造线性基:

inline void Insert(LL x){
    int i=0;
    for (i=50;i>=0;i--){
        if (!(x>>i))    continue;
        if (!p[i])  {p[i]=x;    break;}
        x^=p[i];
    }
}

可以通过类似高斯消元的思想,使得只有第i个线性基的第i位为1,代码如下:

inline void Gauss(){
    int i=0,j=0;
    for (i=0;i<=50;i++){
        if (!(p[i]>>i)) continue;
        b[++cnt]=p[i];
        for (j=i+1;j<=50;j++)
            if ((p[j]>>i)&1)    p[j]^=p[i];
    }
}

求出线性基后,就可以利用其性质解决子集异或的问题了.

例题

[LOJ113]最大异或和

给出一个正整数集合,求出其子集的最大异或值.

链接

线性基模板题.

构造出线性基后,从最高位向最低位枚举,如果异或上这一位线性基可以使答案变得更大,就异或这一位.

代码:

#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#define l(x) (x<<1)
#define r(x) ((x<<1)+1)
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;

LL n,i,Ans,x;
LL p[64];

inline int Abs(int x){return (x<0)?-x:x;}
inline void Swap(int &a,int &b){a^=b^=a^=b;}
inline int Min(int a,int b){return (a<b)?a:b;}
inline int Max(int a,int b){return (a>b)?a:b;}

inline LL read(){
    LL p=0; char    c=getchar();
    while (c<48||c>57)  c=getchar();
    while (c>=48&&c<=57)    p=(p<<1)+(p<<3)+c-48,c=getchar();
    return p;
}

inline void Insert(LL x){
    int i=0;
    for (i=50;i>=0;i--){
        if (!(x>>i))    continue;
        if (!p[i])  {p[i]=x;    break;}
        x^=p[i];
    }
}

int main(){
    n=read();
    for (i=1;i<=n;i++){
        x=read();   Insert(x);
    }
    for (i=50;i>=0;i--)
        if ((p[i]^Ans)>Ans) Ans^=p[i];
    cout<<Ans<<endl;
    return 0;
}

[LOJ114]K大异或和

给出一个正整数集合,求出其第k小的子集异或值.

链接

先构造线性基,然后高斯消元.将不为0的基按照从低位到高位的顺序存起来.依次枚举线性基,当枚举到第i个时,如果k的第i位为1,就异或这个基.注意,当线性基的大小等于集合大小时,0不能被表示出来.

代码:

#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#define N 55
#define l(x) (x<<1)
#define r(x) ((x<<1)+1)
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;

int i;
LL n,m,k,x,cnt;
LL p[N],b[N];

inline int Abs(int x){return (x<0)?-x:x;}
inline void Swap(int &a,int &b){a^=b^=a^=b;}
inline int Min(int a,int b){return (a<b)?a:b;}
inline int Max(int a,int b){return (a>b)?a:b;}

inline LL read(){
    LL p=0; char    c=getchar();
    while (c<48||c>57)  c=getchar();
    while (c>=48&&c<=57)    p=(p<<1)+(p<<3)+c-48,c=getchar();
    return p;
}

inline void Insert(LL x){
    int i=0,j=0;
    for (i=50;i>=0;i--){
        if (!(x>>i))    continue;
        if (!p[i])  {p[i]=x;    break;}
        x^=p[i];
    }
}

inline void Gauss(){
    int i=0,j=0;
    for (i=0;i<=50;i++){
        if (!(p[i]>>i)) continue;
        b[++cnt]=p[i];
        for (j=i+1;j<=50;j++)
            if ((p[j]>>i)&1)    p[j]^=p[i];
    }
//  for (i=1;i<=cnt;i++)    cout<<b[i]<<endl;
}

inline LL Query(){
    int i=0;
    LL Ans=0;
    if (k>=(1ll<<cnt))  return -1;
    for (i=0;i<=50;i++)
        if (k&(1ll<<i)) Ans^=b[i+1];
    return Ans;
}

int main(){
    n=read();
    for (i=1;i<=n;i++){
        x=read();   Insert(x);
    }
    Gauss();
    m=read();
    for (i=1;i<=m;i++){
        k=read();
        if (cnt!=n) k--;
        printf("%lld\n",Query());
    }
    return 0;
}
说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...