A trie over the bits of integers, most significant bit first. Because a higher bit outweighs all lower bits together, XOR questions become greedy walks down the trie. With subtree counts it also answers order statistics and “count with ”.

Implementation

struct BinaryTrie {
    static const int B = 30;
    vector<array<int, 2>> nx{{0, 0}}; vector<int> cnt{0};
    void add(int x, int d) {                       // d = +1 insert, -1 erase
        int v = 0; cnt[0] += d;
        for (int b = B - 1; b >= 0; b--) {
            int c = x >> b & 1;
            if (!nx[v][c]) nx[v][c] = sz(nx), nx.push_back({0, 0}), cnt.push_back(0);
            v = nx[v][c]; cnt[v] += d;
        }
    }
    int maxXor(int x) {                            // set must be non-empty
        int v = 0, r = 0;
        for (int b = B - 1; b >= 0; b--) {
            int c = (x >> b & 1) ^ 1;
            if (nx[v][c] && cnt[nx[v][c]]) r |= 1 << b, v = nx[v][c]; else v = nx[v][c ^ 1];
        }
        return r;
    }
    int countLess(int x, int K) {                  // # stored y with (x ^ y) < K
        int v = 0, res = 0;
        for (int b = B - 1; b >= 0 && v >= 0; b--) {
            int xb = x >> b & 1, kb = K >> b & 1;
            if (kb) { if (nx[v][xb]) res += cnt[nx[v][xb]]; v = nx[v][xb ^ 1] ? nx[v][xb ^ 1] : -1; }
            else v = nx[v][xb] ? nx[v][xb] : -1;
        }
        return res;
    }
};

Memory is nodes. Preallocate when and (6 million nodes, about 72 MB as two int arrays plus counts).

Queries

QueryWalk
max prefer the opposite bit

| min | prefer the same bit |
| count with | where has a 1, add the subtree that keeps the XOR bit at 0, and continue on the other side |
| -th smallest | descend comparing with the count on the preferred side |
| -th smallest stored value, rank of | same walks with |
| max with | walk with a “tight to ” flag, like digit DP; or offline: sort queries by and insert values as grows |

Classic problems

  • Maximum XOR subarray: ; insert prefix XORs as you go, query each new prefix. .
  • Count subarrays with XOR or in : the same with countLess, .
  • -th largest XOR among all pairs: binary-search-free descent over all elements simultaneously: keep for each its current trie node and decide each bit of the answer by summing the counts (CF 241B style), .
  • Max XOR of with an element of : persistent trie; version minus version via counts, or store the max index in each subtree and require it to be in version . per query.
  • Max XOR of with values in a subtree: Euler tour + persistent trie, or small-to-large merging of tries.
  • Minimum spanning tree with weights (Borůvka on a trie, or divide and conquer on the top bit): split by the highest bit, recurse on both halves, and connect them with one minimum-XOR edge found by walking one half’s elements in the other half’s trie. , or with care.
  • Merging tries: merge two tries in time proportional to their common part, like segment tree merging. Small-to-large on tree subtrees.

Trie or basis?

“Choose one element” (or a pair) means the trie. “Choose any subset” means the XOR basis (XOR Basis). The trie stores elements and supports deletion and counting; the basis stores the span. They answer different questions: a trie is about the elements you inserted, a basis is about all XOR combinations of them.

Binary trieXOR basis
Storesa multiset of valuesa linear span
Max XOR with a query
Max XOR over all subsets✘✔
Count values with a property✔✘
-th smallest✔✔ (over the span)
Deletion✔hard
Memory