Binary Trie for XOR Problems
A binary trie stores numbers by their bits from most significant to least significant. It is the standard data structure for maximum XOR queries because XOR wants opposite bits: to maximize x ^ y, try to pair each bit of x with the opposite bit of y.
Trie Structure
Each node has up to two children, one for bit 0 and one for bit 1. Store a count if you need deletion or multiset behavior.
struct BinaryTrie {
struct Node {
int child[2] = {-1, -1};
int cnt = 0;
};
vector<Node> tr;
static const int LOG = 31;
BinaryTrie() { tr.push_back(Node()); }
};
Insertion
void insert(int x) {
int u = 0;
tr[u].cnt++;
for (int b = LOG; b >= 0; --b) {
int bit = (x >> b) & 1;
if (tr[u].child[bit] == -1) {
tr[u].child[bit] = tr.size();
tr.push_back(Node());
}
u = tr[u].child[bit];
tr[u].cnt++;
}
}
Maximum XOR Query
At each bit, greedily take the opposite child if it exists. This makes the current result bit 1, which is always better than 0 at a more significant position.
int max_xor(int x) {
int u = 0, ans = 0;
for (int b = LOG; b >= 0; --b) {
int bit = (x >> b) & 1;
int want = bit ^ 1;
if (tr[u].child[want] != -1 && tr[tr[u].child[want]].cnt > 0) {
ans |= 1 << b;
u = tr[u].child[want];
} else {
u = tr[u].child[bit];
}
}
return ans;
}
Deletion
Deletion is count decrement along the path. You do not need to physically remove nodes unless memory reuse matters.
void erase(int x) {
int u = 0;
tr[u].cnt--;
for (int b = LOG; b >= 0; --b) {
int bit = (x >> b) & 1;
u = tr[u].child[bit];
tr[u].cnt--;
}
}
Queries with Constraints
For "maximize x ^ y where y <= m", sort array values and queries by m. Insert eligible values into the trie as the limit grows, then answer each query.
sort(nums.begin(), nums.end());
sort(queries.begin(), queries.end(),
[](auto& a, auto& b){ return a.limit < b.limit; });
int ptr = 0;
for (auto& q : queries) {
while (ptr < nums.size() && nums[ptr] <= q.limit) {
trie.insert(nums[ptr++]);
}
answer[q.id] = ptr == 0 ? -1 : trie.max_xor(q.x);
}
Complexity and Memory
- Insertion: O(LOG), usually O(31) for non-negative
int. - Query: O(LOG).
- Memory: O(number of inserted values * LOG) in the worst case.
- Use vectors of nodes instead of heap-allocated pointers for speed in CP.
Practice Problems
- LeetCode 421 - Maximum XOR of Two Numbers basic trie insert values and query greedily.
- LeetCode 1707 - Maximum XOR With an Element From Array offline sort by constraint.
- LeetCode 1803 - Count Pairs With XOR in a Range counting trie count pairs below a threshold.
- CSES - Maximum Xor Subarray prefix xor query against previous prefixes.