线性基
本文在学习了博客线性基学习笔记,总结自己的一些理解
处理的问题
对于一组数,通过构造一组线性无关的基底来求解异或最值、异或第k小值以及某值是否可得到的问题。
构造方法
维护一组线性基:对于每一个给定的数,对其二进制表示,从高位到低位扫描,当发现第x位是1时,若当前最高位为x的基底(即bs[x])为空,则bs[x]置为该数,否则将该数异或上bs[x],继续往下扫描。
ll bs[70];
void insert(ll x)
{
for (int i=62;i>=0;--i)
{
if (x&(1ll<<i))
{
if (!bs[i])
{
bs[i]=x;
return;
}
x^=bs[i];
}
}
}
原理
对于给出的数,将其二进制表示看成一个向量,对这一组向量,按上述方法,构造出一组线性无关基底,满足下列性质:
* 最高位1的位置互不相同
* 该组基底能异或出的值的集合,与原来的那组数能异或出的值的集合,相同
* 任意一个可以用这些向量组合出的向量x,组合方式唯一
对于性质一,容易看出按照上述方法可以构造出来。
对于性质二,这是后面用来求解异或最值等问题的正确性的保证。证明这个性质的大概思想:令c=a xor b,则a和c可以得到b,则c可以代替b,加入本该是a、b的组中。
对于性质三,参考开头提到博客的证明方法:
假设x的组合方法不唯一, 也就是说 x=a1 xor a2 ⋯ ap = b1 xor b2 ⋯ bq
那么x xor x = 0 = a1 xor a2 ⋯ ap xor b1 xor b2 ⋯ bq
也就是说 可以用这个向量组里的向量组合出0向量, 与线性无关矛盾。 故组合方法唯一。
### 应用
#### 求解一组数异或最值问题
求最大值:从最高位开始,若异或上该基底可以使当前值变大,则异或。做到最低位结束,得到即为答案
```C++
ll max_qry(ll init=0) // init为初始值,若没有,则置为0;
{
for (int i=62;i>=0;--i)
{
if ((init^bs[i]) 求最小值:从低位往上找到的第一个非零基底。这里特别提一下,若有初始值,则应该仿照上述求最大值的方法,从**高位**开始贪心异或,能使其变小就异或上。
```C++
ll min_qry(ll init=0)
{
if (init==0)
{
for (int i=0;i<=62;++i)
if (bs[i])
return bs[i];
}
else
{
for (int i=62;i>=0;--i)
if ((init^bs[i]) 从高位向低位扫这个数的二进制表示,碰到值为1的位,则异或上最高位为这个位的基底。若最后该数变为0,则表示该数可以被这组基的子集构造出来。结合性质三感受一下。
```C++
bool check(ll x)
{
for (int i=62;i>=0;--i)
if (x&(1ll< 在原有线性基的基础上,重新构造一组基底。这组基底相比于原来的,每个基底变为通过异或得到的包含原来最高位的最小值。再对k二进制表示,异或上对应位的新基底即可。这里特别提一下,0的情况。如果构造基底的个数少于题目给定的数的个数,说明0使可以被异或出来的。那么对于k,要使k=k-1。
方法如下
```C++
vector tmp;
ll kth_query(int k)
{
for(int i=0;i<=62;++i)
{
if (!bs[i])
continue;
for(int j=i-1;j>=0;--j)
if(bs[i]&(1ll<=(1<