做一下整理:
1、线性基:
若干数的线性基是一组数p1,p2,...pn,其中px的最高位的1在第x位。
通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。
2、性质:
性质
- 设线性基的异或集合中不存在0。
- 线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
- 线性基二进制最高位互不相同。
- 如果线性基是满的,它的异或集合为[1,2n−1]。
- 线性基中元素互相异或,异或集合不变。
3、线性基的构造法:
对每一个数a从高位到低位扫,扫到第x位为1时,若px不存在,则px=a并结束此数的扫描,否则令a = a xor px。
void build(int n) {
for(int i = 1; i <= n; i++) {
for(int j = 60; j >= 0; j--) {
if((a[i] >> j) & 1) {
if(!p[j]) {
p[j] = a[i];
break;
}
a[i] ^= p[j];
}
}
}
}
两个线性基合并: 把p2中的线性基,从大到小都加到p1中即可
// p2 加到 p1 上
void merge() {
int x;
for(int i = 60; i >= 0; i--) {
if(p2[i]) {
x = p2[i];
for(int j = 60; j >= 0; j--) {
if((x >> j) & 1) {
if(!p[j]) {
p[j] = x;
break;
}
x ^= p[j];
}
}
}
}
}
4、查询
查询最大值:从高位开始异或,得到的结果大于当前值就更新
ll querymax() {
ll ans = 0;
for(int i = 60; i >= 0; i--)
if(ans < (ans ^ p[i]))
ans ^= p[i];
return ans;
}
查询最小值:存在最低位的数即为最小值
ll querymin() {
for(int i = 0; i <= 60; i++)
if(p[i])
return p[i];
return 0;
}
查询某个值k是否存在:从高位开始异或,看是否能形成k
bool judge(ll k) {
for(int i = 60; i >= 0; i--)
if((k >> i) & 1)
k = k ^ p[i];
return k == 0;
}
查询第k小的数:我们要将线性基改造成每一位相互独立,具体操作就是如果i<j,pj的第i位是1,就将pj异或上pi。经过一系列操作之后,对于二进制的某一位i。只有pi的这一位是1,其他都是0。 所以查询的时候将k二进制拆分,对于1的位就异或上对应的线性基。 最终得出的答案就是k小值。
void rebuild() {
cnt = 0;
for(int i = 60; i >= 0; i--)
for(int j = i - 1; j >= 0; j--)
if((p[i] >> j) & 1)
p[i] ^= p[j];
for(int i = 0; i <= 60; i++)
if(p[i])
p[cnt++] = p[i];
}
ll querykth(ll k) {
ll ans = 0;
if(k >= (1LL << cnt)) return -1;
for(int i = 60; i >= 0; i--)
if((k >> i) & 1)
ans ^= p[i];
return ans;
}
5、应用:看一下这个大佬的博客吧:https://www.cnblogs.com/vb4896/p/6149022.html
应用六:
我就加一个,多次查询区间的,取任意个数的异或和最大,这就是HDU第一场的一个题,这就要用一个二维的记录一下,并且从高到低位保存的是最右边的线性基,并且记录下位置,以便后边查询区间的时候进行判断:
链接:https://blog.csdn.net/mmk27_word/article/details/96898031