线性基
线性基可以理解为 原集合的一个子集,但是可以 线性表示原集合中所有的元素。(按线性代数中来说就是一个最大无关组,或者说基础解系)
ACM中的线性基一般就用于 求异或和的最值(任意选取元素使得异或和最大),线性基里集合的异或组合能表示出原序列的异或组合。(异或组合只能 *1、*0、异或)
关于线性基在线性代数中的定义和证明挺麻烦…就不管了 (因为懒 ) 。
异或运算是按二进制位运算(可以理解为按位相加不进位),所以为了让线性基能够表示原序列,那么应当 按位进行构建,同时线性基中的 元素是原序列的异或组合。
线性基的构建
原序列 a: a[1], a[2], … a[n]
线性基 d[max_base]: d[i]是最高位为第i+1位(即第i+1位一定为1)的一个原序列的异或组合
线性基写成类似矩阵的形式正好是一个上三角矩阵,例如 当max_base=6
1***** d[5]
01**** d[4]
001*** d[3]
0001** d[2]
00001* d[1]
000001 d[0]
这样看可以发现线性基的构建就是对排好的原序列进行高斯消元。
可以发现若线性基填满了,该线性基甚至能表示 0 ~ 2max_base 的所有数(即能控制所有二进制位的0、1)
int a[maxn],d[max_base];
void ins(int x)
{
//从高位到低位进行插入
for(int i=max_base;i>=0;i--)
{
if(x&(1<<i)) //若第i+1位为1 (由于从高位开始遍历,所以第i+1位即x的最高位)
//若为LL,则要改为 (x&(1LL<<i))
{
if(!d[i]) //若该位还无元素
{
d[i]=x; //则放入
break;
}
else
x^=d[i] //让x的第i+1位变为0,同时得到x的一个新异或组合
}
}
}
void built()
{
memset(d,0,sizeof(d)); //全部置零
for(int i=1;i<=n;i++)
ins(a[i]);
}
线性基的基本应用
主要是利用贪心的思想去应用求得的线性基d[i](线性基可以更好地可以表示原序列),若d[i] ≠ 0,则d[i]的第 i+1 位一定为1(且是最高位)
①异或和最大值
从高位到低位贪心选取,很好理解,因为d[i]的第i+1位是1,高位为1权值更大。
int query_max()
{
int ans=0; //若是求和 num 异或的最大值,令 ans = num即可
for(int i=max_base;i>=0;i--)
{
if(ans^d[i]>ans) //若可以更大,则选用
ans=ans^d[i]; //也可以直接写成 ans=max(ans, ans^d[i])
}
return ans;
}
②异或和最小值
明显就是最小的那个d[i],即从低位到高位,找到第一个非空的d[i]。
注意:如果 非空d[i]个数 < 原序列总数n,异或和 最小值是0
int query_min()
{
if(cnt<n) //非空d[i]个数 < 原序列总数n
return 0;
for(int i=0;i<=max_base;i++)
{
if(!d[i])
return d[i];
}
}
③异或判特值(num能否由原序列异或得到)
异或是二进制位相同得到0,相异得到1,所以:
要证明 x = num (x即原序列的异或组合)
即证 x ^ num = 0
所以只要贪心处理,线性基与num从高位到低位异或,尽量让num减小。
注意: 若 num本身就是0,那么只有当 非空 d[i] 个数 < 原序列总数n, 才可以表示。
bool query_num(int num)
{
if(num==0) //若num为0
{
if(cnt<n)
return true;
else
return false;
}
for(int i=max_base;i>=0;i--)
{
if(num^d[i]<num)
num=num^d[i];
}
//其实num最后得到的值也正是num和原序列异或的最小值
if(num==0)
return true;
else
return false;
}
④异或和的个数(原序列有多少个不同的(非零)异或和)
由于求得的线性基中的元素是线性无关的(独立的),那么其能组合出的异或和的个数就是答案
即
2
非
空
d
[
i
]
的
个
数
−
1
2^{非空d[i]的个数}-1
2非空d[i]的个数−1
即 ( 1<<非空d[i]的个数 ) - 1 ( 减1 是因为不能一个都不选)
注意: 若 非空 d[i] 个数 < 原序列总数n,则说明还可以表示 0,个数应当+1
⑤第k小的异或和(HDU-3949)
先对原线性d基进行重建,对于d[i],除第i+1位(即最高位)外尽可能化为0(有点像矩阵化为标准型)
然后对k进行二进制拆分,若第1位为1,则异或第1个非空d[],… ,若第 i 位为1,则异或第 i 个非空d[]。(d[]按其下标递增编号)
void rebuilt() //重构线性基
{
for(int i=max_base;i>=0;i--)
{
for(int j=i-1;j>=0;j--)
{
if(d[i]&(1LL<<j)) //利用之后的d[j]来清零
d[i]^=d[j];
}
}
cnt=0; //非零d[]个数
for(int i=0;i<=max_base;i++)
{
if(d[i])
D[cnt++]=d[i];
}
}
LL query_kth(LL k)
{
LL ans=0;
if(cnt<n) //cnt<N说明原序列可异或得到0,则k--
k--; //因为这里的方法线性基异或组合无法得到0
if(k>(1LL<<cnt)-1) //若 k>(非零)异或和总数,说明不存在第k小
return -1;
for(int i=max_base;i>=0;i--)
{
if(k&(1LL<<i))
ans^=D[i];
}
return ans;
}