线性基笔记

转自:https://www.bilibili.com/video/BV1ct411c7EP?from=search&seid=15895896037046660511

 

线性基

 

一,线性基的概念

线性基是一个满足 从原集合中选取任意多个数异或得到的值都能通过在线性基中选取一些数进行异或得到 的最小集合。可以说线性基是对原集合的压缩

 

二,线性基的性质

对于集合 ,将 其中的用  替换得到集合 。从集合 A 中选取任意多个数异或得到的值都能通过在集合B中选取一些数进行异或得到

证:

设 从原集合A 中选取一些数异或得到

    

如果选取的这些数中不包含 ,那么这些数也在集合 B 中 ,X 也能通过在 B 中选取这些数异或得到;如果包含,则可以通过用替换,综上,x 也能由集合 B 得到

 

利用性质 ①,对于任意一个集合,如果其中有两个元素的最高位相等,可以用异或将其中的一个元素替换掉。如此下去,可以让该集合中 以某一位作为最高位的数唯一。

如 a1 = 1 1 1 (二进制),a2 = 1 0 1(二进制)。此时权为 2^2 的位不唯一,我们可以用 a1^a2 替换  a2,这样 a 2 就变成 0 1 0(二进制),此时权为 2^2 的位就唯一了。

也就是说对于一个集合,如果集合中的每个元素的值小于 10^18,那么该集合与一个大小为 63 的集合等价。因为 集合中元素最大值最多就 63 位(2^63-1,有63位),用上面的方法将每一位作为最高位的数唯一,就得到一个大小为 63 的集合。

 

注意:线性基无法 异或得到 0,如果原集合可以异或得到 0 需要 特判

证明:如果 原集合有 a1^a2^a3 == 0,可得 a1^a2 == a3,如果 a1 和 a2 在线性基中的话,a3 就不会加入线性基 ,因为这样就不是最小的集合了。

 

代码与应用:

①:往线性基中插入 x

