When keys are integers in , word-level tricks beat comparison-based trees. The 64-ary bit tree (“fast set”) gives insert, erase, successor and predecessor in , at most 4 levels for , with words of memory. It’s typically 5-10x faster than std::set.
64-ary bit tree
struct FastSet {
int n; vector<vector<ull>> s; // s[0] = elements, s[h] = non-empty words of level h-1
FastSet(int n_) : n(n_) { int m = n; do { m = (m + 63) >> 6; s.emplace_back(m); } while (m > 1); }
bool has(int x) const { return s[0][x >> 6] >> (x & 63) & 1; }
void insert(int x) { for (auto& v : s) { v[x >> 6] |= 1ULL << (x & 63); x >>= 6; } }
void erase(int x) { for (auto& v : s) { v[x >> 6] &= ~(1ULL << (x & 63)); if (v[x >> 6]) break; x >>= 6; } }
int next(int x) const { // smallest element >= x, or -1
if (x < 0) x = 0;
if (x >= n) return -1;
for (int h = 0; h < sz(s); h++) {
if ((x >> 6) >= sz(s[h])) break;
ull d = s[h][x >> 6] >> (x & 63);
if (!d) { x = (x >> 6) + 1; continue; }
x += __builtin_ctzll(d);
for (int g = h - 1; g >= 0; g--) x = x << 6 | __builtin_ctzll(s[g][x]);
return x;
}
return -1;
}
int prev(int x) const { // largest element <= x, or -1
if (x >= n) x = n - 1;
for (int h = 0; h < sz(s) && x >= 0; h++) {
ull d = s[h][x >> 6] << (63 - (x & 63));
if (!d) { x = (x >> 6) - 1; continue; }
x -= __builtin_clzll(d);
for (int g = h - 1; g >= 0; g--) x = x << 6 | (63 - __builtin_clzll(s[g][x]));
return x;
}
return -1;
}
};Proof: the 64-ary bit tree is per operation, memory
Level has words; level has words, so level has words, and the number of levels until this shrinks to is . Total memory is (geometric series).
insert/erasetouch exactly one word per level (the ancestor-word chain of ), so both are .next/prevclimb at most one level up to find a non-empty word (each level up covers more ground, so within levels a set bit must exist if any element exists at all, since the top level has words covering the whole universe), then descend the same number of levels reading one word each — so both are as well.
Uses: set of alive indices in simulations (“delete , find nearest alive neighbours”), “next free slot”, Dijkstra or BFS with small integer keys (bucket queue over a fast set), sweep lines over compressed coordinates, Mo’s algorithm needing a successor structure with -ish updates.
For “-th smallest” or counts you still need a Fenwick tree over the universe; the fast set only does membership, successor and predecessor.
Other integer structures
| Structure | Operations | Notes |
|---|---|---|
| van Emde Boas | pred/succ/insert/delete in | recursion (split each key into a high half and low half over ); memory naively, with hashing (a “y-fast trie”-style implementation); beats a balanced BST when is small — gives vs. . In practice a bitset with _Find_next is faster for small universes, and a hash set plus a BIT usually beats both in code length |
| x-fast / y-fast trie | pred/succ in | binary search over prefix lengths using hash tables of prefixes |
| fusion tree | pred in | a -tree with branching factor ; each node compares a key against stored keys in via a “sketch and multiply” word trick, beating comparison-based bounds; combined with integer sorting gives sorting — theory, not contest code |
| bucket queue | monotone priority queue over small keys | Dial’s algorithm |
| radix heap | monotone priority queue, | Heaps |
| rank/select bit vector | rank and select in bits | basis of succinct structures below |
| tango / multi-splay tree | -competitive with the optimal offline BST for any access sequence | relevant to the open dynamic optimality conjecture (is a splay tree -competitive? open since 1985) |
| Brodal queue / hollow heap | worst-case insert, meld, find-min, decrease-key; delete-min | Brodal (1996) is the theoretical optimum for a meldable priority queue but enormously complicated; the hollow heap (2015) reaches the same amortized bounds far more simply |
| succinct structures (FM-index, succinct trees) | space close to the information-theoretic minimum, still fast queries | FM-index: a compressed full-text index, what DNA aligners use; succinct trees: bits with navigation; genuinely used in bioinformatics and search engines where data doesn’t otherwise fit in memory |
Proof: solves to
Substitute , so and the recurrence becomes . Writing , this is , which solves directly to . Since gives , substituting back gives .
In contests the 64-ary bit tree covers what vEB would; the others are worth knowing by name.
Ideas that transfer
- Word-level parallelism: packing 64 booleans or 8 bytes into a word and operating on them at once is what bitsets, Shift-Or matching, bit-parallel LCS and the fast set all do (Bitset).
- Four Russians: precompute answers for all small blocks (size ) and combine. That’s the trick behind -preprocessing RMQ (RMQ), boolean matrix multiplication and some LCS speedups.
- Trading exactness for speed: soft heaps (constant-time operations, some keys corrupted) underpin Chazelle’s MST. Approximate counting and sketches (Count-Min, HyperLogLog) use bounded error to save memory.
- Retroactive structures support “insert an operation into the past” and see the effect on the present — partially retroactive (query only the present) or fully retroactive (query any point in time). Related to, but stronger than, persistence: persistence lets you read the past, retroactivity lets you change it.
- Dynamic optimality: whether splay trees are -competitive with every BST is open since 1985. Tango trees achieve -competitiveness.
- Rank/select and succinct structures (wavelet trees, bits, queries) buy space close to the information-theoretic minimum at a small query-time cost — see the table above.