A treap is a BST on keys and a heap on random priorities, so it has expected depth with no balancing code. Everything is built from split and merge. Keyed by position (implicit treap), it becomes an array that supports insert, delete, reverse and move of ranges, which segment trees can’t do.

Implicit treap with lazy reverse, add and range sums

mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
struct Node { int l = 0, r = 0, sz = 1; unsigned pr; ll val, sum, add = 0; bool rev = false; };
vector<Node> t(1);                               // t[0] is the null node (sz 0)
int make(ll v) { Node x; x.pr = rng(); x.val = x.sum = v; t.push_back(x); return sz(t) - 1; }
void apply(int x, ll a, bool r) {
    if (!x) return;
    t[x].val += a; t[x].sum += a * t[x].sz; t[x].add += a;
    if (r) swap(t[x].l, t[x].r), t[x].rev ^= 1;
}
void push(int x) { if (t[x].add || t[x].rev) apply(t[x].l, t[x].add, t[x].rev), apply(t[x].r, t[x].add, t[x].rev), t[x].add = 0, t[x].rev = false; }
void pull(int x) { t[x].sz = 1 + t[t[x].l].sz + t[t[x].r].sz; t[x].sum = t[x].val + t[t[x].l].sum + t[t[x].r].sum; }
void split(int x, int k, int& a, int& b) {        // first k elements -> a, rest -> b
    if (!x) { a = b = 0; return; }
    push(x);
    if (t[t[x].l].sz < k) split(t[x].r, k - t[t[x].l].sz - 1, t[x].r, b), a = x;
    else split(t[x].l, k, a, t[x].l), b = x;
    pull(x);
}
int merge(int a, int b) {
    if (!a || !b) return a ^ b;
    if (t[a].pr > t[b].pr) { push(a); t[a].r = merge(t[a].r, b); pull(a); return a; }
    push(b); t[b].l = merge(a, t[b].l); pull(b); return b;
}
// operate on [l, r): split into A | B | C, modify B, merge back
template<class F> void onRange(int& root, int l, int r, F f) {
    int a, b, c; split(root, l, a, b); split(b, r - l, b, c); f(b); root = merge(merge(a, b), c);
}
OperationHow
insert at position split at , merge left, new node, right
erase split out the middle and drop it
reverse apply(mid, 0, true)
range add / range sumlazy tag / subtree aggregate
cyclic shift of by split into three parts and merge them in rotated order
move a block elsewhere, swap two blockssplit and merge in the new order
-th element, position of a nodedescend by sizes / walk up with parent pointers

Reach for the implicit treap specifically when a problem changes the length or order of the sequence — “move to the front”, “reverse then answer a range query”, “insert at position ”, “delete a range then keep querying”, a text editor with undo (make it persistent) — since a segment tree assumes a fixed set of positions and cannot do any of these.

Keyed (non-implicit) treaps split by key instead of size. Pass a key comparison in split, and it gives an ordered multiset with any augmentation you like (sum of the smallest, max in a key range).

Union of two keyed treaps with interleaved keys runs in expected (a known but non-trivial result for randomized treap union — the recursion splits the larger treap by each node of the smaller one’s path, and bounding the total split work needs a careful accounting of how priorities interleave; not rederived here):

int unite(int a, int b) {                         // keyed treap; splitByKey(x, key, l, r): keys < key go left
    if (!a || !b) return a ^ b;
    if (t[a].pr < t[b].pr) swap(a, b);
    int L, R; splitByKey(b, t[a].val, L, R);
    t[a].l = unite(t[a].l, L); t[a].r = unite(t[a].r, R); pull(a);
    return a;
}

Persistent treap: copy each node before modifying it in split/merge (and in push, since children change). That gives versioned arrays, “copy a range and paste it” with cost, and undo history. With copy-paste operations, the random priorities become correlated, so use the “merge by random choice proportional to sizes” variant (choose a as root with probability ) instead of stored priorities.

Practical: preallocate the node array (t.reserve), use iterative pointers or indices, and expect a treap to be about 3-5x slower than a segment tree for the same job. Seed the RNG from chrono::steady_clock — a fixed seed is hackable on Codeforces. Recursion depth is expected but can spike occasionally; for this is still fine in practice.

Choosing a balanced BST

TreeHeightNotes
treapexpected easiest to write; split/merge; persistent-friendly
splayamortized moves accessed nodes to the root; the engine of link-cut trees (Link-Cut Tree)
red-black (std::set)what the STL gives; can’t be augmented directly
PBDS treeorder statistics for free (PBDS)
AVLdeterministic, strictest balance, fastest lookups, more rotations than red-black
weight-balanced (WBLT)popular for persistent sequences (no randomness, merge by sizes)
scapegoatamortized rebuild unbalanced subtrees; no per-node balance data; useful when nodes carry heavy structures (KD-tree insertions)
B-tree / B+-treemany keys per node, built for disk/cache locality rather than comparisons — every database uses one, essentially never hand-written in CP
skip listexpected probabilistic linked levels; mostly of theoretical interest in CP (proof below)

If you need a balanced BST you can extend — with subtree aggregates, lazy propagation, split and merge — write a treap: it is 40 lines, the balancing is a coin flip, and split/merge make range operations trivial. Splay trees are the alternative when you need the access locality property or are building link-cut trees. AVL, red-black and B-trees are worth understanding but almost never worth implementing from scratch in a contest.

Two classic std::set/multiset bugs

  1. multiset::erase(value) erases every copy. Use erase(find(value)) to remove one.
  2. std::lower_bound(s.begin(), s.end(), x) on a set is — the iterators are not random access. Always use the member function s.lower_bound(x).

Skip list

A probabilistic ordered set built from linked lists with random levels: each key gets a tower of height with , and a search descends from the top level, moving right while the next key is still smaller. Expected insert/erase/search/lower_bound, expected memory; duplicates are kept (erase removes one copy). Mostly of theoretical interest in CP — a treap or std::set dominates it in practice.

Splay tree in one paragraph

Each access rotates the node to the root with zig-zig/zig-zag steps. With potential , the amortized cost of an access is (access lemma). Implicit splay trees do everything implicit treaps do.

// ch[x][0/1] children, fa[x] parent; rotate x above its parent
void rotate(int x) {
    int y = fa[x], z = fa[y], k = ch[y][1] == x;
    if (z) ch[z][ch[z][1] == y] = x;
    fa[x] = z; ch[y][k] = ch[x][!k]; if (ch[x][!k]) fa[ch[x][!k]] = y;
    ch[x][!k] = y; fa[y] = x; pull(y); pull(x);
}
void splay(int x, int goal = 0) {                 // push lazies from the root down to x first
    while (fa[x] != goal) {
        int y = fa[x], z = fa[y];
        if (z != goal) rotate((ch[z][1] == y) == (ch[y][1] == x) ? y : x);
        rotate(x);
    }
}

Cartesian tree

The treap of (index, value) pairs: built in with a monotonic stack (construction and correctness proved in Monotonic Stack), root at the minimum, in-order traversal = the array order. RMQ is LCA in it. Equal values: break ties consistently (e.g. the leftmost occurrence becomes the ancestor) so the tree is well-defined. Depth can be on adversarial input (a sorted array), so traverse it iteratively rather than recursively.