A persistent structure keeps every version. Path copying copies only the nodes an update touches and shares the rest, so each version costs memory. Because versions of a persistent segment tree have the same shape, two versions can be subtracted node by node, which turns prefix versions into range queries.

Persistent segment tree

struct PST {
    struct N { int l, r; ll s; };
    vector<N> t{{0, 0, 0}};                       // node 0 = empty tree (its children point to itself)
    int upd(int v, int l, int r, int pos, ll d) { // returns the new root
        int u = sz(t); t.push_back(t[v]); t[u].s += d;
        if (r - l > 1) {
            int m = (l + r) / 2;
            if (pos < m) { int c = upd(t[v].l, l, m, pos, d); t[u].l = c; }
            else { int c = upd(t[v].r, m, r, pos, d); t[u].r = c; }
        }
        return u;
    }
    ll sum(int v, int l, int r, int ql, int qr) {
        if (!v || qr <= l || r <= ql) return 0;
        if (ql <= l && r <= qr) return t[v].s;
        int m = (l + r) / 2;
        return sum(t[v].l, l, m, ql, qr) + sum(t[v].r, m, r, ql, qr);
    }
    int kth(int a, int b, int l, int r, ll k) {   // k-th smallest (0-indexed) in version b minus version a
        while (r - l > 1) {
            int m = (l + r) / 2; ll c = t[t[b].l].s - t[t[a].l].s;
            if (k < c) a = t[a].l, b = t[b].l, r = m; else k -= c, a = t[a].r, b = t[b].r, l = m;
        }
        return l;
    }
};

Note: take the child index into a local variable before writing t[u].l = ...: push_back inside the recursive call may reallocate t. Reserve nodes up front.

Range -th smallest: version = version plus inserted at its compressed value. Then is root[r+1] - root[l], and kth descends in . The same subtraction gives count of values in within , sum of the smallest, and median.

Distinct values in online: version has a 1 at the last occurrence (up to ) of each value; the answer is the sum over positions in version .

Mex of online: a persistent tree over values storing the last position of each value (version ); descend to the first value whose last position is (store the min in each node).

Other persistent structures

StructureCost per updateTypical use
persistent array (persistent segment tree leaves)the primitive for persistent DSU, persistent anything
persistent triemax XOR with an element of
persistent treap / WBLTversioned sequences, copy-paste of ranges (Treaps)
persistent leftist heapEppstein’s shortest paths
persistent DSUconnectivity in any version
persistent lazy segment tree with tag copying, or permanent tags that are never pushed downrange add on versions (Dynamic and Persistent Segment Trees)
persistent stack/queue / amortizedundo histories

Partial vs full persistence: partial persistence lets every old version be read, but only the newest is writable — a linear history, which covers essentially every contest use. Full persistence lets any version be updated, producing a version tree rather than a line; path copying gives this too, just by keeping a root per version and never assuming a linear order. Amortized structures (splay trees, doubling arrays, union by rank with compression) lose their bounds under persistence because an expensive old state can be reused many times.

Offline alternative: version tree

If all operations are known, put versions in a tree (each version is a child of the version it was derived from), then DFS it with an ordinary structure that supports undo: apply the operation on entry, answer the version’s queries, undo on exit. extra memory per operation instead of , and it works for structures that can’t be made persistent cheaply (DSU, bitsets, knapsack layers).

Memory checklist

  • Nodes: ; for that’s about nodes. With two int children and an int count, 12 bytes each, that’s about 90 MB.
  • Don’t build the initial tree; start from the null node 0 whose children are itself.
  • Use int indices, not pointers; store only needed fields.
  • If memory is tight: wavelet matrix ( bits) for static order statistics, or an offline sweep.

When to prefer something else

SituationUse instead
queries known in advancean offline BIT sweep: memory and faster
only the latest version mattersan ordinary segment tree
memory very tight, need range -tha wavelet tree — bits instead of integers