What is the Linear Basis Algorithm?

Linear Basis is an advanced data structure/algorithm that uses linear algebra concepts over the binary field to solve subset XOR problems. It can represent the XOR sums of all possible subsets of an array using a compact basis of size at most (where is the maximum number of bits, e.g., 30 or 60). Insert / Query Complexity: O(D).

Mathematical Foundations

1. Integers as Vectors

  • Any integer can be represented in binary:
  • This binary representation maps directly to a vector in a -dimensional vector space over the field :

2. XOR as Vector Addition

  • The bitwise XOR operation () corresponds exactly to vector addition modulo 2 (without carry) in :

3. Linear Independence and Basis

  • A set of vectors is linearly independent if no vector in the set can be formed by XORing a subset of the other vectors.
  • A Linear Basis of a set of numbers is a minimal subset of linearly independent numbers that spans (can generate via XOR) the exact same set of values as .
  • Since the vector space has dimension , the size of the basis is at most .

How the Algorithm Works

  • We maintain an array basis of size (initially all zeros), where basis[i] stores a number in our basis whose highest set bit (MSB) is at index .

1. Insertion (insert(x))

  • To insert a number :
    • Iterate through its bits from MSB () down to LSB ().
    • If the -th bit of is set ():
      • If basis[i] is empty (), we insert here (basis[i] = x) and stop.
      • If basis[i] is occupied, we XOR with basis[i] (x ^= basis[i]) and continue.
    • If becomes , it means was already linearly dependent on the current basis elements, so it cannot be added.

2. Querying Maximum XOR (query_max())

  • To find the maximum possible XOR sum of any subset of the inserted numbers:
    • Initialize res = 0.
    • Iterate from down to .
    • If (res ^ basis[i]) > res, set res ^= basis[i].
    • Return res.
    • This greedy approach is guaranteed to yield the global maximum because the MSB of basis[i] is strictly at index , meaning each step optimizes the highest possible bit.

Step-by-Step Trace (Insert [4, 6, 2])

  • Let (bases for bits 2, 1, 0). basis is initially [0, 0, 0].
  • Let’s insert the numbers 4, 6, and 2:
    1. Insert 4 (100 in binary):
    • MSB is at bit index 2.
    • basis[2] is empty ().
    • Insert: basis[2] = 4.
    • basis is now [4, 0, 0].
    1. Insert 6 (110 in binary):
    • MSB is at bit index 2.
    • basis[2] is occupied (, 100).
    • XOR: 6 ^ 4 = 2 (010 in binary).
    • Now process . MSB of is at bit index 1.
    • basis[1] is empty ().
    • Insert: basis[1] = 2.
    • basis is now [4, 2, 0].
    1. Insert 2 (010 in binary):
    • MSB is at bit index 1.
    • basis[1] is occupied (, 010).
    • XOR: 2 ^ 2 = 0.
    • Value becomes . Not inserted (already spanned by ).
  • Final Basis: [4, 2, 0]. Spans the subset values .
  • Max XOR sum is query_max() = 0 ^ 4 ^ 2 = 6.

Time & Space Complexity

Complexity Table

OperationTime ComplexitySpace ComplexityDetails
Insert bit iterations
Query MaxGreedy scan down
Query MinSmallest non-zero element
Check SpannedTest if element reduces to 0
  • is the maximum number of bits (usually 30 for and 60 for ).

Implementation

class LinearBasis:
    def __init__(self, d=30):
        self.d = d
        self.basis = [0] * d
 
    def insert(self, x):
        for i in range(self.d - 1, -1, -1):
            if (x >> i) & 1:
                if not self.basis[i]:
                    self.basis[i] = x
                    return True
                x ^= self.basis[i]
        return False
 
    def query_max(self):
        res = 0
        for i in range(self.d - 1, -1, -1):
            if (res ^ self.basis[i]) > res:
                res ^= self.basis[i]
        return res
 
    def exists(self, x):
        for i in range(self.d - 1, -1, -1):
            if (x >> i) & 1:
                if not self.basis[i]:
                    return False
                x ^= self.basis[i]
        return x == 0
 
