线性基 (Linear Basis)

线性基用于处理"异或空间"的集合压缩。它能把一组数压缩成不超过位数大小的基向量,并保持所有可达异或值不变。


一、核心概念与术语

线性基的目标是:用一组线性无关的向量,表示原集合所有异或组合的结果集合。

  • 向量:这里的向量就是一个整数的二进制表示。
  • 基向量:在某一最高位上为 1 的代表元素。
  • 维度:最多等于整数的位数,比如 64 位以内就是 64。

二、算法流程(How)

2.1 插入一个数

从高位到低位尝试消元,保持每一位最多一个基向量。

插入完成后满足一个关键不变量:若 basis[i] != 0,则 highestBit(basis[i]) = i,也就是 basis[i] 在所有更高位 > i 上都是 0。

  1. 对当前数 x,从最高位到最低位遍历。
  2. 若第 i 位是 0,跳过。
  3. 若第 i 位是 1:
    • basis[i] 为空,直接令 basis[i] = x 并结束。
    • basis[i] 已存在,用 x ^= basis[i] 消掉第 i 位,然后继续向更低位处理。根据不变量 highestBit(basis[i]) = ibasis[i] 的所有更高位 > i 都是 0,所以这次异或不会影响已经处理过的更高位。
  4. 如果 x 被消成 0,表示它可以由现有基表示,不入基。

注意:遍历到某一位 i 时如果 x 的第 i 位为 0,仍可能在更低位为 1,因此不能因为"当前位为 0"提前跳出;只有当 x 被消成 0 时才可以提前结束。

2.2 求最大异或值

从高位到低位贪心尝试提升答案。

  1. 初始化 ans = 0
  2. 对每一位 i 从高到低:
    • ans ^ basis[i] 更大,则 ans ^= basis[i]
  3. 最终 ans 即为最大可达异或值。

三、为什么正确(Why)

  • 插入过程等价于高斯消元,确保基向量在二进制空间中线性无关。
  • 每个数都可以表示成这些基向量的异或组合,集合不变。
  • 最大异或查询使用贪心,因为每一位是否置 1 的最优选择不会影响更高位的最优性。

四、复杂度

  • 单次插入:O(log⁡C)O(\log C)O(logC),其中 CCC 为数值范围。
  • 单次查询最大异或:O(log⁡C)O(\log C)O(logC)。
  • 空间:O(log⁡C)O(\log C)O(logC)。

五、例题:最大子集异或和

5.1 题目描述

给定一个数组,选择任意子集,使异或和最大,输出这个最大值。

5.2 解题过程

  1. 把数组所有元素依次插入线性基。
  2. 用线性基的最大异或查询得到答案。

5.3 具体例子

数组为 [6, 5, 10],二进制为 0110, 0101, 1010(按位从低到高编号为 0, 1, 2, 3)。

从空基开始,依次插入并记录 basis 的变化:

  1. 插入 6 (0110):最高位是 2,且 basis[2] 为空,所以 basis[2] = 0110
  2. 插入 5 (0101):最高位也是 2,basis[2] 已存在。
    • 先消掉第 2 位:x = 0101 ^ 0110 = 0011
    • 继续向低位走,x 的最高位变成 1,且 basis[1] 为空,所以 basis[1] = 0011
  3. 插入 10 (1010):最高位是 3,且 basis[3] 为空,所以 basis[3] = 1010

最终得到:

  • basis[3] = 1010
  • basis[2] = 0110
  • basis[1] = 0011

然后用 maxXor 从高位到低位贪心:

  • 初始 ans = 0000
  • 处理第 3 位:ans ^= basis[3] -> 1010
  • 处理第 2 位:1010 ^ 0110 = 1100 更大,令 ans = 1100
  • 处理第 1 位:1100 ^ 0011 = 1111 更大,令 ans = 1111

所以最大子集异或和为 1111 (15)(例如选择子集 {5, 10})。


六、Java 模板

java 复制代码
class LinearBasis {
    private final long[] basis;

    LinearBasis(int bit) {
        basis = new long[bit + 1];
    }

    void insert(long x) {
        for (int i = basis.length - 1; i >= 0 && x != 0L; i--) {
            if (((x >> i) & 1L) == 0L) {
                continue;
            }
            if (basis[i] == 0L) {
                basis[i] = x;
                return;
            }
            x ^= basis[i];
        }
    }

    long maxXor() {
        long ans = 0L;
        for (int i = basis.length - 1; i >= 0; i--) {
            if ((ans ^ basis[i]) > ans) {
                ans ^= basis[i];
            }
        }
        return ans;
    }
}

七、扩展:带位置的线性基(Codeforces 1100F)

7.0 整体思路

对每个询问 [L, R],我们要的是:只用下标在 [L, R] 内的数能拼出的最大异或值。

