线性基,可以拿来搞异或一类的东西。它可以表示出原数组互相异或能异或出的所有值。
一些性质:
构造:我不太懂。
void ins(int x){//将线性基中插入数 for(int i=63;i>=0;i--){ if(!(x>>i))continue; if(!p[i]){//如果当前位有值且线性基当前位为0则改 p[i]=x;break; } x^=p[i];//反之异或当前值 } } bool query(int x){//查询一个数能否被线性基中数表出 for(int i=63;i>=0;i--){ if((x>>i)&1)x^=p[i];//当前位有就异或一下 } if(x==0)return false; return true; }
大概原理是线性基的当前位的最高位就是这一位(举个例子,p[60]的最高位是1<<60)。然后贪心就可以搞出来。
判断一组数能异或出的最大值也差不多的贪心就行。
int ans=0; for(int i=63;i>=0;i--)ans=max(ans,ans^p[i]);
最小值同理,但是注意特判0,直接看有没有元素不能插入线性基就行了。
还有一个结论:设线性基大小为\(size\),原数组大小为\(n\),那么显然有\(2^{size}\)种不同的异或结果。而原数组有\(2^n\)种有重复的结果,每个结果重复\(2^{n-size}\)次(也就是平均分布)。
然后是一些科技:
题意:一个序列,两种操作:区间选数异或最大值,序列末尾加一个数。强制在线。
前缀线性基,顾名思义,我们对每个前缀都建一个线性基,这样我们就只需要统计一个询问\([l,r]\)中\(l\)之后的部分对\([1,r]\)的贡献。
然后显然,如果插入两个数的效果相同,这个数的位置越靠后越好。所以我们记录一下线性基中的每个数的位置,如果能更改成位置更优的就更改一下。怎么搞?我们线性基插入的时候,如果一个位置有值且位置更优,就把要插入的数和线性基内的数换一下。代码实现如下:
#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> using namespace std; struct node{ int p[32],pos[32];//值和位置 void ins(int x,int val){//x位置 val值 for(int i=31;i>=0;i--){ if((val>>i)&1){ if(!p[i]){ p[i]=val;pos[i]=x;return;//没有就正常插入 } else if(pos[i]<x){ swap(pos[i],x);swap(p[i],val);//有的话就更改一下 } val^=p[i];//正常流程 } } } int query(int l){ int ans=0; for(int i=31;i>=0;i--){ if(p[i]&&pos[i]>=l)ans=max(ans,ans^p[i]);//只有位置在l后面才更新 } return ans; } }a[500010]; void update(int x){ n++;a[n]=a[n-1];a[n].ins(n,x); } int find(int l,int r){ return a[r].query(l); }
题意:维护一堆数的线性基,要求插入、删除、查询异或最大值。强制在线。
首先如果不强制在线显然可以线段树分治乱搞。
我们可以维护线性基内所有的原数,插入就正常插入,删除开始分类讨论。(咕)