What the standard containers really cost, how to hash safely and fast, and a decision map to pick the right structure for a query pattern. Per-container API details are in C++ for CP.

Real costs

ContainerNotes
vectorcontiguous; growth doubles capacity, so push_back is amortized but may invalidate references; reserve when the size is known
array-backed stack / queuea vector plus a head index beats std::queue (which is a deque)
dequeblocks of 512 bytes; at both ends; about 2x slower than vector and has a big per-instance overhead (avoid vector<deque> with instances)
listalmost never: one allocation per node, no locality. Use an array-based linked list (nxt[i], prv[i]) when you need unlink
set/mapred-black tree, ~40-50 bytes per node, cache-unfriendly; with a constant around 10x a sorted vector
priority_queuebinary heap over a vector; fast
unordered_mapchaining, one allocation per node; slow constant, hackable

Array-based linked list (for “delete element, find neighbours” simulations, like removing elements in order and asking for the new neighbours):

vector<int> L(n), R(n);
for (int i = 0; i < n; i++) L[i] = i - 1, R[i] = i + 1;
auto erase = [&](int i) { if (L[i] >= 0) R[L[i]] = R[i]; if (R[i] < n) L[R[i]] = L[i]; };

Processing deletions in reverse turns them into insertions, which sometimes removes the need for a balanced tree.

Hashing

OptionSpeedNotes
sort + lower_bound / compress to [0, m)fastestwhen all keys are known in advance
gp_hash_table<K, V, SafeHash> (PBDS)3-5x faster than unordered_mapopen addressing
unordered_map<K, V, SafeHash> + reserve + max_load_factor(0.25)ok
hand-written linear probingfastest dynamicfixed capacity, power of two, keep load

Always randomise the hash (splitmix64 with a time seed, see C++ for CP). On Codeforces anti-hash tests target the default identity hash and even fixed custom hashes.

template<class V> struct HashMap {                     // u64 keys, linear probing, no erase
    vector<ull> k; vector<V> v; vector<char> used; ull mask, seed;
    HashMap(int lg) : k(1 << lg), v(1 << lg), used(1 << lg), mask((1 << lg) - 1),
        seed(chrono::steady_clock::now().time_since_epoch().count()) {}
    static ull mix(ull x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); }
    V& operator[](ull key) {
        ull i = mix(key + seed) & mask;
        while (used[i] && k[i] != key) i = (i + 1) & mask;
        if (!used[i]) used[i] = 1, k[i] = key, v[i] = V();
        return v[i];
    }
};

Erasing from linear probing: use backward-shift deletion (move later entries of the same cluster back if their home slot permits) instead of tombstones.

Hashing composite keys: for pairs (a, b) of 32-bit values, use (ull)a << 32 | b. For vectors or sets, use polynomial or sum-of-random-values hashing (set hashing).

Bloom filter: bits, hashes, keys gives a false-positive rate , minimised at (about 0.6% at 10 bits per key). No false negatives. Rarely needed in contests, but it’s the standard answer for “approximate membership in little memory”.

Which structure? (decision map)

Array with range queries
├─ static, idempotent (min, max, gcd)          -> sparse table, O(1)
├─ static, any associative op                  -> disjoint sparse table / sqrt tree
├─ static, k-th smallest / count <= x          -> wavelet matrix, persistent segment tree, merge sort tree
├─ point update, prefix sums                   -> Fenwick tree
├─ point update, any monoid                    -> segment tree (bottom-up)
├─ range update + range query                  -> lazy segment tree (or two Fenwicks for add/sum)
├─ chmin / chmax / historic values             -> segment tree beats
├─ insert / delete / reverse inside the array  -> implicit treap, splay
├─ operation that doesn't merge                -> sqrt decomposition, Mo
└─ all queries known in advance                -> offline sweep, Mo, CDQ, parallel binary search

Tree
├─ subtree queries                              -> Euler tour + Fenwick / segment tree
├─ path queries, static tree                    -> HLD, binary lifting (no updates)
├─ count / optimise over all paths              -> centroid decomposition
├─ queries on small vertex subsets              -> virtual tree
├─ subtree aggregates offline                   -> small-to-large (DSU on tree)
└─ link / cut online                            -> link-cut tree, Euler tour tree, top tree

Sets
├─ merge only                                   -> DSU (+ rollback / parity / weights)
├─ ordered with k-th / rank                     -> PBDS tree, Fenwick over values, treap
├─ min / max with deletions                     -> heap + lazy deletion, multiset
├─ meldable priority queues                     -> leftist / skew / pairing heap, small-to-large
├─ predecessor over integers in [0, 2^20)       -> 64-ary bit tree ("fast set")
└─ max XOR with an element / subset             -> binary trie / XOR basis

Strings                                          -> hashing, suffix array / automaton, Aho-Corasick, eertree
Geometry                                         -> Li Chao / CHT, KD-tree, sweep + BIT
Versions / history                               -> persistent segment tree / treap

Each structure has a note in this chapter or in Range Queries, Trees and Strings.

