• 【数学】线性基


    这是一个看起来很像Gauss-Jordan消元法里面的形式(行最简形矩阵)的线性基,和其他人的Gauss消元法里面的形式(上三角形矩阵)看起来并不一样

    d[i] 表示掌管二进制第i位(1LL<<i)的这个线性基。

    struct LB {
    
        static const int LEN = 60;
    
        int n, z;
        ll d[LEN];
    
        void Init() {
            n = 0;
            z = 0;
            ms(d);
        }
    
        bool Insert(ll x) {
            for(int i = LEN - 1; i >= 0; --i) {
                if(x >> i & 1) {
                    if(d[i]) {
                        x ^= d[i];
                        continue;
                    }
                    ++n;
                    d[i] = x;
                    for(int j = 0; j < i; ++j) {
                        if(d[i] >> j & 1)
                            d[i] ^= d[j];
                    }
                    for(int j = i + 1; j < LEN; ++j) {
                        if(d[j] >> i & 1)
                            d[j] ^= d[i];
                    }
                    return true;
                }
            }
            z = 1;
            return false;
        }
    
        bool Query(ll x) {
            if(x == 0)
                return z == 1;
            for(int i = LEN - 1; i >= 0; --i) {
                if(x >> i & 1) {
                    if(d[i]) {
                        x ^= d[i];
                        continue;
                    }
                    return false;
                }
            }
            return x == 0;
        }
    
        // Maximal
        ll Max() {
            if(!n)
                return z ? 0 : -1;
            ll res = 0;
            for(int i = LEN - 1; i >= 0; --i)
                res ^= d[i];
            return res;
        }
    
        // Minimal
        ll Min() {
            if(!n)
                return z ? 0 : -1;
            for(int i = 0; i < LEN; ++i) {
                if(d[i])
                    return d[i];
            }
        }
    
        ll KthMin(ll k) {
            if(z)
                --k;
            ll res = 0;
            for(int i = 0; i < LEN; ++i) {
                if(d[i]) {
                    if(k & 1)
                        res ^= d[i];
                    k >>= 1;
                }
            }
            return k ? -1 : res;
        }
    
        ll Size() {
            return (1LL << n) - 1 + z;
        }
    
        void Show(int sl = LEN) {
            printf("n=%d z=%d
    ", n, z);
            for(int i = LEN - 1; i >= 0; --i) {
                if(d[i]) {
                    printf("d[%02d]=", i);
                    for(int j = sl - 1; j >= 0; --j)
                        printf("%d", d[i] >> j & 1);
                    printf("
    ");
                }
            }
            printf("
    ");
        }
    
    } lb;
    

    n:线性基中非零值有多少个
    z:是否可以组成0

    设值域为 (A)

    尝试往线性基中插入一个x:

    问题就是x是否能被当前的线性基中的数字线性表示。
    所以对于x的每个非0的位,都去找对应的d[i]和他异或,注意这样可能会引入新的位,所以规定从高到低异或就可以每次消去一位。当其可以被线性基中的数字表示时,那么再加上这个数本身就可以表示0了,就置z标记为1。否则要把这个剩余的数字插入到对应的d[i]中,然后用比它低位的d[j]消去d[i]中带有d[j]的位,再用d[i]消去比它高位的d[j]中的带有d[i]的位。(注意顺序,要先确保比i低的被掌管的位已经被消为0)。

    最大值:

    当不存在非零值时,判断是否可以组成0,若0都没有,那么就是空集。否则,由于是行最简形,所以就所有的d[i]都异或起来。

    最小值:

    当不存在非零值时,判断是否可以组成0,若0都没有,那么就是空集。判断是否能够组成0,有0就是0,否则输出最小的那个基底。

    第k小值:

    判断是否能够组成0,有0的话0就是最小值,--k。然后在非零的里面找第k小的。
    然后从最小的基底开始往高位扫,每次k的最低位是奇数,就选中最小的基底,否则就不选。若最后k没有被消除完,则说明非0的值不足k个。
    http://acm.hdu.edu.cn/showproblem.php?pid=3949

    合并:
    (改变d[i]的定义后)把规模小的那个线性基插入到大的那个,或者都直接暴力扫一遍。

    Insert x=41
    n=1 z=0
    d[05]=000000101001
    
    Insert x=35
    n=2 z=0
    d[05]=000000100011
    d[03]=000000001010
    
    Insert x=190
    n=3 z=0
    d[07]=000010010111
    d[05]=000000100011
    d[03]=000000001010
    
    Insert x=1924
    n=4 z=0
    d[10]=011100010011
    d[07]=000010010111
    d[05]=000000100011
    d[03]=000000001010
    
    Insert x=737
    n=5 z=0
    d[10]=010101000110
    d[09]=001001010101
    d[07]=000010010111
    d[05]=000000100011
    d[03]=000000001010
    
    Insert x=1388
    n=6 z=0
    d[10]=010101000101
    d[09]=001001010101
    d[07]=000010010100
    d[05]=000000100000
    d[03]=000000001001
    d[01]=000000000011
    
    Insert x=1238
    n=7 z=0
    d[10]=010001000001
    d[09]=001001010101
    d[08]=000100000100
    d[07]=000010010100
    d[05]=000000100000
    d[03]=000000001001
    d[01]=000000000011
    
    Insert x=686
    n=8 z=0
    d[10]=010000000100
    d[09]=001000010000
    d[08]=000100000100
    d[07]=000010010100
    d[06]=000001000101
    d[05]=000000100000
    d[03]=000000001001
    d[01]=000000000011
    

    好好用哦。

    提供一个用vector的实现:

    struct LB {
    
        static const int LEN = 60;
    
        int z;
        vector<ll> d;
    
        void Init() {
            z = 0;
            d.clear();
        }
    
        bool Insert(ll x) {
            for(ll &i : d)
                cmin(x, x ^ i);
            if(x) {
                for(ll &i : d)
                    cmin(i, i ^ x);
                d.eb(x);
                sort(all(d));
                return true;
            }
            z = 1;
            return false;
        }
    
        bool Query(ll x) {
            if(x == 0)
                return z == 1;
            for(ll &i : d)
                cmin(x, x ^ i);
            return x == 0;
        }
    
        // Maximal
        ll Max() {
            if(!d.size())
                return z ? 0 : -1;
            ll res = 0;
            for(ll &i : d)
                res ^= i;
            return res;
        }
    
        // Minimal
        ll Min() {
            if(!d.size())
                return z ? 0 : -1;
            return d[0];
        }
    
        ll KthMin(ll k) {
            if(z)
                --k;
            ll res = 0;
            for(ll &i : d) {
                if(k & 1)
                    res ^= i;
                k >>= 1;
            }
            return k ? -1 : res;
        }
    
        ll Size() {
            return (1LL << d.size()) - 1 + z;
        }
    
        void Show(int sl = LEN) {
            printf("n=%u z=%d
    ", d.size(), z);
            for(ll &i : d) {
                printf("d=", i);
                for(int j = sl - 1; j >= 0; --j)
                    printf("%d", i >> j & 1);
                printf("
    ");
            }
            printf("
    ");
        }
    
    } lb;
    

    合并线性基的时候记得顺便把z标记也合并。

    当可以离线查询时,可以维护[i,R]的所有线性基,当++R时从右往左扫描所有“当前后缀线性基”并尝试插入,因为每个线性基最多被插入成功log次,当插入失败时说明这个位置的值可以替代当前R的值,更前面的也会插入失败。

    前缀线性基:给一个序列,每次往末尾加一个数或者查询LR的最大值,强制在线。
    维护若干个[1,i]的线性基,线性基里面每个基底最后被插入的pos(当不存在这个基底,直接插入并设pos为当前pos,当存在这个基底时,假如当前pos>基底的pos,说明现在这一位需要换成最新的(因为是最大值),则交换插入的x和当前基底(然后把交换后的基底继续往后看看能不能插回去)),当查询[L,R]时,找[1,R]的线性基,然后找pos>=L的基底组合。

    struct LB {
    
        static const int LEN = 60;
    
        int n;
        ll d[LEN];
        int pos[LEN];
    
        void Init() {
            n = 0;
            ms(d);
            ms(pos);
        }
    
        void Insert(ll x, int xpos) {
            for (int i = LEN - 1; i >= 0; --i) {
                if (x >> i & 1) {
                    if (!d[i]) {
                        d[i] = x;
                        pos[i] = xpos;
                        break;
                    } else {
                        if (xpos > pos[i]) {
                            swap(x, d[i]);
                            swap(xpos, pos[i]);
                        }
                        x ^= d[i];
                    }
                }
            }
        }
    
        // Maximal
        ll Max() {
            ll res = 0;
            for (int i = LEN - 1; i >= 0; --i)
                res ^= d[i];
            return res;
        }
    
    } lb;
    
  • 相关阅读:
    hdu_5718_Oracle(大数模拟)
    hdu_2222_Keywords Search(AC自动机板子)
    hdu_5616_Jam's balance(暴力枚举子集||母函数)
    hdu_2255_奔小康赚大钱(KM带权二分匹配板子)
    hdu_2544_最短路(spfa版子)
    hdu_2457_DNA repair(AC自动机+DP)
    hdu_5555_Immortality of Frog(状压DP)
    hdu_2159_FATE(完全背包)
    [USACO2002][poj1944]Fiber Communications(枚举)
    [AHOI2013]打地鼠(网络流)
  • 原文地址:https://www.cnblogs.com/purinliang/p/14397340.html
Copyright © 2020-2023  润新知