线性基
看了好多博客,,,,
线性基是一组数,是一个集合的产物,一般用来求原集合子集异或和极值的问题。
有什么作用?
通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。
但同时它这组数的大小只有原集合最大数的二进制位那麽多 (60 多位),
线性基与原集合关于异或作用一样,但是他是最简的,好像线性代数里面叫做 基。
构造
求线性基时将一个数的二进制位看成一个向量。假定原集合为
数组,线性基为
对于每个
,按二进制从高位到低位扫,找最高位的 1,假如
的第
位是最高位的 1。
为 0 的话,即不存在,直接插入
;否则将
^=
,继续扫
其实相当于不断的将
const int base = 60; // 一般long long 也就18位,即 60 多位二进制
int a[N], p[base+3];
for(int i = 0; i < n; i++){ // 对于每个数 a[i]
for (int j = base; j >= 0; j--){ // 从高位到低位找 a[i] 最高位 1 的位置
if(a[i] >> j & 1) // 跳过前导 0
if(!p[j]){ // 该行没数,直接填
p[j] = a[i];
break; // 填完跳出
}else{
a[i] ^= p[j]; // 该行有数,异或之后继续看 a[i] 低位
}
}
}
性质
- 线性基中的每个向量的二进制最高位均不同,并且,我们称二进制最高位为第 i 位的元素称为“线性基的第 i 位”。
- 原集合
- 所在线性空间中每个元素都有唯一的方案由线性基中元素异或得到。
这样我们得到的
数组是一个上三角矩阵,而且元素全都是 0, 1。
类似于这样:
10010
01001
00100
00000
00001
可以知道线性基有多少组非零向量。
进一步简化
数组可以得到很多信息。下面两种方式将
for(int i = 0; i < n; i++){ // 对于每个数 a[i]
for (int j = base; j >= 0; j--){ // 从高位到低位找 a[i] 的第一个不为 0 的数的位置
if(a[i] >> j == 0)
continue; // 跳过前导 0
if(!p[j]){ // 该行没数,直接填
p[j] = a[i];
for (int k = j - 1; k >= 0; k--) // 使第 p[j] 只有第 j 位为 1
if(p[k] && p[j]>>k&1)
p[j] ^= p[k]; // 下面的行消自己
for (int k = j + 1; k <= base; k++)
if(p[k] >> j&1)
p[k] ^= p[j]; // 自己消上面的行
break;
}else{
a[i] ^= p[j]; // 该行有数
}
}
}
for(int i = base; i >= 0; i--){
if(!p[i])
continue;
for (int j = i + 1; j < base; j++){
if(p[j]>>i&1)
p[j] ^= p[i];
}
}
得到对角矩阵之后可以算第
// 求线性基时加上
for (int i = 0; i <= base; i++){ // 后面查询用到,存非零位
if(p[i]){
v.push_back(p[i]);
}
}
ll ask(ll x){ // 查询第 k 小
if(v.size() != n) // 线性有关,可以异或出来 0
x--;
if(!x)
return 0; // 第一小的数是 0
if(x >= (1LL << v.size())) // x 超出所有能得到的值数量的范围
return -1;
// 能得到 0,x 已经 -1了,x 最多达到2^v.size()-1;
// 不等得到 0,x 最多达到v.size()-1
ll ans = 0;
for(int i = 0; i <= base; i++){ // 将第 i 为异或起来
if(x >> i&1){ // 第 i 位为 1
ans ^= v[i];
}
}
return ans;
}
参考
https://blog.sengxian.com/algorithms/linear-basis