# Example usage
lb = LinearBasis()
lb.insert(4)
lb.insert(6)
lb.insert(2)
print("Max XOR sum:", lb.query_max())  # 6
print("Exists 2:", lb.exists(2))      # True
print("Exists 5:", lb.exists(5))      # False
#include <iostream>
#include <vector>
 
class LinearBasis {
private:
    int d;
    std::vector<long long> basis;
 
public:
    LinearBasis(int d = 60) : d(d), basis(d, 0) {}
 
    bool insert(long long x) {
        for (int i = d - 1; i >= 0; --i) {
            if ((x >> i) & 1) {
                if (!basis[i]) {
                    basis[i] = x;
                    return true;
                }
                x ^= basis[i];
            }
        }
        return false;
    }
 
    long long query_max() {
        long long res = 0;
        for (int i = d - 1; i >= 0; --i) {
            if ((res ^ basis[i]) > res) {
                res ^= basis[i];
            }
        }
        return res;
    }
 
    bool exists(long long x) {
        for (int i = d - 1; i >= 0; --i) {
            if ((x >> i) & 1) {
                if (!basis[i]) return false;
                x ^= basis[i];
            }
        }
        return x == 0;
    }
};
 
int main() {
    LinearBasis lb(30);
    lb.insert(4);
    lb.insert(6);
    lb.insert(2);
    std::cout << "Max XOR sum: " << lb.query_max() << "\n"; // 6
    std::cout << "Exists 2: " << (lb.exists(2) ? "Yes" : "No") << "\n"; // Yes
    return 0;
}
class LinearBasis {
    constructor(d = 30) {
        this.d = d;
        this.basis = new Array(d).fill(0);
    }
 
    insert(x) {
        for (let i = this.d - 1; i >= 0; i--) {
            if ((x >> i) & 1) {
                if (this.basis[i] === 0) {
                    this.basis[i] = x;
                    return true;
                }
                x ^= this.basis[i];
            }
        }
        return false;
    }
 
    queryMax() {
        let res = 0;
        for (let i = this.d - 1; i >= 0; i--) {
            if ((res ^ this.basis[i]) > res) {
                res ^= this.basis[i];
            }
        }
        return res;
    }
 
    exists(x) {
        for (let i = this.d - 1; i >= 0; i--) {
            if ((x >> i) & 1) {
                if (this.basis[i] === 0) return false;
                x ^= this.basis[i];
            }
        }
        return x === 0;
    }
}
 
const lb = new LinearBasis();
lb.insert(4);
lb.insert(6);
lb.insert(2);
console.log("Max XOR sum:", lb.queryMax()); // 6
public class LinearBasis {
    private final int d;
    private final long[] basis;
 
    public LinearBasis(int d) {
        this.d = d;
        this.basis = new long[d];
    }
 
    public boolean insert(long x) {
        for (int i = d - 1; i >= 0; i--) {
            if (((x >> i) & 1) == 1) {
                if (basis[i] == 0) {
                    basis[i] = x;
                    return true;
                }
                x ^= basis[i];
            }
        }
        return false;
    }
 
    public long queryMax() {
        long res = 0;
        for (int i = d - 1; i >= 0; i--) {
            if ((res ^ basis[i]) > res) {
                res ^= basis[i];
            }
        }
        return res;
    }
 
    public boolean exists(long x) {
        for (int i = d - 1; i >= 0; i--) {
            if (((x >> i) & 1) == 1) {
                if (basis[i] == 0) return false;
                x ^= basis[i];
            }
        }
        return x == 0;
    }
 
    public static void main(String[] args) {
        LinearBasis lb = new LinearBasis(30);
        lb.insert(4);
        lb.insert(6);
        lb.insert(2);
        System.out.println("Max XOR sum: " + lb.queryMax()); // 6
    }
}

Key Takeaways

  • XOR Vector Space — Integers are treated as vectors over , and XOR functions as vector addition.
  • Linear Independence — Only numbers that introduce new set bits not covered by the current basis span are inserted.
  • O(D) Efficiency — The basis keeps space and query costs bound to the word size (), which is exceptionally fast in practice.

More Learn

Resources