线性基的理解及其应用

线性基的理解及其应用

 

(一)定义:

基:在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。

同样的,线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:通过原集合S的某一个最小子集S1使得S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。

 

(二)性质:

①线性基能相互异或得到原集合的所有相互异或得到的值。

②线性基是满足性质1的最小的集合

③线性基没有异或和为0的子集。

(以上来自百度百科)

 

(三)求解:

接下来我们重点关注线性基怎么求解。首先确认一条十分有用的性质:

将原集合的子集(A0,A1,A2,...Ai)相互异或后的值替换为该子集中的任意一个元素,原集合的向量空间不变。证明十分简单:

①如果不需要用到A1,那么替换A1后自然没有影响;

②如果子集的异或值为x,被替换的数为A1,那么需要用到A1时,只需要将x与(A0,A2,A3...Ai)进行异或即可得到A1。

那么我们利用这一条性质可以得到这样一组线性基(A0,A1,A2...Ai),其中Ax的二进制表示的最高位的1在第x位。

设线性基为base[max_bit](base[i]的二进制表示的最高位的1在第i位),具体构造过程如下:

对于原集合中的数X而言,如果X的第i为1:

①如果base[i]存在则将X与base[i]进行异或,然后继续判断下一位;

②如果base[i]不存在,则令base[i]=X,结束对于X的判断。

为什么这样是正确的呢?利用上述性质可以一个解释:首先,由于base[i]的性质决定了base[i]不能由其它的base[j]异或得到,所以当base[i]不存在时,直接把X加入线性基中,那么当前得到的线性基当然线性无关。而如果base[i]以及存在了,我们用X与base[i]进行异或,假设得到Y,那么Y显然是由原集合的一个子集异或得到的,继续对Y继续判断相当于替换了原集合中的X,由上述说明我们知道替换之后的向量空间不变,故线性基也不会变。

或者可以想,如果当前在已经得到线性基中的元素已经可以通过相互异或得到X了,那么X显然是不需要加入线性基中的,而对于base[i]不存在,而X的第i为1的情况,X显然还不能被异或出来,所以将X加入线性基中。

 

(四)代码:

void init(ll n){                 //n-原集合中的元素个数
    while(n--){
        scanf("%lld",&x);        //x-原集合中的元素
        for(int i=0;i<32;i++){   //32-线性基中最多的元素个数(取决于x的大小)
            if((x>>i)&1){        //如果x的第i为1
                if(!base[i]){base[i]=x;break;}  //如果base[i]不存在,那么将x加入线性基
                else  x^=base[i];               //否则x^=base[i]
            }
        }
    }
}

 

(五)应用:

Ⅰ)首先是一道入门题:判断n个数的子集是否可以相互异或得到x

题解:求出线性基以后,如果x的第i为1,那么base[i]必须要异或进去(否则不可能使异或得到的结果的第i为1)。故对于x为1的位,直接将x异或上base[i],如果最终得到的x为0,那么x可以被异或出来,否则不行。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1e5+10;
ll base[33],N,Q,x,y;
void init(ll n){               
    while(n--){
        scanf("%lld",&x);     
        for(int i=0;i<32;i++){ 
            if((x>>i)&1){      
                if(!base[i]){base[i]=x;break;}  
                else  x^=base[i];
            }
        }
    }
}
void answer(ll q){
    while(q--){
        scanf("%lld%lld",&x,&y);x^=y;
        for(int i=0;i<32;i++){
            if((x>>i)&1){
                if(base[i])x^=base[i];
                else break;
            }
        }
        printf(x?"NO\n":"YES\n");
    }
}
int main(){
    #ifdef DanDan
    freopen("in.txt","r",stdin);
    #endif // DanDan
    scanf("%lld",&N);init(N);
    scanf("%lld",&Q);answer(Q);
    return 0;
}

Ⅱ)求从n个数中取x(x>=1)个数的异或和的第k小值

题解:

首先还是求这n个数的线性基,那么我们就可以由线性基中的数得到所有可得到的数。

假设线性基中有x个元素,则由线性基可以组成2^x-1个数(是否可以得到0需要特判,因为线性基中的元素是不能通过异或得到0的)。

由于线性基中的各个元素的最高位为1的位置不同,那么我们异或的值的最高位为1的位越低,得到的值就越小。也就是说优先选最高位为1的位置低的数。那么我们将k化成二进制串,二进制串中为1的位置就是对应的就是线性基中需要选取的元素。

比如线性基中有5个元素,分别为a0,a1,a2,a3,a4。k=5。那么我们显然就选a0,a2即可。

首先,显然不取a3,因为只从a0,a1,a2中取已经可以7个数了,而取a3大于这7个数的任意组合。而由于a0,a1只能得到3个数,所以a2必取。

其次,我们先假设a2中的<a1中为1的最高位>所在的位置为0(如果不为0,那么异或a2可以将这一位归0),那么我们就应该尽可能不选a1,而选a0。

上述过程实际上就是一个从线性基中从小到大选一个集合的过程,仔细想一下就可以发现和二进制的枚举子集是一样的。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
ll base[64],t,q,n,x,o,c=1,mx,ans;
vector<ll>_base;
void init(){
    _base.clear();o=0;
    memset(base,0,sizeof base);
    scanf("%lld",&n);
    for(int i=0;i<n;i++){
        scanf("%lld",&x);
        for(int k=63;k>=0;k--){
            if((x>>k)&1){
                if(base[k])x^=base[k];
                else{base[k]=x;break;}
            }
        }
        o=o|!x;
    }
    for(int i=0;i<64;i++)
        if(base[i])
        for(int j=i+1;j<64;j++)
            if((base[j]>>i)&1)
                base[j]^=base[i];
    for(int i=0;i<64;i++)
        if(base[i])_base.push_back(base[i]);
    mx=(1ll<<(int)_base.size());
}
void work(){
    printf("Case #%lld:\n",c++);
    scanf("%lld",&q);
    while(q--){
        scanf("%lld",&x);
        ans=0;x-=o;
        if(x>=mx)ans=-1;
        else
        for(int i=0;i<64;i++)
            if((x>>i)&1)ans^=_base[i];
        printf("%lld\n",ans);
    }
}
int main(){
    #ifdef DanDan
    freopen("in.txt","r",stdin);
    #endif // DanDan
    scanf("%lld",&t);
    while(t--){init();work();}
    return 0;
}

(ⅲ)推荐一篇我参考的大佬博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值