[笔记]线性基
注意
- long long
- 成功插入线性基之后记得返回
线性基用来干什么
维护一个序列的异或值:
记\(d[]\)为序列\(a[]\)的线性基,那么:
- \(d[]\)中任意一些元素异或的集合与\(a[]\)中任意一些元素异或的集合相同
- \(d[]\)中任意一些元素异或不为0
- \(d[]\)是所有满足条件1的集合中最小的
其中\(d[i]\)的第\(i\)位上为1
插入
从高位往低位找
- 如果当前为没有值,直接插入并返回
- 否则,把\(x\)异或上\(d[i]\),继续找
int INS(LL x){
tep(i,60,0){
if(!((x>>i)&1))continue;
if(!d[i]){
d[i]=x;
cnt++;//记录线性基中有多少元素
return 1;
}
x^=d[i];
}
return 0;//返回值表示是否能够成功插入(如果原来的线性基中已经存在某一些
//数异或和为X,那么x就没有必要被插入。这个时候a[]异或可以得到0,需要特判
}
查询
最大值
从高位往低位扫,如果\(ans\oplus d[i]>ans\),那么把\(ans\)异或上\(d[i]\),否则说明\(ans\)的第\(i\)为已经有\(1\),异或\(d[i]\)只会使答案变小,就skip
最小值
就是\(d[]\)里面最小的
第\(k\)小
就是求所有能异或得到的数中第\(k\)小的那个
Part1
先重构\(d[]\),使得对于每一个\(i\),最多只有一个\(d[j]\)的第\(i\)位上为1
rep(i,1,maxn)rep(j,0,i-1){
if((d[i]>>j)&1)d[i]^=d[j];
}
正确性:
\(d[i]\oplus d[j]\)后,线性基里的元素从\(d[i],d[j]\)变成了\(d[i]\oplus d[j],d[j]\),可以发现,这两个元素异或得到的集合还是不变的,所以整个集合的异或值也不会变
Part2
先上代码
rep(i,0,60){
if(d[i]!=0){
if(k&1)ans^=d[i];
k/=2;
}
}
没想到吧,二进制真是奇妙,自己琢磨吧
Part3 Notice
注意判断0。如果原序列可以异或得到0,那么k要减1
猴啦上代码啦
Code
#include<bits/stdc++.h>
#define rep(X,A,B) for(int X=A;X<=B;X++)
#define tep(X,A,B) for(int X=A;X>=B;X--)
#define LL long long
const int N=1000010;
const int M=2000010;
const int maxn=60;
using namespace std;
void read(int &x){
scanf("%d",&x);
}
int n,Q,flg,cnt=0;
LL d[N],tot;
int INS(LL x){
tep(i,60,0){
if(!((x>>i)&1))continue;
if(!d[i]){
d[i]=x;
cnt++;
return 1;
}
x^=d[i];
}
return 0;
}
void READ(){
LL x;
flg=1;
cnt=0;
read(n);
rep(i,0,60)d[i]=0;
rep(i,1,n)scanf("%lld",&x),flg&=INS(x);
rep(i,1,maxn)rep(j,0,i-1){
if((d[i]>>j)&1)d[i]^=d[j];
}
tot=1;
rep(i,1,cnt)tot*=2;tot--;
read(Q);
}
LL SOLVE(){
LL k,ans=0;
scanf("%lld",&k);
if(!flg)k--;
if(k>tot)return -1;
rep(i,0,60){
if(d[i]!=0){
if(k&1)ans^=d[i];
k/=2;
}
}
return ans;
}
int main(){
int T;
read(T);
rep(op,1,T){
printf("Case #%d:\n",op);
READ();
while(Q--)printf("%lld\n",SOLVE());
}
return 0;
}