线性基的学习+总结

线性基的学习+总结

参考博客:线性基详解

(Summary:)

线性基四大性质

  1. 原序列里面的任意一个数都可以由线性基里面的一些数异或得到
  2. 线性基里面的任意一些数异或起来都不能得到 0
  3. 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
  4. 一个序列的线性基不唯一,只是元素数量唯一而已。

线性基的构造:

(add) 函数 ,将一个数 (x) 插入线性基

void add(long long x) {
    tot = 1;//tot 表示线性基元素的数量
    for (int i = 60; i >= 0; i--) {
        if(d[i]) tot++;
        if (x & (1ll << i)){
            if (d[i]) x ^= d[i];
            else {
                d[i] = x;
                break;//插入成功就退出
            }
        }
    }
}

如何求最大值

如何求在一个序列中,取若干个数,使得它们的异或和最大。

首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为 (0)

long long MaxVal() {
    long long ans = 0;
    for (int i = 60; i >= 0; i--)//记得从线性基的最高位开始
        if ((ans ^ d[i]) > ans)ans ^= d[i];
    return ans;
}

如何求最小值

注意,这里指的是用线性基内的元素能异或出的最小值。

显然就是最小的 (d[i]) 了,因为最小的 (d[i])无论异或谁都会变大。

如果是求整个序列能异或出的最小值而不是这个序列的线性基能异或出的最小值的话,要另外看一看有没有元素不能插入线性基,如果有,那么最小值就是 (0),否则依然是最小的 (d[i])

如何求第k小的值

从一个序列中取任意个元素进行异或,求能异或出的所有数字中第 (k) 小的那个。
void work()//处理线性基,使得对于 d[x]一定是第x位为1,可以异或成的最小值
{
    for (int i = 1; i <= 60; i++)
        for (int j = 1; j <= i; j++)
            if (d[i] & (1ll << (j - 1)))d[i] ^= d[j - 1];
            //注意这个i也是从小到大枚举的,所以异或完之后,对于d[i]转化成二进制之后,如果低x位为1,那么说明d[x]=0
}
long long k_th(long long  k) {
    if (k == 1 && tot < n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
    if (tot < n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
    work();
    long long ans = 0;
    for (int i = 0; i <= 60; i++) {
        if (d[i] != 0) {
            if (k % 2 == 1)ans ^= d[i];
            k /= 2;
            /*
             * 这里解释一下为什么是这样的
             * 首先明确此时的线性基已经是最小的了
             * 假设k的二进制表示是 1101,那么说明这个组成是第一个线性基存在,第二个不存在,第三个和第四个不存在
             * 这样表示的一定是第k个,前面就k-1个有:0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100
             */
        }
    }
}
原文地址:https://www.cnblogs.com/EchoZQN/p/13398482.html