Container internals and pitfalls

ContainerKey factComplexity
vectordoubling capacity amortizes growth (proof below)access ; unsorted search ; sorted search ; push/pop back amortized; insert/erase elsewhere
singly/doubly linked listno random access; splice once you’re at the nodeaccess/search ; insert/delete at a known node (doubly: also to delete given only that node, since it knows its predecessor; singly needs a predecessor scan, )
stack (LIFO)only the most recent element is reachable; array- or list-backedpush/pop/top all
queue (FIFO) / dequequeue needs a circular buffer or list to pop the front in (a plain array shifting elements is ); deque generalizes to both ends plus random accesspush/pop front and back ; deque random access
unordered_map/setchaining (this repo’s default) vs. open addressing (PBDS gp_hash_table, hand-rolled linear probing) are the two collision strategies average, if hash-attacked (see Hashing above)
map/setred-black tree; the two abilities a hash table lacks: sorted iteration and lower_bound/upper_boundinsert/erase/find/lower_bound ; in-order traversal total
priority_queuebinary heap = complete binary tree stored implicitly in an array; max-heap by default, pass greater<int> for minpush/pop ; peek ; build from elements (proof: Heaps)
trieeach root-to-node path spells a shared prefixinsert/search a length- string: , independent of how many other strings are stored (binary trie over bits: Binary Trie)
DSUunion by size/rank + path compression amortized (Tarjan) — full proofs, including the undo/persistent variants, in DSU, not reproduced here
bitset<N>packs bits into machine wordsAND/OR/XOR/count/shift over all bits: instead of (detail: Bitset)

Iterator and reference invalidation — the classic silent-corruption source, worth a table of its own:

ContainerInsertErase
vectorinvalidates everything if capacity grows; otherwise only iterators/pointers from the insertion point onwardinvalidates the erased element and everything after it
dequeinvalidates all iterators; references/pointers survive unless the insert is in the middleinvalidates iterators to the erased element; references/pointers to others survive only if the erase is at an end
list / forward_listnever invalidates existing iterators/references/pointersinvalidates only the erased element’s own iterator
map / set / multimap / multisetnever invalidates existing iterators/references/pointersinvalidates only the erased element’s own iterator
unordered_map / unordered_setinvalidates all iterators on rehash (references/pointers survive); reserve upfront avoids itinvalidates only the erased element’s own iterator

The practical rule: vector and deque are the only containers where modifying the container can silently invalidate an iterator you’re still holding to a different element — the node-based containers (list, map/set, unordered_map/set) never do that, which is exactly why “erase while iterating” is safe for them (it = c.erase(it)) but needs care for vector/deque.

Array declaration choices:

TypeSizeNotes
int a[N] (global)compile-timezero-initialised, no stack limit, fastest
int a[N] (local)compile-timeon the stack, ~1–8 MB limit, uninitialised
std::array<int, N>compile-timevalue semantics, usable as map keys
std::vector<int>runtimereserve, push_back amortised
vector<vector<int>>2D runtimerows can be ragged, slower than a flat array
flat vector<int>(n * m)2Dindex i * m + j, cache-friendly

Pitfalls:

MistakeFix
accumulate(..., 0) sums in int and overflowsseed with 0LL
memset(a, 1, sizeof a) sets bytes to 1, giving 16843009 per int, not 1only safe for 0 and -1
clearing a big global array every test case, per testclear only the used prefix
vector<bool> is bit-packed: slow, not addressableuse vector<char> or bitset
out-of-bounds writes corrupt neighbours silentlycompile with -D_GLIBCXX_DEBUG when debugging
std::list: one allocation per node, cache missesprefer an array-based linked list (nxt[i]/prv[i])
missing sentinels at a linked list’s endsforces null checks everywhere; add them
deleting a linked-list node twice corrupts ittrack a deleted flag
std::deque’s per-element overhead in a hot loop (e.g. BFS)a vector with a head index is faster
priority_queue with greater<> needs the full priority_queue<T, vector<T>, greater<T>>the two-argument form doesn’t compile
front()/top() on an empty queue/stackundefined behaviour — check empty() first

Classic pointer-chasing exercises worth having solved once: reverse a list (three pointers), detect a cycle (Tortoise and Hare), find the middle (slow/fast pointers), merge two sorted lists, intersect two lists (switch-heads trick). Doubly linked list + hash table is the standard LRU cache: the list keeps eviction order, the hash table gives lookup of any node.

Stack applications, since “most recent unmatched thing” recurs constantly:

ProblemIdea
balanced bracketspush opens, pop on matching close
next/previous greater elementMonotonic Stack
largest rectangle in a histogrammonotonic stack of increasing heights
expression evaluationtwo stacks (values, operators), or shunting-yard
iterative DFSexplicit stack instead of recursion
undo operationsstack of changes (rollback DSU)
stock span / days until warmermonotonic stack
min-stackstore (value, min so far) pairs
queue with two stacksamortised , aggregates any associative op