“What is the -th smallest element of ?” — and its relatives: rank, count in a value range, median.
The methods
| Method | Preprocess | Query | Memory | Updates | Online |
|---|---|---|---|---|---|
| Sort the range | — | ✔ | ✔ | ||
nth_element on a copy | — | ✔ | ✔ | ||
| Merge sort tree + binary search | ✘ | ✔ | |||
| Persistent segment tree | ✘ | ✔ | |||
| Wavelet tree | bits | ✘ | ✔ | ||
| Offline: sort by value + BIT | — | am. | ✔ | ✘ | |
| Mo’s + a value BIT | — | ✘ | ✘ | ||
| Parallel binary search + BIT | — | am. | ✔ | ✘ | |
| BIT of sorted vectors (“BIT-of-BIT”) | ✔ | ✔ |
The two to know: persistent segment tree (online) and the offline BIT sweep (simpler, less memory).
Persistent segment tree — the online answer
Build version by inserting into version , with the tree indexed by value. Then version minus version is a segment tree over exactly the values in , and descending it finds the -th smallest.
int kth(int u, int v, int lo, int hi, int k) { // u = root[l-1], v = root[r]
if (lo == hi) return lo;
int leftCount = cnt[left[v]] - cnt[left[u]];
int mid = (lo + hi) / 2;
if (k <= leftCount) return kth(left[u], left[v], lo, mid, k);
else return kth(right[u], right[v], mid+1, hi, k - leftCount);
}per query, nodes. Compress the values first.
The same structure answers:
- count of elements in ,
- count in a value range,
- sum of the smallest (store sums alongside counts),
- the median (with ).
Offline: sort by value and sweep
If the queries are known in advance, this is simpler and uses memory:
// binary search the answer VALUE for all queries in parallel
// (parallel binary search), with a BIT counting positions of inserted valuesOr, for “count in ” specifically, sort both the array elements and the queries by value and sweep with a BIT over positions — time, memory. See Offline Query Processing.
With updates
Point updates plus -th-smallest queries need either:
- BIT of segment trees (a “2D” structure), per operation, memory — heavy;
- parallel binary search offline, amortized, memory — much lighter;
- sqrt decomposition with sorted blocks, per query, to rebuild a block on update.
The offline route is nearly always the right choice in a contest.
The whole array, not a range
For “-th smallest of the entire array”:
nth_element(a.begin(), a.begin() + k, a.end()); // O(n) expected
int kth = a[k];expected with introselect. For a guaranteed , median-of-medians exists but is slower in practice.
Related queries
| Query | Method |
|---|---|
| Rank of in | count of elements |
| Median of | |
| Count in the value range | difference of two counts |
| Sum of the smallest | persistent segtree storing sums |
| Mode (most frequent) of | different problem — offline, or an preprocessing |
| -th smallest on a tree path | persistent segment tree indexed by root-path, combined with LCA |
| -th distinct value | a distinct-count structure plus a descent |
The tree-path version is a nice combination: build a persistent segment tree along each root-to-vertex path, then the multiset on the path is where is the LCA — four versions descended in lockstep.
See also: Persistent Structures · Wavelet Tree · Parallel Binary Search