不知道最初是从哪个角度发现的
蒟蒻我最初是从玄学的线性代数与向量思想开始学的,然后因为不会高斯消元转入另一种。
其实对于线性基来说,只要搞懂一个关系式子:
axorb=c,cxora=b,bxorc=a;
a
x
o
r
b
=
c
,
c
x
o
r
a
=
b
,
b
x
o
r
c
=
a
;
也就是说,知二求一。那么将一堆数合成一个线性基,应用的也是这个道理;
线性基长什么样
就是一个一维数组, a[i] a [ i ] 等于的数满足该数二进制表示下,最高有一的那一位为i;比如 a[3]=(110)2 a [ 3 ] = ( 110 ) 2
下面具体讲操作
给你一个线性基,给你一个数,问这个数是否属于该线性基的张成。
最简单的想法:2^n枚举;但这显然过于暴力。
这样想:如果属于,那么用线性基异或出来的数异或这个数等于0。我们只要让这个数去异或线性基中的数,使它竭力靠近零。
如何竭力靠近0?
举个例子:一个数为 (1011001101)2 ( 1011001101 ) 2 ,然后发现了第十位为一。最终结果第十位不为1,肯定要异或掉就拿 a[10] a [ 10 ] 来异或。这样就对10位以上的更高位保持为0没有影响.然后再看第九位.
唯一值得注意的,本身是零要特判。
给你一堆数构造他们的线性基。
这一个操作实际上结合了定义之后就变得简单。只要你加入的这一个数有一的最高位在原线性基中还没有出现,直接赋值即可。如果出现了,那么就与这一位异或一下。
讲的可能不太清楚,上一份代码就懂了
for(int i=1;i<=n;i++) {
for(int j=62;j>=0;j--) {
if(!(a[i]>>j)) continue;//对线性基的这一位没有贡献
if(!p[j]) { p[j]=a[i]; break; }//选入线性基中
a[i]^=p[j];
}
}
给你一个线性基要你求能构造出来的第k小
这一个的做法其实有点利用二进制思想。如果现在有 (1)2,(10)2,(100)2,(1000)2,(10000)2,(100000)2.... ( 1 ) 2 , ( 10 ) 2 , ( 100 ) 2 , ( 1000 ) 2 , ( 10000 ) 2 , ( 100000 ) 2 . . . . 这些数,要你构造他们异或和的第k大,你可能会觉得这太容易了。这是什么原理呢??掘其本质,你会发现他们无论怎么异或,原有位置上的1永远不会异或变成零,这又是由于如果他们有一位为一,其它数这个位上绝对不为一。如果我们的线性基有这一个性质就好了。也就是说,按定义 a[i] a [ i ] 表示有一的最高位是 i i ,那如果其余的任意的第i位都为0,那就满足了 a[i] a [ i ] 最高位上的i怎么也不会异或成零。
现在最重要的一点是如何构造?假设现在这个线性基已经构造好了,由于对于 a[i],a[k],i>k a [ i ] , a [ k ] , i > k , a[k] a [ k ] 显然不会影响 a[i] a [ i ] 只要扫两遍就行了
上个代码
il void rebuild(){ for (RG ll i=62;i>=0;--i) for (RG ll j=i-1;j>=0;--j) if (p[i]>>j&1) p[i]^=p[j]; for (RG ll i=0;i<=62;++i) if (p[i]) [cnt++]=p[i]; return; } il ll query_kth(RG ll k){ if (k>=(1LL<<cnt)) return -1; RG ll res=0; for (RG ll i=62;i>=0;--i) if (k>>i&1) res^=d[i]; return res; }
最后讲一讲个人搞出来的时间戳线性基。
如果有 n n 个数依次加入线性基,在加入过程中,不断询问,区间 [l,r] [ l , r ] 的数能异或出来的最大的数是多少。遮盖怎么做?
这就需要再次仔细研究线性基的性质。这次我们要考虑加入的时间。现在加入 a,b,c,d,e a , b , c , d , e 五个数,加入到d时,询问 b,c,d b , c , d 能够异或的最大数是多少。
这时就显然不能考虑 a a 的影响。在这里还要插入一个小技巧,就是把询问区间按左端点与右端点排序。这样你的询问就是有序的了(嗯,这个方法真的常用)。
我们这样做后 显然 要使得
- 排除线性基中所有被 影响了的数
- 剩下的这些数中是 b,c,d b , c , d 的线性基
我们的任务是这两点。乍一看,不可能实现,实际上由于加入是一个不可逆的过程,我们无法用线性基去删除一个元素,而询问区间是有序的,对每一个线性基中的数打上一个时间戳就显得尤为重要。
我们不妨设线性基中的每一个数都有一个时间戳 d d 这个代表的意义是:影响该位的最小位置为 d d <script type="math/tex" id="MathJax-Element-759">d</script>
有了这个时间戳后,被小位置更新过的一定不包含被大位置更新过。
每次加入一个数后,直接处理以该数位置为右端点的所有询问。
详细见代码
#include<bits/stdc++.h>
using namespace std;
inline char gc(){
static char buf[1<<6],*p1=buf,*p2=buf;
return (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<6,stdin),p1==p2))?EOF:*p1++;
}
template <class T>
inline void read(T&data){
register char ch=0;
data=0;
while(ch<'0'||ch>'9')ch=gc();
while(ch<='9'&&ch>='0'){
data=(data<<3)+(data<<1)+(ch^48);
ch=gc();
}
return ;
}
const int _ (1e6+5);
int n,Time[_],L[_],head[_],nt[_],a[_],poww[_],Xor[32],ans[_],m;
int main(){
freopen("e.in","r",stdin);
read(n);
read(m);
for(register int i=1;i<=n;++i){
read(a[i]);
}
for(register int i=1,R;i<=m;++i){
read(L[i]);
read(R);
nt[i]=head[R],head[R]=i;
}
for(register int i=1;i<=4;++i){
register int I=i;
for(register int j=30;~j;--j){
if((1<<j)&a[i]){
if(!Xor[j]){
Xor[j]=a[i];
Time[j]=I;
break;
}
if(Time[j]<I){
swap(Time[j],I);
swap(Xor[j],a[i]);
}
a[i]^=Xor[j];
}
}
for(register int j=head[i];j;j=nt[j]){
for(register int k=30;~k;--k){
if(Time[k]>=L[j])ans[j]=max(ans[j],ans[j]^Xor[k]);
}
}
}
for(register int i=1;i<=m;++i){
cout<<ans[i]<<endl;
}
}