标准线性基不支持删除元素。关键观察是:如果我们手里有一个"前缀到 R"的线性基,并且每个基向量都带着来源位置 pos,那么查询时忽略 pos < L 的向量,就等价于把 1..L-1 从可选集合里删除。

7.0.1 数据结构

维护长度为 W 的数组(通常 W = 61),每一位至多一个基向量:

  • b[i]:最高位(pivot)为 i 的基向量值
  • p[i]:该基向量来源下标

需要维持两个不变量:

  • 形态不变量:若 b[i] != 0,则 highestBit(b[i]) = i
  • 位置不变量:在所有能作为第 i 位 pivot 的表示里,p[i] 尽量大(更靠右优先)。所以插入的时候,如果有冲突,就用"更靠右"的向量交换。

7.1 预处理时的拷贝

basis[R] 只是一份长度为 W 的定长数组(W = 61),保存 (b[i], p[i])

构建时:

  • basis[0] 全 0。
  • R = 1..n:先把 basis[R-1]W 个槽位复制到 basis[R],再把 (a[R], R) 插入到 basis[R]

拷贝在 Java 里就是对数组切片做 System.arraycopy

7.2 查询时的限制

[L, R]

  • basis[R]
  • 从高位到低位遍历 i
  • 只有当 p[i] >= L 时,才允许用 b[i] 去更新答案。

7.3 正确性要点

  • 形态不变量保证贪心不会影响更高位:b[i] 的更高位 > i 全为 0,因此选择 b[i] 只影响位 <= i
  • 位置不变量保证过滤不会误杀区间内的表达能力:插入冲突时把"更靠右"的向量交换到更高的 pivot 上,使得能用 [L, R] 形成的 pivot 尽量满足 p[i] >= L

7.3.1 复杂度

  • 预处理:n 次插入,每次 O(W),总计 O(nW)
  • 查询:每次 O(W),总计 O(qW)
  • 空间:存 n 份基,每份 W 个槽位,O(nW)

7.4 Java 实现(数组扁平化存储)

java 复制代码
class RangeLinearBasis {
    static final int W = 61;

    final long[] base;
    final int[] pos;
    final int n;

    RangeLinearBasis(long[] a) {
        this.n = a.length - 1;
        this.base = new long[(n + 1) * W];
        this.pos = new int[(n + 1) * W];

        for (int r = 1; r <= n; r++) {
            int cur = r * W;
            int prev = (r - 1) * W;
            System.arraycopy(base, prev, base, cur, W);
            System.arraycopy(pos, prev, pos, cur, W);
            insert(cur, a[r], r);
        }
    }

    private void insert(int off, long x, int pNew) {
        for (int i = W - 1; i >= 0 && x != 0L; i--) {
            if (((x >>> i) & 1L) == 0L) {
                continue;
            }
            long bi = base[off + i];
            if (bi == 0L) {
                base[off + i] = x;
                pos[off + i] = pNew;
                return;
            }
            if (pos[off + i] < pNew) {
                long tv = base[off + i];
                base[off + i] = x;
                x = tv;

                int tp = pos[off + i];
                pos[off + i] = pNew;
                pNew = tp;
            }
            x ^= base[off + i];
        }
    }

    long queryMaxXor(int L, int R) {
        int off = R * W;
        long ans = 0L;
        for (int i = W - 1; i >= 0; i--) {
            if (pos[off + i] < L) {
                continue;
            }
            long cand = ans ^ base[off + i];
            if (cand > ans) {
                ans = cand;
            }
        }
        return ans;
    }
}

这段代码里:

  • System.arraycopy 负责 basis[R] = basis[R-1] 的定长拷贝。
  • queryMaxXor(L, R) 里用 pos[off+i] < L 过滤,等价于只允许使用区间 [L, R] 的元素。
相关推荐
twilight_4692 小时前
人工智能数学基础——第二章 高等数学基础
人工智能·算法·机器学习
_OP_CHEN2 小时前
【算法提高篇】(二)线段树之区间修改:懒标记的核心奥义与实战实现
算法·蓝桥杯·线段树·c/c++·区间查询·acm/icpc·懒标记
啊阿狸不会拉杆2 小时前
《机器学习导论》第 18 章-增强学习
人工智能·python·学习·算法·机器学习·智能体·增强学习
田里的水稻2 小时前
FA_规划和控制(PC)-D*规划
人工智能·算法·数学建模·机器人·自动驾驶
小跌—2 小时前
Redis数据结构和单线程
数据结构·数据库·redis
We་ct2 小时前
LeetCode 61. 旋转链表:题解+思路拆解
前端·算法·leetcode·链表·typescript
Felven2 小时前
D. Find the Different Ones!
算法
mit6.8242 小时前
logtrick
算法
mit6.82410 小时前
Xai架构
算法