当给出多个数据,求异或时,通常会使用线性基进行处理。
线性基是什么?
对于原数组,我们试图用最少个数作为基础,用这些数之间的异或就可以表示子集合的异或和,而这些数即基础,称为线性基。
这种特殊的基通常是由32位数字或64位数字构成。具体多少位取决于题目中所给数字集合中二进制下最高位位数。
例如int范围需要32位线性基,而long long范围需要64位基解决。
-------------------------------------------------------------------------------------------------------------------------------
线性基常用函数
插入
我们用数组a表示线性基,a[i] 表示线性基当中最高位为第i位的基。
基的插入构造过程为:
1.如果当前数v的最高位i所在的基a[i]已经存在,即a[i]=0,那么这个数通过基a[i]转化为低位的数:v^=a[i];
2.如果a[i]不存在,就把当前数作为基a[i]。
例如现在给出如下例子1100,1011,1110,1101,0001,0101
1.1100,因为a[4]==0 所以a[4]=1100
2.1011,a[4]存在,所以1011->0111,故a[3]=0111
3.1110,a[4]存在,->0010,a[2]=0010
4.1101,a[4]存在,->0001,a[1]=0001
5.0001,a[1]存在,->0000
6.0101,a[3]存在,->0010,a[2]存在,->0000
当基满后,就可以表示所有数了
void insert(int v)
{
for(int i=31;i>=1;i--)
{
int j=i-1;
if(v&(1<<j))//判断v的位数,只有v的位数等于j,才会继续
{
if(!a[i])//当前a[i]为空
{
a[i]=v;
break;
}
v^=a[i];
}
}
if(v==0)
a[0]=1;//可以通过异或和变为0,用于找最小值
}
-------------------------------------------------------------------------------------------------------------------------------
验证存在性
从高位到低位只要同位的最高位基存在就异或,到最后成功由这些基转化成0就说明存在。与插入的过程类似。
int findin(int v)
{
if(v==0&&a[0])
return 1;
for(int i=31;i>=1;i--)
{
if(v&(1<<j))//先找到同位数的
{
v^=a[i];//异或
if(!V)//异或完为0
return 1;
}
}
return 0;
}
-------------------------------------------------------------------------------------------------------------------------------
合并
把一个基作为基底,将另一个基的内容逐条插入即可(此处将a作为基底,b插入)。
void merge(int b[])
{
for(int i=31;i>=1;i--)
{
if(!b[i])
continue;
insert(b[i]);
}
}
-------------------------------------------------------------------------------------------------------------------------------
查找最大值
所有原数之间的异或和等价于基之间的异或和,所以找最大值,只需要判断所有基的取与不取即可找到最大值,从高位基开始找,取某个基时使答案变大,那么就是需要取的。
void findmax()
{
int ans=0;
for(int i=31;i>=1;i--)
{
if(ans^a[i]>ans)
{
ans^=ans;
}
}
return ans;
}
-------------------------------------------------------------------------------------------------------------------------------
查找最小值
最小位数的基肯定就是答案(需要注意一个基都没有的情况)。
int findmin()
{
if(a[0])
return 0;
for(int i=1;i<=31;i++)
{
if(a[i])
return a[i];
}
}