线性基学习笔记
简介
线性基是一种擅长处理异或问题的数据结构。设值域为\([1,N]\),就可以用一个长度为\(\lceil \log_2N \rceil\)的数组来描述一个线性基。特别地,线性基第\(i\)位上的数二进制下最高位也为第\(i\)位。
一个线性基满足,对于它所表示的所有数的集合\(S\),\(S\)中任意多个数异或所得的结果均能表示为线性基中的元素互相异或的结果,即意,线性基能使用异或运算来表示原数集使用异或运算能表示的所有数。运用这个性质,我们可以极大地缩小异或操作所需的查询次数。
插入和判断
我们考虑插入的操作,令插入的数为\(x\),考虑\(x\)的二进制最高位\(i\),
- 若线性基的第\(i\)位为\(0\),则直接在该位插入\(x\),退出;
- 若线性基的第\(i\)位已经有值\(a_i\),则\(x = x\oplus a_i\),重复以上操作直到\(x=0\)。
如果退出时\(x=0\),则此时线性基已经可以表示原先的\(x\)了;反之,则说明为了表示\(x\),往线性基中加入了一个新元素。
很容易证明这样复杂度为\(\log_2x\),也可以用这种方法判断能否通过原数列异或得到一个数\(x\)。
代码:
void insert(ll x)
{
for (ll i = 55; i >= 0; i -- )
{
if (x & (1ll << i))
{
if (!b[i])
{
b[i] = x;
tot ++ ;//线性基的子集能异或出来的不同的数(包括0)的个数就是2^tot
break;
}
else x ^= b[i];
}
}
return;
}
int check(ll x)
{
for(ll i = 55; i >= 0; i -- )
if (x & (1ll << i)) x ^= b[i];
return x == 0;
}
查询异或最值
查询最小值相对比较简单。考虑插入的过程,因为每一次跳转操作,\(x\)的二进制最高位必定单调降低,所以不可能插入两个二进制最高位相同的数。而此时,线性基中最小值异或上其他数,必定会增大。所以,直接输出线性基中的最小值即可。
考虑异或最大值,从高到低遍历线性基,考虑到第\(i\)位时,如果当前的答案\(x\)第\(i\)位为\(0\),就将\(x\)异或上\(a_i\);否则不做任何操作。显然,每次操作后答案不会变劣,最终的\(x\)即为答案。
同样,我们考虑对于一个数\(x\),它与原数列中的数异或的最值如何获得。用与序列异或最大值类似的贪心即可解决。
代码:
ll qmax()
{
ll res = 0;
for (ll i = 55; i >= 0; i -- )
res = max(res, res ^ b[i]);
return res;
}
ll qmin()
{
ll res = 0;
if (flag) return 0;//能得到0
for (ll i = 0; i <= 55; i ++ )
if (b[i]) return b[i];
}