线形基(来自OI Wiki):
线性基是向量空间的一组基,通常可以解决有关异或的一些题目。
通俗一点的讲法就是由一个集合构造出来的另一个集合,它有以下几个性质:
-
线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
-
线性基是满足性质 1 的最小的集合。
-
线性基没有异或和为 0 的子集。
-
线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
-
线性基中每个元素的二进制最高位互不相同。
对于线性基的数组a[],a[i]表示最高位的1在第i位的值
构造线性基的方法:对原集合的每一个数p转化为二级制,从高位向低位扫,对于第i位是1,若a[i]==0,令a[x]=p,扫描结束;
若a[i]!=0,令p^=a[i],继续扫描。
代码:
void insert(int p) {
//具体从最高位多少开始扫描,视具体情况而定
for(int i=30;i>=0;i--) {
//若i>31,使用(1LL)<<i
if(p&1<<i){
if(!a[i]) {
a[i]=p;
break;
}
p^=a[i];
}
}
}
通过上面插入过程,我们可以知道对一个数判断其是否可以被异或出来,如果成功插入则不可,否则可以。
线性基模板:
//插入
void insert(int p) {
//具体从最高位多少开始扫描,视具体情况而定
for(int i=30;i>=0;i--) {
//若i>31,使用(1LL)<<i
if((p>>i)&1){
if(!a[i]) {
a[i]=p;
break;
}
p^=a[i];
}
}
}
//判断是否可以由原集合某些子集异或得到
bool check(int x) {
for(int i=30;i>=0;--i) {
if((x>>i)&1) {
if(!a[i]) return 0;
x^=a[i];
}
}
return 1;
}
//求最大值
int get_max() {
int ans=0;
for(int i=30;i>=0;--i) ans=max(ans,ans^a[i]);
return ans;
}
//求最小值
int get_min() {
int ans=0;
for(int i=0;i<=30;++i) if(a[i]) return a[i];
}
//求第k小的值,对于每一个a[i],
//枚举 j = 1->i
//如果 a[i]的第j位为1,那么d[i]异或d[j−1]。
//然后对k转化为二进制,如果k第i为1,res^=a[i]
ll get_kth(ll k) {
//处理线性基
for(int i=1;i<=60;i++)
for(int j=1;j<=i;j++)
if(a[i]&(1LL<<(j-1))) a[i]^=a[j-1];
ll res=0;
for(int i=1;i<=60;i++) {
if(a[i]) {
if(k%2) ans^=a[i];
k/=2;
}
}
return res;
}
//两个线性基合并 修改一下插入函数 inse(int *a,int x)
void combine(int *a,int *b) {
for(int i=30;i>=0;i--) {
if(b[i]) inse(a,b[i]);
}
}