鸣谢Yveh博主的博客!
一、定义
设数集T的值域范围为[1,2n-1] ,
T的线性基是T的一个子集A={a1,a2,a3,…,an}。
A中元素互相xor所形成的异或集合,等价于原数集T的元素互相xor形成的异或集合。
(可以理解为将原数集进行了压缩)
例:A={1,2,4}可以是数集T=[1,7]的一个线性基,因为A中数的异或和可以表示T中任意的一个数。
我们如果把上例的十进制数拆成二进制数就能知道,这与线代中“一组线性无关的向量可张起一个向量空间”是一个道理。
A
=
[
1
0
0
0
1
0
0
0
1
]
A=\left[ \begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]
A=⎣⎡100010001⎦⎤
当然,如果选取A={1,3,5}亦可……此时:
A
=
[
1
0
1
0
1
1
0
0
1
]
A=\left[ \begin{matrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 0 & 0 & 1 \\ \end{matrix} \right]
A=⎣⎡100010111⎦⎤
(A经过初等行变换获得的最简的基底就是**A={ai|ai=2i,i=0,1,2……}**这组基底)
二、一些性质
1. 线性基的异或集合中不存在0。
- 就是不能从线性基内取出一些数,使得它们的异或和为0(十分显然,对吧)
2. 线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
- 大概就是说一个数只能由某几个数的异或和得到,并且找不到第二种异或和等于这个数的方案
3. 线性基二进制最高位互不相同。
- 只有ai位最高位为1,其它的a向量最高位都不能是第i位(稍后的插入元素会有代码过程)
4. 如果线性基是满的,它的异或集合为[1,2n−1]。
- 很显然
5. 线性基中元素互相异或,异或集合不变。
- 显然++
三、插入元素到线性基中
1.如果向线性基中插入数x,从高位到低位扫描它为1的二进制位。
2.扫描到第i时,如果ai不存在,就令ai=x,否则x=x⊗ai。
3.x的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了0。
(插入元素n的复杂度为O(logn))
bool insert(long long val){
for (int i=60;i>=0;i--)
if (val&(1LL<<i)){//扫描为1的二进制位
if (!a[i]){//如果不存在就把val放进线性基中
a[i]=val;
break;
}
val^=a[i];//存在的话就异或一下接着找
}
return val>0;//返回1即插入成功
}
四、暴力合并两个线性基
L_B merge(const L_B &n1,const L_B &n2){
L_B ret=n1;
for (int i=0;i<=60;i++)
if (n2.d[i])
ret.insert(n2.d[i]);
return ret;
}
五、一些具体应用
1.存在性问题
如果要查询x是否存于异或集合中,从高位到低位扫描x的为1的二进制位,且扫描到第i位的时候x=x⊗ai,如果在查询中途x变为了0,那么表示x存于线性基的异或集合中。
2.求异或集合中的最大值
很简单,从高位到低位扫描线性基,如果异或后可以使得答案变大,就异或到答案中去。
long long query_max(){
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
3.求异或集合中的最小值
long long query_min(){
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
……
六、模板(再次感谢Yveh博主!)
struct L_B{
long long d[61],p[61];
int cnt;
L_B(){
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
bool insert(long long val){
for (int i=60;i>=0;i--)
if (val&(1LL<<i)){
if (!d[i]){
d[i]=val;
break;
}
val^=d[i];
}
return val>0;
}
long long query_max(){
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
long long query_min(){
for (int i=0;i<=60;i++)
if (d[i]) return d[i];
return 0;
}
void rebuild(){//利用了高斯消元
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1LL<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i]) p[cnt++]=d[i];
}
long long kthquery(long long k){//求最小值
int ret=0;
if (k>=(1LL<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1LL<<i))
ret^=p[i];
return ret;
}
}
L_B merge(const L_B &n1,const L_B &n2){
L_B ret=n1;
for (int i=60;i>=0;i--)
if (n2.d[i]) ret.insert(n1.d[i]);
return ret;
}