前言:
如果你不会线性基,希望能对你有点帮助。
作者较菜,请大佬轻表。
请大家多多支持,谢谢
定义:
基:在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。
同样的,线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:通过原集合S的某一个最小子集S1使得S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。(摘自百度百科)
性质:
1. 线性基能相互异或得到原集合的所有相互异或得到的值。
2. 线性基没有异或和为0的子集。
3. 线性基的值域与原数组的值域相同 。
作用:
线性基是常用来解决子集异或一类题目的算法。
线性基可以在log的时间复杂度求出一个集合的子集异或(min,max.......)
构造:
插入一个数x:
若干数的线性基是一组数,其中为最高位的1在第x位的值。
对于一个数x,从最高位到0枚举,如果x的这一位上是1,那么分两类。
如果这一位的a数组为空,说明没有这样的情况,那么就把这一位的a数组赋值成x,注意然后要break
如果这一位的a数组不为空,说明已经出现这种情况,那么x异或上这一位的a数组上的值,然后继续。
void insert(int x) { for(int i=20;i>=0;i--) if(x&(1<<i)) { if(!a[i]) { a[i]=x; return; } x^=a[i]; } }
查询能否xor出x这个数:
这相当于就是插入的逆操作。
就是对于一个数x,从最高位到0枚举,如果x的这一位上是1,那么分两类。
如果这一位的a数组为空,说明没有出现过这样的情况,直接返回false
否则x异或上这一位的a数组上的值,然后继续。
bool find(int x) { for(int i=20;i>=0;i--) if(x&(1<<i)) { if(!a[i])return false; x^=a[i]; } return true; }
查询最大值:
我们从最高位开始,记录一个最大值ans,ans=max(ans,a[i]);
显然,每次操作后答案不会变劣,所以是正确的。
inline int query() { res=0; for(int i=20;i>=0;i--) res=max(res,res^a[i]); return res; }
查询最小值:
这就相对较简单了,和最大值的思想一样,每次异或上a[i]答案只会变大,所以最小值就是min(a[i])
int query() { for(int i=0;i<=20;i++) if(a[i])return a[i]; }
查询K最小值(#114. k 大异或和):
#include using namespace std; #define next Next #define int long long const int N=1e6+5; int n,m,cnt,a[N],b[N]; bool flag; /*char buf[1<<21],*p1=buf,*p2=buf; inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/ #define gc getchar inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } void insert(int x) { for(int i=50;i>=0;i--) if(x&(1ll<<i)) { if(!a[i]) { a[i]=x; return; } x^=a[i]; } } int query(int k) { int res=0; if(k>=(1ll<<cnt))return -1ll; for(int i=0;i<cnt;i++) if(k&(1ll<<i))res^=b[i]; return res; } signed main() { n=read(); for(int i=1;i<=n;i++)insert(read()); cnt=0; for(int i=50;i>=0;i--) { if(!a[i])continue; for(int j=i+1;j<=50;j++) { if(a[j]&(1ll<<i))a[j]^=a[i]; } } for(int i=0;i<=50;i++) if(a[i])b[cnt++]=a[i]; m=read(); while(m--) { int x=read(); if(n!=cnt)x--; printf("%lld\n",query(x)); } return 0; }
组合:
线性基可以和很多东西组合,比如线段树,st表等等。
CF1100F Ivan and Burgers 这题就是线性基和其他东西结合。
题意:
给你n个数,每次查询这个区间,问着个区间的最大异或值。
题解:
这题可以很好的和线段树结合起来,只需线性基的插入,合并,找最大即可。
查询答案可以用线段树上二分。
但由于时间复杂度是三只log的,过不了。
#pragma GCC optimize(2) #pragma GCC optimize(3) #include using namespace std; #define mid (l+r)/2 const int N=500005; int n,m,l,r; char buf[1<<21],*p1=buf,*p2=buf; inline int gc() { return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++; } inline int read() { int ret=0,f=0; char c=gc(); while(!isdigit(c)) { if(c=='-')f=1; c=gc(); } while(isdigit(c)) { ret=ret*10+c-48; c=gc(); } if(f)return -ret; return ret; } struct node{ int res,a[25]; inline void insert(int x) { for(int i=20;i>=0;i--) if(x&(1<<i)) { if(!a[i]){a[i]=x;return;} else x^=a[i]; } } inline int query() { res=0; for(int i=20;i>=0;i--) res=max(res,res^a[i]); return res; } }xu,T[N*4]; void ins(int x) { if(!x)return; int hb=log2(x); if(!xu.a[hb])xu.a[hb]=x; if(xu.a[hb])ins(x^xu.a[hb]); } inline node merge(node a,node b) { xu=b; for(int i=0;i<=20;i++) ins(a.a[i]); return xu; } void build(int nod,int l,int r) { if(l==r) { T[nod].insert(read()); return; } build(nod*2,l,mid); build(nod*2+1,mid+1,r); T[nod]=merge(T[nod*2],T[nod*2+1]); } node find(int nod,int l,int r,int L,int R) { if(L==l&&R==r)return T[nod]; if(R<=mid)return find(nod*2,l,mid,L,R); else if(L>mid)return find(nod*2+1,mid+1,r,L,R); else return(merge(find(nod*2,l,mid,L,mid),find(nod*2+1,mid+1,r,mid+1,R))); } int main() { n=read(); build(1,1,n); m=read(); while(m--) { l=read();r=read(); node ans=find(1,1,n,l,r); printf("%d\n",ans.query()); } return 0; }
这题还可以用线性基和整体二分配合使用。(这里不过多解释)
正解是线性基+贪心。
表示1-i,中j这个位上的数
表示1-i,中j这个位上的数最接近i的是哪个位置
然后贪心一下就好了
#include using namespace std; const int N=500005; int n,m,b[N][25],p[N][25]; #define gc getchar inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } void get(int x,int k,int r) { for(int i=20;i>=0;i--) if(x&(1<<i)) { if(!b[r][i]) { b[r][i]=x; p[r][i]=k; return; } if(p[r][i]<k) { swap(p[r][i],k); swap(x,b[r][i]); } x^=b[r][i]; } } int solve(int l, int r) { int ans=0; for(int i=20;i>=0;i--) if(p[r][i]>=l)ans=max(ans,ans^b[r][i]); return ans; } int main() { n=read(); for(int i=1;i<=n;i++) { int x=read(); memcpy(b[i],b[i-1],sizeof(b[i])); memcpy(p[i],p[i-1],sizeof(p[i])); get(x,i,i); } m=read(); while(m--) { int l=read(),r=read(); printf("%d\n",solve(l,r)); } return 0; }
例题:
题目:【模板】线性基
这题就是给你n个数,求他们的最大异或值是多少。
题解:
这不就是线性基的模板题吗,只要一个插入,一个查询最大值即可。
代码:
#include using namespace std; #define int long long int n,x,ans,p[55]; void find(int x) { for(int i=51;i>=0;i--) { if((x>>i)==0)continue; if(!p[i]) { p[i]=x; break; } x^=p[i]; } } signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",&x); find(x); } for(int i=51;i>=0;i--) if((ans^p[i])>ans)ans^=p[i]; cout<<ans; return 0; }
题目:[BJWC2011]元素
给定一些矿石的编号与价值,输出得到最大的价值和,要求选定物品的编号异或之和不为0.
题解:
这题的做法是线性基+贪心
贪心就是将所有矿石的魔力值降序排列。然后把元素编号x插入线性基里去维护。
若是插入结束后 x 的值不为 0 说明选择它不会导致选择的矿石元素编号异或起来为0
那我们就把魔力值累加起来就行了。
代码:
#include using namespace std; #define int long long int n,ans,p[105]; struct node{ int id,val; }a[2005]; bool cmp(node a,node b) { return a.val>b.val; } bool find(int x) { for(int i=105;i>=0;i--) { if((x&(1ll<<i))==0ll)continue; if(p[i])x^=p[i]; else{p[i]=x;return true;} } return false; } signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].id,&a[i].val); sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++) if(find(a[i].id))ans+=a[i].val; cout<<ans; }
再来一题模板题,考验一下大家。
题目:牛客xor序列
小a有n个数,他提出了一个很有意思的问题:他想知道对于任意的x, y,能否将x与这n个数中的任意多个数异或任意多次后变为y
题解:
首先知道一个性质:x^y=z z^x=y z^y=x
那不就是简单的插入和判断x^y是否存在吗
代码:
#include using namespace std; #define int long long int n,x,y,m,p[55]; void add(int x) { for(int i=50;i>=0;i--) { if((x>>i)==0)continue; if(!p[i]) { p[i]=x; break; } x^=p[i]; } } bool pd(int x) { for(int i=50;i>=0;i--) { if((x>>i)==0)continue; if(!p[i])return false; x^=p[i]; } return true; } signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",&x); add(x); } scanf("%lld",&m); while(m--) { scanf("%lld%lld",&x,&y); if(pd(x^y))puts("YES"); else puts("NO"); } return 0; }
如果你觉得简单,你可以去做一下牛客知识点练习 > 线性基 。
请大家多多支持,谢谢。