我们可以将插入过程总结为以下几点:(对于插入一个\(w\)位的数\(x\))
- 如果\(x\)是\(0\),直接结束
- 判断集合内是否有\(w\)位的数,如果没有,向集合内插入\(x\),结束
- 否则找到集合内那个\(w\)位的数\(x'\),递归插入\(x\oplus x'\)
实际应用中其实不需要维护一个集合,因为位数两两不同,所以只需要按照位数索引即可。另外由于每次算完\(x\oplus x'\),位数必定会减少,所以完全可以把递归改为迭代。
typedef unsigned int type; // 假设值域在 2^32-1 以内
const int W = 31; // 对应这个值域,位数最大为 31
type basis[W + 1]; // 线性基,basis[i] 表示集合内位数为 i 的数, 没有就是 0
void ins(type x) // 插入一个 x
{
for (int i = W; i >= 1; i--)
{
if (x >> (i - 1))
{
if (basis[i] == 0)
{
basis[i] = x;
return;
}
x ^= basis[i];
}
}
}
应用
首先注意到这样处理之后,将空间复杂度由\(O(n)\)降到了\(O(W)\)(\(W\)是最大位数)。
判断一个数能否被xor出
原理和插入时是一样的,不再赘述。
bool uniqu(type x) // 判断x是否能被集合内的数表示出来,1为不能,0为能
{
for (int i = W; i >= 1; i--)
{
if (x >> (i - 1))
{
x ^= basis[i];
}
}
return x;
}
最大xor值
思考这样一个问题:有若干个数,可以取出任意个进行xor运算,问所能得到的结果的最大值。
根据线性基的定义,这些数取任意个进行xor运算所能得到的结果,与这些数的线性基取任意个进行xor运算所能得到的结果是一样的。
所以问题转化为在线性基上取若干个数进行xor运算,问最大结果。
根据思考4,首先应该最大化这个结果的位数。这一点不难做到,取线性基里位数最大的一个作为xor运算的一个操作数即可(即“当前答案”初始化为线性基里位数最大的一个数)。
然后从高到低,依次尝试把每一位弄成1。
注意到 \(a\oplus0=a\),所以说想让一个数发生改变,只能异或上1。
然后又因为线性基里“能保证第\(w\)位为1并且不会在异或之后导致答案里比\(w\)高的位由1变成0”的数正好就是位数为\(w\)的数。
所以我们就可以判断如果当前答案异或上线性基位数为\(w\)的数能使第\(w\)位由0变成1,那么就把这个数选入操作数,更新当前答案。
实际操作时,因为保证第\(w\)位是1,所以要么0变1,要么1变0,也就是一个变大,一个变小;而我们想要的0变1是使得当前答案变大的操作,所以只需判断异或上这个数之后当前答案会不会变大即可。
当然也无需单独将ans初始化为线性基里位数最大的一个。
type maxxor() // 求能xor出的最大值
{
type ans = 0;
for (int i = W; i >= 1; i--)
{
ans = max(ans, ans ^ basis[i]);
}
return ans;
}
另外一个类似的操作是给定一个数,求取出若干个数与这个数xor之后能得到的最大值。
直接把当前答案初始化为给定的这个数即可。
type maxxor(type x) // 求与x能xor出的最大值
{
type ans = x;
for (int i = W; i >= 1; i--)
{
ans = max(ans, ans ^ basis[i]);
}
return ans;
}
合并
如何合并两个线性基\(a\)和\(b\)?
没有好做法。直接把\(b\)里的所有数暴力插到\(a\)里。