// 根据 x 的二进制,为x选择一个 最高位唯一的位置加入线性基,如果最后没有加入,只能是x被异或成 0 了
// which means 原集合可以异或 得到0
bool
insert(ll x) { for (int i = 62; i >= 0; i--) { // 1左移i位,然后把得到的结果再和x进行按位与运算,即 判断 x 的第 i 位是否为 1 if (x&(1 << i)) { // 如果以当前位数为最高位的数已经存在,就将 x 异或上这个数,将 x 的目前最高位变为 0 if (b[i]) x ^= b[i]; else // 如果以当前位数为最高位的数不存在,就将 x 存入线性基中 { b[i] = x; return 1; } } } // 能走到这一步的,无论是 x 中途被异或成0,还是因为数组b满了,x被异或成0 // 都代表原集合可以异或得到 0,用 flag 标记 flag = 1; return 0; }

 

② 求原集合能异或得到的最大值

// 线性基异或能得到的最大值,就是原来集合异或能得到的最大值
ll get_max()  // 求线性基异或能得到的最大值
{
    ll  ret = 0;   // 0 异或 x 为 x
    /* 尝试异或线性基中所有数,因为 最高位必选,所以从最高位开始
     为什么最高位必选呢?因为 低位的数 不管怎么异或都不可能 影响得到比你高位的数,
     所以不管怎么异或都不可能大于比你高位的数                          */
    for (int i = 62; i >= 0; i--)  
    {
        // 高位的数异或上低位的数,结果可能大于原先高位的数,也可能小于原先高位的数
        // 所以还要判断一下,如果异或上低位的数后值变小,就不能异或了
        if ((ret^b[i]) > ret)  
            ret ^= b[i];
        return ret;   // 返回最大值
    }
}

 

③ 求原集合能异或得到的最小值

// 线性基异或能得到的最小值,就是原来集合异或能得到的最小值
ll get_min()
{
    if (flag)  // 如果原集合能异或得到 0,那么 0 就是最小值
        return 0;
    // 因为 低位的数异或高位的数后值一定变大,所以只需要选取 线性基中最低位的数
    for (int i = 0; i <= 62; i++)
        if (b[i])
            return b[i];
    return 0; // 线性基为 空
}

 

④ 求原集合异或能得到的第 k 小

void rebuild()  // 重构线性基
{
    // 用 低位的最高位 将 高位的对应位 异或掉
    for (int i = 62; i >= 1; i--)  //  最低位只有一位就不用改
    {
        if (b[i])  // 讨论 b[i]
            for (int j = i - 1; j >= 0; j--)  // 讨论b[i]除最高位的其他位
            {
                if (b[i] & (1 << j)) // 如果 b[i] 的第 j 位存在的话
                    b[i] ^= b[j];    // 尝试 异或掉 b[i] 的第 j 位
                                     // 如果有以第 j 位为最高位的元素的话,就能将 高位的第 j 位异或掉
                                     // 如果没有以第 j 位为最高位的元素的话,那就异或不掉了
                                     // 因为是从b[i]的第2高位开始往下异或的,所以第 j 位以下的随它乱搞,等之后循环到了再去处理
            }
    }
    /*
    原先 
    低位的数异或高位的数 值一定变大
    高位的数异或低位的数 值可能变大也可能变小

    现在,处理之后的线性基 
    低位的数异或高位的数 值一定变大,
    高位的数异或低位的数 值一定变小
    */
    for (int i = 0; i <= 62; i++)
    {
        if (b[i])
            p[cnt++] = b[i];
    }
    /* 
    将存在的线性基从小到大存在 p数组 中,
    则对线性基能异或得到的数排序,从小到大为:
    第 1~2 小        p[0], p[1],
    第 3~4 小        p[0]^p[1], p[2],
    第 5~6 小        p[2]^p[0], p[2]^p[1],
    第 7~8 小        p[2]^p[1]^p[0], p[3]
    那么 异或p[i] 对名次的贡献为  1<<i
    如: p[0]为第 1 小,p[0]^p[1]为: 第 1+(1<<1) = 3 小

    总结 :
    对于第 k 小,k 的 二进制与 p 对应
    如  第10小 二进制为:1010,为 p[3]^p[1]
    */
}
ll kth(ll k)  // 查询 第 k 小
{
    // 因为线性基没有考虑 0,所以如果原来集合能够异或得到 0
    // 那么原来第 k 小的,扣掉 0,在线性基中就变成 第 k-1 小了
    if (flag)
        k--;
    if (k == 0)
        return 0;

    ll ret = 0;  // 0 异或 x 为 x
                 /*
                 cnt 为 p[] 的长度
                 cnt 位二进制最多能 表示 2的cnt次方-1,
                 如 2 位二进制最多能表示到 3,也就是 2^2-1     
                 */
    if (k >= (1 << cnt))
        return -1;
    for (int i = 0; i <= cnt - 1; i++)
    {
        if (k&(1 << i))  // 如果 k 的第 i 位存在
            ret ^= p[i];
    }
    return ret;
}

 

 

 

三,模板题:

XOR(HDU 3949)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define ll long long
#define N 65
ll a[N], p[N];
int flag, cnt;
void insert(ll x)
{
    for (int i = 62; i >= 0; i--)
    {
        if (x&(1ll << i)) // 1ll 是必要的
            if (a[i])
                x ^= a[i];
            else
            {
                a[i] = x;
                return;
            }
    }
    flag = 1;
}
void rebuild()
{
    for (int i = 62; i >= 1; i--)
    {
        if (a[i])
            for (int j = i - 1; j >= 0; j--)
                if (a[i] & (1ll << j))
                    a[i] ^= a[j];
    }
    for (int i = 0; i < 62; i++)
        if (a[i])
            p[cnt++] = a[i];
}
ll kth(int k)
{
    if (flag)
        k--;
    if (k == 0)
        return 0;
    ll ret = 0;
    if (k >= (1ll << cnt))
        return -1;
    for (int i = 0; i < cnt; i++)
        if (k&(1ll << i))
            ret ^= p[i];
    return ret;
}
int main(void)
{
    int tt = 1, t;
    scanf("%d", &t);
    while (t--)
    {
        memset(a, 0, sizeof(a));
        memset(p, 0, sizeof(p));
        cnt = 0, flag = 0;

        int n; scanf("%d", &n);
        for (int i = 0; i < n; i++)
        {
            ll x; scanf("%lld", &x);
            insert(x);
        }
        rebuild();
        int q; scanf("%d", &q);
        printf("Case #%d:\n", tt++);
        while (q--)
        {
            ll x; scanf("%lld", &x);
            printf("%lld\n", kth(x));
        }
    }
    system("pause");
    return 0;
}
View Code

 

 

 

============= ========== ======== ======= ====== ======= ==== === == =

更漏子·玉炉香   唐代: 温庭筠

玉炉香,红蜡泪,偏照画堂秋思。眉翠薄,鬓云残,夜长衾枕寒。
梧桐树,三更雨,不道离情正苦。一叶叶,一声声,空阶滴到明。

 

posted @ 2020-10-15 19:55  叫我妖道  阅读(175)  评论(0编辑  收藏  举报
~~加载中~~