zoukankan      html  css  js  c++  java
  • 线性基笔记

    转自: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(二进制),我们可以用 a1^a2 替换  a1,

    这样 a 2 就变成 0 1 0(二进制)。

    也就是说对于一个集合 ,集合中的每个元素的值小于 10^18,该集合与一个大小为 63 的集合等价

    ,因为 集合中元素最多 63 位(2^63-1,有63位),每一位作为最高位的数唯一

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

    证明:如果 原集合有 a1^a2^a3 == 0,可得 a1^a2 == a3,

    如果 a1 和 a2 在线性基中的话,a3 就不会加入线性基 ,因为重复了。

    代码与应用:

    ①:往线性基中插入 x

    // 根据 x 的二进制,为x  选择一个 最高位唯一的位置加入线性基,如果坠吼没有加入,只能是 x 被异或成 0 了
    // which mmeans 原集合可以异或得到 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,用 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])  // 枚举高位
                for (int j = i - 1; j >= 0; j--)
                {
                    if (b[i] & (1 << j)) // 如果 b[i] 的第 j 存在的话
                                         // 如果有对应的 低位的最高位的话,就能将 高位的这一位异或掉
                                         // 如果没有对应的 低位的最高位的话,那就异或不掉了
                                         // 因为是从最高位开始往下异或的,所以 j 位以下的随它乱搞,等到之后的循环再去处理
                        b[i] ^= b[j];  // 异或掉 b[i] 的第 j 位
                }
        }
        /*
        原先 低位的异或高位的值一定变大,所以只需要选取 线性基中最低位的数    
        高位的数异或上低位的数,结果可能大于原先大的那个数,也可能小于原先大的那个数
    
        现在,处理之后的线性基 低位的异或高位的值一定变大,高位的数异或上低位的数一定变小
        */
        /* 将存在的线性基从小到大存在 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 的 二进制与 p 对应
        如  第10小 二进制为:1010
        为 p[3]^p[1]```*/
        for (int i = 0; i <= 62; i++)
        {
            if (b[i])
                p[cnt++] = b[i];
        }
    }
    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

    XOR is a kind of bit operator, we define that as follow: for two binary base number A and B, let C=A XOR B, then for each bit of C, we can get its value by check the digit of corresponding position in A and B. And for each digit, 1 XOR 1 = 0, 1 XOR 0 = 1, 0 XOR 1 = 1, 0 XOR 0 = 0. And we simply write this operator as ^, like 3 ^ 1 = 2,4 ^ 3 = 7. XOR is an amazing operator and this is a question about XOR. We can choose several numbers and do XOR operatorion to them one by one, then we get another number. For example, if we choose 2,3 and 4, we can get 2^3^4=5. Now, you are given N numbers, and you can choose some of them(even a single number) to do XOR on them, and you can get many different numbers. Now I want you tell me which number is the K-th smallest number among them.
    代码:
    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define  ll long long
    ll a[65];
    ll p[65];
    ll cnt;
    int flag;
    bool insert(ll x)
    {
        for (int i = 62; i >= 0; i--)
        {
            if (x&(1ll << i))
                if (a[i])
                    x ^= a[i];
                else
                {
                    a[i] = x;
                    return 1;
                }
        }
        flag = 1;
        return 0;
    }
    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(ll k)
    {
        if (flag)
            k--;
        if (k == 0)
            return 0;
        ll ret = 0;
        if (k >= (1ll << cnt))
            return -1;
        for (int i = 0; i <= cnt - 1; i++)
        {
            if (k&(1ll << i))
                ret ^= p[i];
        }
        return ret;
    }
    int main(void)
    {
        int tt = 1;
        int 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 = 1; i <= n; i++)
            {
                ll x; scanf("%lld", &x);
                insert(x);
            }
            rebuild();
            int q; scanf("%d", &q);
            printf("Case #%d:
    ", tt++);
            for (int i = 1; i <= q; i++)
            {
                ll x; scanf("%lld", &x);
                printf("%lld
    ", kth(x));
            }
        }
        system("pause");
        return 0;
    }
    View Code

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

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

    玉炉香,红蜡泪,偏照画堂秋思。眉翠薄,鬓云残,夜长衾枕寒。
    梧桐树,三更雨,不道离情正苦。一叶叶,一声声,空阶滴到明。
  • 相关阅读:
    dimensionality reduction动机---data compression(使算法提速)
    K-means:如何选择K(cluster的数目)
    python2和python3共存方法
    docker postgresql 数据库
    转:MySQL到底能支持多大的数据量?
    数据库jdbc链接:mysql, oracle, postgresql
    python获取参数列表
    转载:ubuntu系统启动顺序,常见系统服务说明
    使用postman开发testcases记录贴
    python gevent使用例子
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/13821850.html
Copyright © 2011-2022 走看看