Integers are vectors over and XOR is vector addition. Any set of numbers spans a subspace of dimension (the bit width), represented by a basis of at most numbers. Insertion is Gaussian elimination, one vector at a time.

Structure

struct XorBasis {
    static const int B = 60;
    ll b[B] = {}; int rank = 0;
    bool insert(ll x) {                         // true if x enlarged the span
        for (int i = B - 1; i >= 0 && x; i--) if (x >> i & 1) {
            if (!b[i]) { b[i] = x; rank++; return true; }
            x ^= b[i];
        }
        return false;
    }
    bool contains(ll x) const {
        for (int i = B - 1; i >= 0; i--) if (x >> i & 1) { if (!b[i]) return false; x ^= b[i]; }
        return true;
    }
    ll maxXor(ll x = 0) const { for (int i = B - 1; i >= 0; i--) x = max(x, x ^ b[i]); return x; }
    ll minXor(ll x) const { for (int i = B - 1; i >= 0; i--) x = min(x, x ^ b[i]); return x; }
};

b[i] has highest bit (row echelon form). Insertion and queries are .

What it answers

QuestionAnswer
maximum subset XOR (optionally XORed with )greedy from the top bit
minimum XOR of with an element of the spangreedy minimising
is a subset XOR?reduce to 0
number of distinct subset XORs
number of subsets (of inputs) with XOR , for reachable (each value hit equally often, including the empty subset for )
-th smallest subset XORfully reduce, then read in binary
minimum non-zero subset XORsmallest vector after full reduction (if , then 0 is reachable non-trivially)
merge two basesinsert one into the other,
rank of a set over rank

Full reduction: for each pivot , clear bit from every other basis vector. Then the reduced vectors sorted by pivot are , and the -th smallest element of the span (0-indexed, counting 0) is .

Range and path queries

Prefix basis with timestamps (max subset XOR of online): insert into the prefix- basis, keeping at each pivot the vector with the latest index (swap when the incoming index is newer, then continue reducing the older one). A query on uses only pivots with index . build, per query, and it’s persistent (store one basis per prefix, memory).

ll b[B]; int pos[B];
void insert(ll x, int idx) {
    for (int i = B - 1; i >= 0; i--) if (x >> i & 1) {
        if (!b[i]) { b[i] = x; pos[i] = idx; return; }
        if (pos[i] < idx) swap(b[i], x), swap(pos[i], idx);
        x ^= b[i];
    }
}
// query [l, r] on the prefix-r snapshot: use b[i] only if pos[i] >= l

The same timestamp trick works on trees: the basis of the root-to- path keeps the deepest vectors, and a path query – merges two such bases restricted to depth depth(LCA). Alternatives: segment tree of bases ( per query), binary lifting, or offline divide and conquer.

Deletion: offline with a segment tree over time and an undo stack (record which b[i] was set). Online deletion of the most recently inserted element with the timestamp trick is also possible (“basis with deletion by time”).

Cycle space of a graph

Edge weights XORed around cycles form a linear space. Build a spanning tree with root XOR distances ; for each non-tree edge insert . Then:

  • the maximum XOR of a (not necessarily simple) path from to is maxXor(d[u] ^ d[v]);
  • the minimum is minXor(d[u] ^ d[v]);
  • the number of distinct path XOR values between and is .

Walks may repeat edges, which is why every cycle can be added for free (go to the cycle, around it, and back).

Over other fields

The same row-echelon insertion works over with vectors of dimension in per insertion: normalise the pivot with an inverse and eliminate. The span has elements and every reachable vector has representations. Gaussian elimination in general is in Gaussian Elimination. Beyond ‘s subset-XOR questions, the same machinery solves “is this modular linear system consistent, and how many solutions” and toggle puzzles like Lights Out (each move is a vector over , the reachable configurations are the span).

Matroid view

Linearly independent sets form a matroid. So “choose items maximising total weight such that their values are XOR-independent” is solved by sorting by weight and inserting greedily; the kept items are exactly those whose insertion enlarged the span — the same reason Kruskal’s algorithm is correct for spanning forests (a graphic matroid): both are “greedy is optimal for max-weight independent set in a matroid.”

Trie vs basis

“Choose a subset” means a basis; “choose one or two elements” means a binary trie (Binary Trie).

XOR basisBinary trie
Representsthe span of the setthe elements of the set
Max XOR over a subset✔✘
Max XOR with a single element✘✔
Count elements with a property✘✔
Deletionhard (offline / timestamp trick above)✔
Memory
Merge two structures