线性基
向量空间的基
向量空间中最大的线性无关组称为该向量空间的一组基
线性无关组
一些向量\(v_1,v_2,···,v_k\)
不存在\(a_1v_1+a_2v_2+···+a_kv_k=0且a_1,a_2,···,a_k不全为零\)
线性基
一般指\(\mathbb{Z}_2\)(模2下,或异或下)的一个子空间的基
应用
- 快速查询一个数是否可以被一堆数异或出来
- 快速查询一堆数可以异或出来的最大/最小值
- 快速查询一堆数可以异或出来的第k大值
性质
- 原数列里的任何一个数都可以通过线性基里的数异或表示出来
- 线性基里任意一个子集的异或和都不为0
- 一个数列可能有多个线性基,但是线性基里数的数量一定唯一,而且是满足性质一的基础上最少的
- 注意特判0
- 一个数二进制拆分后,每一位的线性基异或后所得到的值严格大于一个比它小的数所得的值
具体实现
在插入时,对于二进制的每一位,我们只需要让集合中的唯一一个数的最高位是它就好(因为再多一个没有用,只会更麻烦)
插入:
从高到低检查该数,如果此位为1,查一下线型基中该位有没有被占用过,如果为空,插入到线型基中;如果不为空,用当前位的数异或它(即废掉它的这一位),继续插入;这样一个数只有两种下场:插入/被^成0;
求最大值(求最小值相反):
从高位到低位贪心,如果异或上该数使答案变大,就异或;
若要求出线性基内的元素与一个给定的数M的最大异或值,只需在求解时把x的初值设为M即可;
求第k大值
首先考虑,如果每一位的p[i]与其它位都互不干扰,那么就可以从高到低按顺序选择就行了,但是最开始创建的线性基不满足这个性质,如\(p[3]=101_2,p[0]=1_2\)就相互影响了,那么我们可以重构一个数组d来解决,从而尽力把每个p[i]只留下第i位的1,从而使各个位都互不影响,再根据上述线性基最后的性质便可以求得第k大。
注意重构的线性基的大小cnt为p[i]有数的个数,那么线性基可表示的数的数量为\(2^{cnt}-1\),因为每一位都互不影响,那么每一位都有选和不选2种可能,再减去都不选的一种可能。
基本操作实现
点击查看代码
int zero; //记录n个数中0的个数
struct Lbase{
ll p[101],d[101];
int cnt,lens;
void init(){
memset(p,0,sizeof(p));
memset(d,0,sizeof(d));
cnt=0;lens=0;
}
void insert(ll x){//插入
if(x==0)return ;
for(int i=lens;i>=0;i--){
if((1ll<<i)&x){
if(p[i])x=x^p[i];
else {p[i]=x;break;}
}
}
return;
}
ll mmaxx(){ //查寻最大值
ll x=0;
for(int i=lens;i>=0;i--){
if((x^p[i])>x)x=x^p[i];
}
return x;
}
bool ask(ll x){ //查寻一个数是否能被异或出来
for(int i=lens;i>=0;i--){
if(x&(1ll<<i))x^=p[i];
}
return x==0;
}
void rebuild(){ //重构
cnt=0;
for(int i=lens;i>=0;i--){
for(int j=i-1;j>=0;j--){
if(p[i]&(1ll<<j))p[i]^=p[j];
}
}
for(int i=0;i<=lens;i++)if(p[i])d[cnt++]=p[i];
}
void Kth(int pos){ //找第k小
if(n!=cnt)pos--;//表示可以异或出0,说明n个数没全部插入线性基,未插入的数可以在线性基中异或出来,也就是可以从n个数中异或出0
pos=pos-zero;
if(pos<=0){printf("0\n");return ;}
if(pos>=(1ll<<cnt)){printf("-1\n");return ;} //一个线性基可以表示(1<<cnt)-1个数
ll ans=0;
for(int i=lens;i>=0;i--){
if(pos&(1ll<<i))ans^=d[i];
}
printf("%lld\n",ans);return ;
}
void getcnt(){//线性基大小
for(int i=0;i<=lens;i++)if(p[i])cnt++;
}
}T;
例题网址
例题
1.(Zero XOR Subset)-less 题解
2.Bitsetbaba and Power Grid
题意:给一个n个数的序列\(A={x_1,x_2,····,x_n}\).考虑有编号\(0~2^k-1\)的点的图,若存在i使得\(u XOR v=x_i\),则u与v相连
问这个图有几个联通块
题解:
考虑若\(u \space xor\space v=x_i,u \space xor \space z=x_j\),那么v和z也相连通
同时也可以发现\(v \space xor\space z=x_j\space xor\space x_i\)
所以得到结论:若存在\(X\space xor\space Y= A序列子集的异或值\),那么X和Y相连接
我们可以先通过线性基求得A序列的基,得到线性基的大小sz,也就是说A序列子集的异或值范围为\(0~2^{sz}\)
那么对于任意X,都可以找到\(2^{sz}\)个Y
那么答案就是\(2^{k-sz}\)个连通块
3.F. Mahmoud and Ehab and yet another xor task
题意:给一个长度为n的整数序列A,q次询问。每次询问问A序列前l个数的子序列能异或出x的个数
题解:
问能否异或出什么就能想到线性基
我们可以T[i]表示储存前i个数的线性基,T[i].cnt表示前i个数的线性基的大小
那么首先判断x在前l个的线性基是否能得到,不能就是0,能得到答案就是\(2^{l-T[l].cnt}\)
为什么答案是\(2^{l-T[l].cnt}\)?考虑线性基的性质
对于x能从线性基中表示出来,那么它在线性基的线性表达是唯一的
剩下\(l-T[l].cnt\)个数都是可以随意跟线性基组合,使得每种组合都有唯一线性组合得到x
所以有\(2^{l-T[l].cnt}\)中可能
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=1e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll power(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%MOD;
y>>=1;x=x*x%MOD;
}
return ans;
}
int zero; //记录n个数中0的个数
struct Lbase{
ll p[101],d[101];
int cnt,lens;
void init(){
memset(p,0,sizeof(p));
memset(d,0,sizeof(d));
cnt=0;lens=0;
}
void insert(ll x){//插入
if(x==0)return ;
for(int i=lens;i>=0;i--){
if((1ll<<i)&x){
if(p[i])x=x^p[i];
else {p[i]=x;break;}
}
}
return;
}
bool ask(ll x){ //查寻一个数是否能被异或出来
for(int i=lens;i>=0;i--){
if(x&(1ll<<i))x^=p[i];
}
return x==0;
}
void getcnt(){//线性基大小
for(int i=0;i<=lens;i++)if(p[i])cnt++;
}
}T[maxn];
int n,q,cun[maxn];
int main(){
n=read();q=read();T[0].lens=20;
for(int i=1;i<=n;i++){
T[i]=T[i-1];
T[i].insert(read());
T[i].cnt=0;T[i].getcnt();
}
while(q--){
int l=read(),x=read();
if(T[l].ask(x))printf("%d\n",power(2,l-T[l].cnt));
else puts("0");
}
return 0;
}