数据结构:如何维护线性基

我们可以将插入过程总结为以下几点:(对于插入一个\(w\)位的数\(x\)

  1. 如果\(x\)\(0\)​,直接结束
  2. 判断集合内是否有\(w\)位的数,如果没有,向集合内插入\(x\),结束
  3. 否则找到集合内那个\(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\)里。

你可能感兴趣的:(数据结构:如何维护线性基)