给出一组数,构造出异或线性基。使得该组数所属空间内的所有数都可以由线性基异或表示。
扩展一下,我们可以利用异或线性基实现:
1.判断一个数能否表示成某数集子集的异或和;
2.求一个数表示成某数集子集异或和的方案数;
3.求某数集子集的最大/最小/第 k 大/第 k 小异或和;
4.求一个数在某数集子集异或和中的排名。
贪心法构造方法(b表示原数组如何组合出线性基):
class liner_base{
public:
//贪心法构造线性基
vector<bitset<101>> a;
vector<vector<int>> b;
int cnt;
int B;
liner_base(int x=100){
B = x;
a.resize(B + 1);
b.resize(B + 1);
}
int insert(bitset<101> x,int id){//插入x 是原数组第id个数
vector<int> v={id};//组合出x 需要的原数组的数的下标
for(int i=B;i>=0;i--){
if(x[i]){
if(a[i].count()==0){
a[i]= x;
b[i] = v;
cnt++;
return 1;//插入成功
}else{
x ^=a[i];
for(auto y:b[i]) v.push_back(y);//异或了第i个线性基,v要加上组成该线性基的数
}
}
}
return 0; //插入失败 当前插入的数与原线性基组线性相关了
}
int size() {
return cnt;
}
vector<int> ask(bitset<101> x){//查询x能否被线性基异或出来
vector<int> res;//构造出该线性基需要的原数组的数的下标
for(int i=B;i>=0;i--){
if(x[i]){
x^=a[i];
for(auto y:b[i]) res.push_back(y);
}
}
if(x.count()!=0) res.clear(); //代表x不能被线性基异或出来
return res;
}
};
1.查询原集合内任意几个元素$\text{xor}$的最大值,只需将线性基从高位向低位扫,若$ \text{xor}$上当前扫到的$ a_x$答案变大,就把答案异或上$ a_x$。
2.查询原集合内任意几个元素$ \text{xor}$ 的最小值,就是线性基集合所有元素中最小的那个。
3.查询某个数是否能被异或出来,类似于插入,如果最后插入的数 p 被异或成了 0,则能被异或出来。
例题
把所有灯状态看成一个二进制串,初始状态为s。则开关第i盏灯导致的状态变化同样可以用一个数表示。原状态异或上该数则可得到开关第i盏灯后的状态。则问题转化为用这n个数,能否异或出!s。即构造线性基,然后看线性基能否异或出!s,特别地要求操作灯的次数小于等于n,需要去重一次(开关同一盏灯无意义)。