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

    线性基具有的性质:

    参见线性基学习笔记 - Menci

    线性基插入和查询的写法

    Menci Version

    单次插入时间复杂度为 (O(log^2 n))

    单次查询时间复杂度为 (O(log n))

    const int MAXL = 60;
    
    struct LinearBasis{
        long long a[MAXL + 1];
        LinearBasis(){std::fill(a, a + MAXL + 1, 0);}
        void insert(long long t){
            // 逆序枚举二进制位
            for (int j = MAXL; j >= 0; j--){
                // 如果 t 的第 j 位为 0,则跳过
                if (!(t & (1ll << j))) continue;
                // 如果 a[j] != 0,则用 a[j] 消去 t 的第 j 位上的 1
                if (a[j]) t ^= a[j];
                else{
                    // 找到可以插入 a[j] 的位置
                    // 用 a[0...j - 1] 消去 t 的第 [0, j) 位上的 1
                    // 如果某一个 a[k] = 0 也无须担心,因为这时候第 k 位不存在于线性基中,不需要保证 t 的第 k 位为 0
                    for (int k = 0; k < j; k++) if (t & (1ll << k)) t ^= a[k];
                    // 用 t 消去 a[j + 1...L] 的第 j 位上的 1
                    for (int k = j + 1; k <= MAXL; k++) if (a[k] & (1ll << j)) a[k] ^= t;
                    // 插入到 a[j] 的位置上
                    a[j] = t;
                    // 不要忘记,结束插入过程
                    return;
                }
                // 此时 t 的第 j 位为 0,继续寻找其最高位上的 1
            }
            // 如果没有插入到任何一个位置上,则表明 t 可以由 a 中若干个元素的异或和表示出,即 t 在 span(a) 中
        }
        long long queryMax(){
            long long res = 0;
            for (int i = 0; i <= MAXL; i++) 
                res ^= a[i];
            return res;
        }
    };
    
    Another Version

    单次插入时间复杂度 (O(log n))

    单次查询时间复杂度 (O(log n))

    typedef unsigned long long ull;
    const int MAXL = 63;
    
    ull p[MAXL + 5];
    
    inline void ins(ull x){
        for(int i = MAXL; i + 1; -- i){
        	if((x >> i) & 1){
    	    if(!p[i]){
    	        p[i] = x;
    	        return ;
    	    }
    	    x ^= p[i];
    	}
        }
        return ;
    }
    ull query(ull x) {
        ull res = x;
        for (int i = MAXL; i + 1; -- i)
            if((res ^ p[i]) > res)
                res ^= p[i];
        return res;
    }
    

    序列上的线性基

    引题

    CF1100F Ivan and Burgers

    CF1100F Ivan and Burgers

    题意: 给定一个序列({ a_n })(q)次查询, 每次查询一个区间([l, r]), 求在这段区间内选取任意个数字的异或和最大是多少

    Solution.

    首先我们考虑用线段树来维护线性基,每次在父节点将两个区间的线性基暴力合并

    复杂度为(O(n log ^ 3 n))

    但是在序列上我们可以换一种思路

    考虑离线做法: 我们先把询问区间按照右端点排序, 然后构造序列上 ([1, r]) 的线性基, 再令 (pos_i) 表示 ([l , r]) 线性基第 (i) 位有值的最大的 (l),每一次按照普通线性基插入的方式向线性基(base)中插入(a_i)。处理答案时,如果(base_i)有值,且(pos_i geq l),那么就代表这一位是在 ([l, r]) 上的, 所以我们就可以选取了

    Sample Code.
    #include<cstdio>
    #include<algorithm>
    
    #define rep(i, a, b) for(register int i = (a); i <= (b); ++ i)
    #define drep(i, a, b) for(register int i = (a); i >= (b); -- i) 
    
    template <typename T>
    extern inline void SWAP(T &a, T &b){
        T t = a; a = b, b = t;
    }
    
    const int MAXN = 500005;
    const int MAXBIT = 20;
    const int MEMBIT = MAXBIT + 5; 
    
    int bas[MEMBIT],pos[MEMBIT],n,rt,a[MAXN],q;
    struct quest{
        int lt,rt,id;
        bool operator < (const quest &tmp) const{
              return rt<tmp.rt;
        }
    }Q[MAXN];
    void insert(int x,int id){
        drep(i, MAXBIT, 0){
              if(x & (1 << i)){
                    if(!bas[i]){
                          bas[i] = x,
                          pos[i] = id;
                          return ;
                    }
                    if(pos[i] < id) 
                          SWAP(pos[i], id),
                          SWAP(x, bas[i]);
                    x ^= bas[i];
              }
        }
    }
    int work(int lt){
        int ans = 0;
        drep(i, MAXBIT, 0)
              if(pos[i] >= lt && ans < (ans ^ bas[i]))
                    ans ^= bas[i];
        return ans;
    }
    int ans[MAXN];
    int main(){ 
        scanf("%d",&n);
        rep(i, 1, n)
              scanf("%d",&a[i]);
        scanf("%d",&q);
        rep(i, 1, q)
              scanf("%d%d",&Q[i].lt,&Q[i].rt),
        Q[i].id = i;
        std::sort(Q + 1, Q + 1 + q);
        rep(i, 1, q){
              while(rt < Q[i].rt){
                    ++ rt;
                    insert(a[rt],rt);
              }
              ans[Q[i].id] = work(Q[i].lt);
        }
        rep(i, 1, q)
                printf("%d
    ",ans[i]);
        return 0;
    }
    

    例题

    P3292 [SCOI2016]幸运数字

    P3292 [SCOI2016]幸运数字

    给定一颗树,每个节点有点权

    (q)组询问, 每次询问两个点(u, v), 求在这两个点的简单路径上所有点权中任意选取点权异或和的最大值

    Solution.

    我们可以先对这棵树上的节点从节点到根节点上所有的点权维护在一个线性基里,每个节点维护一个线性基

    然后我们就能够将这道题转化到序列上来做

    Sample Code.
    #include <cstdio>
    #define ll long long
    #define rep(i, a, b) for(register int i = (a); i <= (b); ++ i)
    #define Rep(i, a, b) for(register int i = (a); i < (b); ++ i)
    #define drep(i, a, b) for(register int i = (a); i >= (b); -- i)
    #define fwd(i, u) for(register int i = head[(u)]; i; i = e[i].nxt)
    ll rd() {
        ll k = 0;
        char c = getchar();
        while (c > '9' || c < '0') c = getchar();
        while (c >= '0' && c <= '9') k = k * 10 + c - '0', c = getchar();
        return k;
    }
    const int N = 20001;
    void upmax(ll& x, ll y) { if(x < y) x = y; }
    int n, m, u, v, t, head[N], cnt, f[N], d[N], top[N], sz[N], son[N], lca;
    ll a[N];
    struct LB {
        ll a[61]; int b[61];
        void Insert(ll x, int v) {
        	drep(i, 60, 0){
                if (!(x >> i))
                    continue;
                if (!a[i]) {
                    a[i] = x, b[i] = v;
                    break;
                }
                x ^= a[i];
                if (b[i] < v)
                    a[i] ^= x, 
    		t = v, v = b[i], b[i] = t;
            }
        }
        ll Query(int x) {
            ll s = 0;
            drep(i, 60, 0)
                if (b[i] >= x)
                    upmax(s, s ^ a[i]);
            return s;
        }
    } P[N], p;
    struct E {
        int v, nxt;
    } e[N << 1];
    void add(int u, int v) { e[++ cnt].v = v, e[cnt].nxt = head[u], head[u] = cnt; }
    void dfs1(int u, int fa) {
        d[u] = d[(f[u] = fa)] + 1, sz[u] = 1, 
    	P[u] = P[fa], P[u].Insert(a[u], d[u]);
        fwd(i, u){
            int v = e[i].v;
            if (v == fa)
                continue;
            dfs1(v, u), sz[u] += sz[v];
            if (sz[son[u]] < sz[v])
                son[u] = v;
        }
    }
    void dfs2(int u, int t) {
        top[u] = t;
        if (!son[u])
            return;
        dfs2(son[u], t);
        fwd(i, u){
            int v = e[i].v;
            if (v != f[u] && v != son[u])
                dfs2(v, v);
        }
    }
    int LCA(int x, int y) {
        while (top[x] != top[y]) {
            if (d[top[x]] > d[top[y]])
                x = f[top[x]];
            else
                y = f[top[y]];
        }
        return d[x] < d[y] ? x : y;
    }
    int main() {
        n = rd(), m = rd();
        rep(i, 1, n)
    		a[i] = rd();
    	Rep(i, 1, n)
    		u = rd(), v = rd(), 
    		add(u, v), add(v, u);
        dfs1(1, 0), dfs2(1, 1);
        while (m--) {
            u = rd(), v = rd(), 
    		lca = LCA(u, v), p = P[u];
            drep(i, 60, 0)
                if (P[v].b[i] >= d[lca] && P[v].a[i])
                    p.Insert(P[v].a[i], d[lca]);
            printf("%lld
    ", p.Query(d[lca]));
        }
        return 0;
    }
    
  • 相关阅读:
    拯救祭天的程序员——事件溯源模式
    啥?SynchronousQueue和钟点房一个道理
    程序员应该掌握的一些 Linux 命令
    Linux 环境下使用 sqlplus 访问远程 Oracle 数据库
    我对鸿蒙OS的一些看法
    我对技术潮流的一些看法
    git merge --ff/--no-ff/--ff-only 三种选项参数的区别
    go语言的初体验
    完全使用 VSCode 开发的心得和体会
    重复代码的克星,高效工具 VSCode snippets 的使用指南
  • 原文地址:https://www.cnblogs.com/eqvpkbz/p/13254940.html
Copyright © 2011-2022 走看看