Named algorithms that matter for the theory (or for production libraries) more than for contests: what each achieves, how, and what to write instead. Asymptotic complexity is not running time: several entries are galactic (faster only for inputs larger than anything that will exist), and Dinic, Kruskal, Hungarian, KMP and NTT beat their “faster” relatives at every contest size.

AreaAlgorithmResultWrite instead
primalityAKSPRIMES P, Miller–Rabin
primalityLucas–LehmerMersenne in squarings(it is the practical test)
factoringLenstra ECM, depends on the smallest factorPollard’s rho
factoringquadratic sievePollard’s rho
sortingHan; Han–Thorup; expectedradix sort
multiplicationToom–Cook, Schönhage–Strassen, Fürer, Harvey–van der Hoeven, , , Karatsuba / NTT
DFTGood–Thomas, Raderno twiddles for coprime factors; prime lengths via convolutionradix-2 FFT / NTT
max flowKarzanov, MPM, Ahuja–Orlin, Goldberg–Tarjan, Orlin, , , , Dinic
min-cost flowGoldberg–Tarjan cost scalingSSP with potentials
matchingGabow scaling, Gabow–Tarjan, Micali–Vazirani, , Hungarian, Hopcroft–Karp, blossom
MSTFredman–Tarjan, Chazelle, Karger–Klein–Tarjan, Pettie–Ramachandran, , expected, optimalKruskal
dynamic MST / treesFrederickson (topology trees) per updateoffline segment tree + rollback DSU
LCAHarel–Tarjan, Tarjan / Mehlhorn offline; binary lifting, Euler tour + sparse table
planar min cutReif, now dual shortest path (Dijkstra)
string matchingApostolico–Giancarlo, Galil–Seiferas, Crochemore–Perrin (Two-Way) comparisons; space; spaceKMP / Z
multi-patternCommentz–Walter, Wu–Manbersublinear on averageAho–Corasick
suffix treesWeiner, McCreight, Farach; ; for integer alphabetssuffix automaton / suffix array

Number theory and arithmetic

AKS primality (Agrawal, Kayal, Saxena, 2002)

For : is prime . Checking this directly costs coefficients; AKS checks it modulo the small polynomial for a bounded range of . Algorithm: (1) if with , composite; (2) find the smallest with ; (3) if for some , composite; (4) if , prime; (5) for , if , composite; (6) prime. in the paper, with Lenstra–Pomerance, polynomial space. It resolved an old question (primality in P unconditionally; Miller had it only under the generalized Riemann hypothesis) but nobody uses it: deterministic Miller–Rabin with fixed bases takes under a microsecond for 64-bit inputs where AKS takes seconds, and ECPP produces certificates far faster for huge .

TestTimeDeterministic?Practical?
trial divisionyestiny
Miller–Rabin, fixed basesyes for 64-bityes: Primality and Factorization
Miller–Rabin, random baseserror yes
Baillie–PSWno counterexample knownyes
ECPPheuristic yes, with a certificatehuge
AKSyesno

Lucas–Lehmer (Mersenne primes)

With , : for an odd prime , is prime . Reduction mod needs no division (: fold the high bits onto the low ones). squarings of -bit numbers: schoolbook, with FFT / IBDWT multiplication; around this is days of GPU time per candidate, which is what GIMPS distributes. Facts: prime needs prime (), the converse fails (); 52 Mersenne primes are known; the largest known prime has been a Mersenne prime almost continuously since 1952; even perfect numbers are exactly with prime (Euclid–Euler). Relatives: Lucas–Lehmer–Riesel for , Pépin’s test for Fermat numbers .

bool lucasLehmer(int p) {                                // M_p = 2^p - 1 prime? p prime, p <= 61 (i128 products)
    if (p == 2) return true;
    const ull M = (1ULL << p) - 1; ull s = 4;
    auto modM = [&](i128 x) { while (x > (i128)M) x = (x & M) + (x >> p); return (ull)(x == M ? 0 : x); };   // 2^p = 1
    for (int i = 0; i < p - 2; i++) s = modM((i128)s * s + M - 2);   // + M keeps the value non-negative
    return s == 0;
}

Lenstra’s elliptic curve method (ECM)

Pollard’s : if is -smooth then and reveals , but is fixed. Lenstra: replace by the group of an elliptic curve mod , whose order lies in (Hasse) and differs from curve to curve: unlimited retries. Pick a random curve and point (choose and set ), compute with ; point addition needs inverses mod , and a failed inversion hands out , a factor: arithmetic “mod ” is a group modulo each prime simultaneously, and the failure happens when modulo but not modulo another factor, i.e. when the curve’s order mod is -smooth. Running time in the smallest prime factor , times per curve operation: the best method for medium factors (up to 60–70 digits) of huge numbers. Practice: Montgomery curves and the ladder (no inversions in the inner loop), stage 2 (one extra prime between and ; roughly doubles effectiveness).

Factor digitsCurves
152 000~25
2011 000~90
30250 000~700
403 000 000~2 400
5044 000 000~7 500

Quadratic sieve

Find with ; then is a proper factor (Fermat’s idea). (1) Factor base: primes up to a bound with (otherwise never divides ). (2) is small for small , hence often smooth. (3) Sieve: on arithmetic progressions (roots by Tonelli–Shanks); subtract along them and keep entries reaching : the entire speed advantage. (4) Collect smooth relations, exponent vectors mod 2. (5) A dependency over exists ( vectors in dimension ), found by block Lanczos or Wiedemann (huge sparse matrix). (6) Its product is a square on both sides; a trivial gcd happens about half the time, then try another dependency. Time , factor base about primes. Variants: MPQS (many polynomials), SIQS (self-initialising, the fastest QS). The idea “build relations, then linear algebra over ” recurs in XOR bases and subset-parity problems.

Digits of Best method
(64-bit)Pollard’s rho
20–60ECM (especially if some factor is small)
60–100quadratic sieve
general number field sieve,

Integer sorting in the word RAM

The bound holds for comparison sorting; the word RAM (arithmetic, bit operations and indexing on -bit words in ) is a stronger model. Techniques: packed sorting (several keys per word, sorted by a constant number of word operations, a bitonic network with masks and one multiplication), signature sorting (sort short hashed signatures, then repair ties), fusion trees (Fredman–Willard: branching factor , a node compared in by “sketch and multiply”; sorting), range reduction (halve the key length recursively: the ). None is usable in practice, but the ideas are: LSD radix sort beats std::sort 3–5 times on large 32-bit arrays (worth a template for ), word packing is bitset optimization, and “sort by a cheap key, then repair ties” is how prefix doubling works.

AlgorithmTimeSpace
comparison sort–
counting sort
radix sort, base
fusion trees
Han (2002) deterministic
Han–Thorup expected
?open
void radixSort(vector<uint32_t>& a) {                      // LSD, 4 passes of 8 bits, stable
    vector<uint32_t> b(sz(a));
    for (int shift = 0; shift < 32; shift += 8) {
        int cnt[257] = {};
        for (uint32_t x : a) cnt[((x >> shift) & 255) + 1]++;
        for (int i = 0; i < 256; i++) cnt[i + 1] += cnt[i];
        for (uint32_t x : a) b[cnt[(x >> shift) & 255]++] = x;
        swap(a, b);
    }
}

Multiplication: Toom–Cook, Schönhage–Strassen, Fürer

Toom–Cook treats each operand as a polynomial in the split radix with pieces, evaluates both at small points ( = leading coefficient), multiplies pointwise ( recursive products of size ), interpolates the coefficients by a fixed sequence of additions and exact small divisions, and recomposes with carries: . Karatsuba is (), Toom-3 gives , Toom-4 , and approaches 1 while the interpolation constant explodes, so libraries stop around Toom-4 to Toom-8 and switch to FFT. FFT is the same evaluate–multiply–interpolate at roots of unity, where evaluation and interpolation themselves cost ; unbalanced Toom variants handle operands of very different sizes.

// Polynomial product by Toom-3 (exact integer coefficients), schoolbook below 32 terms.
vector<ll> toom3(vector<ll> a, vector<ll> b) {
    int n = max(sz(a), sz(b));
    if (n <= 32) {
        vector<ll> c(sz(a) + sz(b) - 1, 0);
        for (int i = 0; i < sz(a); i++) for (int j = 0; j < sz(b); j++) c[i + j] += a[i] * b[j];
        return c;
    }
    int k = (n + 2) / 3; int na = sz(a), nb = sz(b);
    a.resize(3 * k, 0); b.resize(3 * k, 0);
    auto part = [&](const vector<ll>& v, int i) { return vector<ll>(v.begin() + i * k, v.begin() + (i + 1) * k); };
    auto lin = [&](const vector<ll>& x, ll cx, const vector<ll>& y, ll cy, const vector<ll>& z, ll cz) {
        vector<ll> r(k); for (int i = 0; i < k; i++) r[i] = cx * x[i] + cy * y[i] + cz * z[i]; return r;
    };
    auto a0 = part(a, 0), a1 = part(a, 1), a2 = part(a, 2), b0 = part(b, 0), b1 = part(b, 1), b2 = part(b, 2);
    auto r0 = toom3(a0, b0);                                                     // x = 0
    auto r1 = toom3(lin(a0, 1, a1, 1, a2, 1), lin(b0, 1, b1, 1, b2, 1));         // x = 1
    auto rm = toom3(lin(a0, 1, a1, -1, a2, 1), lin(b0, 1, b1, -1, b2, 1));       // x = -1
    auto r2 = toom3(lin(a0, 1, a1, 2, a2, 4), lin(b0, 1, b1, 2, b2, 4));         // x = 2
    auto ri = toom3(a2, b2);                                                     // x = infinity
    int L = 2 * k - 1; vector<ll> c(6 * k - 1, 0);
    for (int i = 0; i < L; i++) {                        // interpolate c0..c4 of degree-4 C(x) at 0, 1, -1, 2, inf
        ll v0 = r0[i], v1 = r1[i], vm = rm[i], v2 = r2[i], vi = ri[i];
        ll c0 = v0, c4 = vi;
        ll e = (v1 + vm) / 2 - c0 - c4;                  // c2
        ll o = (v1 - vm) / 2;                            // c1 + c3
        ll t = (v2 - c0 - 4 * e - 16 * c4) / 2;          // c1 + 4 c3
        ll c3 = (t - o) / 3, c1 = o - c3;
        ll cs[5] = {c0, c1, e, c3, c4};
        for (int j = 0; j < 5; j++) c[i + j * k] += cs[j];
    }
    c.resize(na + nb - 1);
    return c;
}

Schönhage–Strassen (1971): an FFT over , where is a -th root of unity (), so every twiddle multiplication is a shift; split into blocks, transform with shifts only ( bit operations), multiply pointwise by recursion on -bit numbers, inverse transform, carry: (the recursion depth is the ). GMP uses it above roughly 30 000–100 000 bits (Toom variants below): the only sub-quadratic asymptotic improvement that is both production code and asymptotically strong. The contest instinct it teaches: choose an algebraic setting where the expensive operation becomes cheap (an NTT-friendly prime like 998244353).

Fürer (2007): in the ring , which has both cheap roots of unity (powers of : shifts) and expensive ordinary ones, arranged so that only levels use the expensive ones. below , but it was never competitive: a galactic algorithm. Harvey and van der Hoeven (2019) reached , the conjectured optimum, building on Fürer’s mix of cheap and expensive roots; it beats Schönhage–Strassen only beyond about bits.

YearMultiplicationTime
antiquityschoolbook
1960Karatsuba
1963Toom–Cook
1971Schönhage–Strassen
2007Fürer
2019Harvey–van der Hoeven
Operand size (limbs, GMP-style)Method
schoolbook
30–300Karatsuba
300–1000Toom-3
1000–3000Toom-4 / 6.5 / 8.5
Schönhage–Strassen / FFT
Galactic algorithmBeats the practical option only forPractical?
Fürer multiplicationastronomically large no
Harvey–van der Hoevenno
Coppersmith–Winograd-type matrix multiplicationenormous matricesno
Pettie–Ramachandran MSTunanalysableno
Chen et al. max flownot yetno
AKS primalityneverno

Transforms

Good–Thomas (prime-factor algorithm)

For with , map the input index by the CRT () and the output index by the Ruritanian map (); then with no cross terms, and the 1-D DFT becomes a 2-D DFT on an grid: transform the columns, then the rows. Strictly fewer multiplications than Cooley–Tukey (no twiddle stage) at the price of cache-unfriendly index permutations. FFTW’s planner combines Good–Thomas (coprime factors), Cooley–Tukey (non-coprime factors, with twiddles), Rader (prime factors), Bluestein (the general fallback, Convolution Variants) and hard-coded codelets, which is why it is fast at lengths like . The reusable idea is the CRT index map: a problem of size with coprime factors becomes independent multi-dimensional pieces (modular arithmetic split into prime powers, counting over , multiplicative functions). Winograd’s FFT pushes the multiplication count lower with minimal convolution algorithms for small sizes.

Rader (prime lengths)

For a prime with primitive root , re-index , (): , a cyclic convolution of length of with , computed by an FFT (or Bluestein if factors badly), plus : . Reusable move: re-index by a primitive root to turn multiplication into addition (discrete logarithms, Pohlig–Hellman, convolution over under multiplication: Primitive Roots).

RaderBluestein
requiresa primitive root mod nothing
convolution length (exact, cyclic) (padded, linear)
accuracybetterchirp factors grow, slightly worse
generalityprime lengthsany length
used by FFTW formoderate primeslarge or awkward primes
using cd = complex<double>;
vector<cd> naiveDFT(const vector<cd>& x) {
    int n = sz(x); vector<cd> X(n); const double PI = acos(-1.0);
    for (int k = 0; k < n; k++) for (int j = 0; j < n; j++) X[k] += x[j] * polar(1.0, -2 * PI * j * k / n);
    return X;
}
 
vector<cd> goodThomasDFT(const vector<cd>& x, int n1, int n2) {   // gcd(n1, n2) = 1; sub-DFTs done naively here
    int n = n1 * n2; vector<vector<cd>> g(n1, vector<cd>(n2));
    for (int j = 0; j < n; j++) g[j % n1][j % n2] = x[j];        // CRT input map
    for (int r = 0; r < n1; r++) g[r] = naiveDFT(g[r]);           // length-n2 DFTs
    for (int c = 0; c < n2; c++) { vector<cd> col(n1); for (int r = 0; r < n1; r++) col[r] = g[r][c]; col = naiveDFT(col); for (int r = 0; r < n1; r++) g[r][c] = col[r]; }
    vector<cd> X(n);
    for (int k1 = 0; k1 < n1; k1++) for (int k2 = 0; k2 < n2; k2++) X[(k1 * n2 + k2 * n1) % n] = g[k1][k2];   // Ruritanian output map
    return X;
}
 
vector<cd> raderDFT(const vector<cd>& x, int g) {                // prime length p, primitive root g; cyclic convolution done naively
    int p = sz(x); const double PI = acos(-1.0);
    vector<int> pw(p - 1); pw[0] = 1; for (int i = 1; i < p - 1; i++) pw[i] = (ll)pw[i - 1] * g % p;
    vector<cd> A(p - 1), B(p - 1), X(p);
    for (int a = 0; a < p - 1; a++) A[a] = x[pw[a]];
    for (int c = 0; c < p - 1; c++) B[c] = polar(1.0, -2 * PI * pw[c] / p);   // omega^(g^c)
    for (int j = 0; j < p; j++) X[0] += x[j];
    for (int b = 0; b < p - 1; b++) {                    // X[g^{-b}] = x0 + sum_a A[a] B[a - b]
        cd s = 0; for (int a = 0; a < p - 1; a++) s += A[a] * B[((a - b) % (p - 1) + (p - 1)) % (p - 1)];
        X[pw[(p - 1 - b) % (p - 1)]] = x[0] + s;
    }
    return X;
}

Flows and matching

Karzanov (1974) introduced the preflow (inflow outflow, vertices may hold excess): within each level graph, saturate the source edges, push excess forward by increasing level, return what cannot reach by decreasing level, until no excess remains (a blocking flow); per blocking flow, total, space with a matrix. Goldberg–Tarjan’s push-relabel (1986) kept the preflow and replaced the level graph by a height function with purely local operations (parallelizable and, with highest-label and gap heuristics, the fastest family in practice). Karzanov also proved the bound for unit-capacity Dinic and worked on multicommodity flow.

Malhotra–Kumar–Maheshwari (MPM, 1978): blocking flow in via vertex potentials in the level graph (, count one side). The minimum-potential vertex can push its whole forward to and pull it back from greedily (no vertex can block that amount); then it is saturated and deleted with all zero-potential vertices. Each vertex is deleted once, each push saturates an edge or exhausts , and potential updates cost per deletion with a matrix: per phase, total. Dinic’s is famously pessimistic; MPM’s value is the guarantee on dense graphs with large capacities.

GraphDinicMPM
sparse,
dense,
unit capacities, bipartite matching

Capacity scaling works in phases , augmenting only along residual capacities : Ford–Fulkerson becomes , Dinic . Ahuja–Orlin scales the excess instead: in phase process only nodes with excess , push from the large-excess node of smallest height, and never push more than into ; this caps non-saturating pushes at per phase: . Goldberg–Tarjan: push-relabel with dynamic trees pushes along whole paths in : . Orlin (2013) with King–Rao–Tarjan: for all densities (Orlin for by compaction of already-determined parts, excess scaling, dynamic trees and an abundance argument; KRT for denser graphs). Chen, Kyng, Liu, Peng, Probst Gutenberg, Sachdeva (2022): for max flow and min-cost flow, via interior-point methods with dynamic data structures; purely theoretical.

Max flowTime
Ford–Fulkerson, pseudo-polynomial
Edmonds–Karp
capacity-scaling Ford–Fulkerson
Dinic; unit capacities , unit networks
Karzanov, MPM, push-relabel FIFO
push-relabel highest label
Ahuja–Orlin
Goldberg–Tarjan (dynamic trees)
Orlin + King–Rao–Tarjan
Chen et al.

Min-cost flow by cost scaling (Goldberg–Tarjan): with potentials , reduced costs ; a flow is -optimal if every residual arc has (0-optimal = optimal by complementary slackness). Start with and the zero flow, refine to -optimality with a push-relabel loop where “admissible” means , and stop when : integer reduced costs summed around a cycle of at most arcs cannot reach , so the flow is optimal. phases, each with dynamic trees ( without).

Min-cost flowTimeNotes
cycle cancellingsimple, pseudo-polynomial
min-mean cycle cancellingstrongly polynomial (Goldberg–Tarjan, using Karp’s min-mean cycle)
SSP with potentials (Bellman–Ford once, then Dijkstra on reduced costs)write this: Min-Cost Flow
capacity-scaling SSP
Goldberg–Tarjan cost scalingfast general purpose
network simplexexponential worst caseoften fastest in practice

Gabow’s bit scaling: with , solve for the top bits ; since , a doubled optimal solution is within of optimal and augmentations repair it; after rounds it is exact. replaces a factor (pseudo-polynomial becomes weakly polynomial). It works whenever doubling preserves near-optimality and repair is cheap: max weight perfect matching in general graphs (versus or blossom), bipartite ; Goldberg’s scaling shortest paths with negative weights , and the near-linear of Bernstein, Nanongkai and Wulff-Nilsen (2022). The contest idea that survives: solve the high bits first, then refine (build an answer from the most significant bit down, checking feasibility).

Gabow–Tarjan: the assignment problem by cost scaling in : maintain -optimal dual prices (), halve each phase and repair with Hopcroft–Karp-style phases of vertex-disjoint augmenting paths (the ), stop at . Separately, linear-time union-find when the union tree is known in advance: micro-trees of vertices solved by table lookup (four Russians), ordinary DSU on the macro nodes; this makes Tarjan’s offline LCA truly linear and underlies linear MST verification.

AssignmentTime
all permutations
bitmask DP, best for with irregular costs
Hungarian, ~40 lines, well under a second for : Assignment Problem
MCMF with potentials
Gabow–Tarjan

Micali–Vazirani (1980): maximum matching in general graphs in , matching Hopcroft–Karp: phases of maximal sets of vertex-disjoint shortest augmenting paths (after phases paths are longer than , and at most disjoint ones remain), each phase . Blossoms break the layering; instead of contracting them it keeps evenlevel and oddlevel per vertex, detects blossoms as bridges (edges whose endpoint levels are inconsistent with a layering) and runs a “double depth-first search”. One of the hardest correct algorithms to implement: the original proof was incomplete, a full proof came in 1994 (Vazirani), and few correct implementations exist.

MatchingAlgorithmTime
bipartite, smallKuhn
bipartiteHopcroft–Karp or Dinic
bipartite, weightedHungarian
generalblossom (randomized order) or : General Matching
generalMicali–Vazirani
general, weightedBlossom V (Kolmogorov), practical
perfect matching exists?Tutte matrix, random determinant randomized, 15 lines

Trees and graphs

MST. Chazelle (2000): deterministic, using soft heaps (insert, meld, extract-min in amortized while corrupting at most an fraction of keys, i.e. increasing their stored values), Borůvka contractions, a hierarchy of contractible subgraphs guided by a -ary tree chosen so corruption stays controlled, and recursive clean-up of the corrupted “bad” edges. Soft heaps also give a clean selection algorithm. Pettie–Ramachandran (2002): provably optimal, running in , the unknown decision-tree complexity of MST (): partition into subgraphs of vertices (Borůvka plus soft heaps), look up a precomputed optimal decision tree for each (all found by brute force in because is tiny), contract and recurse. The open problem is now to analyse it, and to find a deterministic algorithm; MST verification is deterministic (King). Its “table lookup on tiny subproblems” is the four-Russians idea of Farach-Colton–Bender RMQ, Boolean matrix multiplication and block-precomputed bitset DP.

YearMSTTime
1926Borůvka, parallelizable
1956/57Kruskal, Prim; Prim with Fibonacci heap
1975Yao
1984Fredman–Tarjan
1986Gabow, Galil, Spencer, Tarjan
1995Karger–Klein–Tarjan expected
2000Chazelle
2002Pettie–Ramachandranoptimal, value unknown

Fredman–Tarjan: Prim with a Fibonacci heap, but each growth phase stops when the heap exceeds a threshold , the grown trees are contracted, and the next pass uses threshold : passes. Frederickson: topology trees cluster a bounded-degree tree into -vertex clusters recursively ( levels): path and subtree aggregates, link and cut, plus non-local information such as “minimum edge crossing a cut”. Dynamic MST: insertion of adds the edge or swaps it with the heaviest edge on the tree path (path max, ); deleting a tree edge needs the minimum replacement across the cut, which the ambivalent data structure finds in ; sparsification (Eppstein, Galil, Italiano, Nissenzweig) improves this to .

Dynamic connectivity / MSTBest knownTechnique
incremental connectivity amortizedDSU
decremental connectivity on trees amortizedEuler tour tricks
fully dynamic connectivity amortizedHolm–de Lichtenberg–Thorup
fully dynamic MST amortizedHDT extension
Frederickson worst casetopology trees
offline dynamic connectivitysegment tree over time + rollback DSU: ~60 lines, write this: Dynamic Connectivity

LCA. Harel–Tarjan (1984) gave the first / LCA: heavy-path decomposition, embedding into a complete binary tree where LCA of inorder labels comes from the highest differing bit (one XOR and one clz), and per-node words recording which path levels contain an ancestor. Then Schieber–Vishkin (1988, simplified and parallelizable), Berkman–Vishkin (1993, reduction to RMQ), Farach-Colton–Bender (2000, RMQ + four Russians: the standard exposition; 2004 further simplified with a sparse table on blocks). Tarjan’s offline LCA (presented in Mehlhorn’s textbook) answers known queries in with a DSU; if the tree grows by leaves, use an Euler tour in a balanced BST; under link and cut, link-cut trees. “Sort the queries so a cheap incremental structure suffices” generalizes far beyond LCA (Mo’s algorithm, offline BIT sweeps, offline dynamic connectivity). Related: level ancestor in (ladder decomposition), tree distance , virtual trees (LCA of consecutive nodes in DFS order).

LCA needMethodCost
queries known in advanceoffline union-find
online, also -th ancestor and path aggregatesbinary lifting (the default) /
queriesEuler tour + sparse table /
preprocessingFarach-Colton–Bender /
path updates and queriesHLD + segment tree
tree grows by leaves / link and cutEuler tour in a BST / link-cut tree
// Tarjan's offline LCA: queries[u] = {(v, id)}; iterative DFS from root, DSU tagged with the current ancestor.
vector<int> offlineLCA(int n, const vector<vector<int>>& adj, int root, const vector<pair<int, int>>& qs) {
    vector<int> dsu(n), anc(n), ans(sz(qs)), parent(n, -1), it(n, 0); vector<char> done(n, 0), seen(n, 0);
    iota(all(dsu), 0);
    auto find = [&](int x) { while (dsu[x] != x) x = dsu[x] = dsu[dsu[x]]; return x; };
    vector<vector<pair<int, int>>> at(n);
    for (int i = 0; i < sz(qs); i++) at[qs[i].first].push_back({qs[i].second, i}), at[qs[i].second].push_back({qs[i].first, i});
    vector<int> st{root}; seen[root] = 1; anc[root] = root;
    while (!st.empty()) {
        int u = st.back();
        if (it[u] < sz(adj[u])) {
            int v = adj[u][it[u]++];
            if (!seen[v]) { seen[v] = 1; parent[v] = u; anc[v] = v; st.push_back(v); }
        } else {                                         // u finished
            done[u] = 1;
            for (auto [v, id] : at[u]) if (done[v]) ans[id] = anc[find(v)];
            st.pop_back();
            if (parent[u] >= 0) { int p = parent[u]; dsu[find(u)] = find(p); anc[find(p)] = p; }
        }
    }
    return ans;
}

Reif: planar minimum cut via planar duality: cuts in correspond to cycles in the dual (one vertex per face, an edge per primal edge), so the minimum cut is the minimum-weight dual cycle, found by divide and conquer on a planar separator ( vertices, balanced halves; a minimum cycle avoids it or crosses it, handled by shortest paths from separator vertices): , later (Chalermsook–Fakcharoenphol–Nanongkai; Italiano et al.). The - version is the contest one: an - min cut in a planar graph is a shortest path in the dual between the two outer faces, so “block every path from left to right at minimum cost” on a grid is Dijkstra on the dual grid; image segmentation by graph cuts started here.

GraphMin cut
generalStoer–Wagner ; Karger randomized : Global Min Cut
planar, undirected
planar - max flow (Borradaile–Klein)
Primal Dual
cutcycle
- min cutshortest path between two faces
spanning treethe complement edges form a spanning tree of the dual
facevertex

Strings

Boyer–Moore family. Plain Boyer–Moore can re-compare text characters ( for aaaa in aaa…a). Apostolico–Giancarlo stores , the match length that ended at text position in an earlier alignment, and the pattern’s self-overlap table (longest common suffix of pattern[0..j] and the pattern): if a mismatch is certain, if a match of length is certain and skipped, if equal resume comparing: comparisons, preprocessing, space (the array is the price, unwelcome for streams), sublinear on typical text. Horspool and Sunday (bad-character rule only) are faster in practice.

VariantWorst caseExtra spaceNotes
Boyer–Moorethe original
Boyer–Moore–Horspoolbad character only; fastest in practice
Sunday (quick search)looks one past the window
Boyer–Moore–Galillinear for all-occurrence reporting
Apostolico–Giancarlo comparisonsgeneral guarantee
KMPnever skips, always linear

Constant-space matching. Galil–Seiferas (1983): linear time, extra space (no failure function), comparisons, via a decomposition with highly periodic (a mismatch in is resolved by the period, a single integer) and short, computed with the critical factorisation theorem: every string has a position where the local period equals the global period. Crochemore–Perrin Two-Way (1991): the practical descendant, used by glibc’s strstr and memmem: find a critical position from the maximal suffixes under both alphabet orders ( time, space), scan the right part left to right (mismatch at offset : shift by ), then the left part right to left (mismatch: shift by the period), remembering one integer of verified prefix so characters are not re-compared: , at most comparisons. Karp–Rabin is space but only expected linear. The same periodicity theory underlies runs, Lyndon factorisation and linear-time maximal repetitions; constant-space matching matters for embedded and streaming settings.

// Crochemore-Perrin Two-Way: all occurrences of x in y with O(1) extra space.
vector<int> twoWaySearch(const string& x, const string& y) {
    int m = sz(x), n = sz(y); vector<int> res;
    if (m == 0) { for (int j = 0; j <= n; j++) res.push_back(j); return res; }
    auto maxSuf = [&](bool rev, int& per) {             // maximal suffix under < (rev: under >), and its period
        int ms = -1, j = 0, k = 1; per = 1;
        while (j + k < m) {
            char a = x[j + k], b = x[ms + k];
            if (rev ? a > b : a < b) { j += k; k = 1; per = j - ms; }
            else if (a == b) { if (k != per) k++; else { j += per; k = 1; } }
            else { ms = j; j = ms + 1; k = per = 1; }
        }
        return ms;
    };
    int p, q, i = maxSuf(false, p), jj = maxSuf(true, q);
    int ell = i > jj ? i : jj, per = i > jj ? p : q;     // critical position and its local period
    if (ell + 1 + per <= m && x.compare(0, ell + 1, x, per, ell + 1) == 0) {   // periodic pattern
        int j = 0, memory = -1;
        while (j <= n - m) {
            int t = max(ell, memory) + 1;
            while (t < m && x[t] == y[t + j]) t++;
            if (t >= m) {
                t = ell;
                while (t > memory && x[t] == y[t + j]) t--;
                if (t <= memory) res.push_back(j);
                j += per; memory = m - per - 1;
            } else { j += t - ell; memory = -1; }
        }
    } else {
        per = max(ell + 1, m - ell - 1) + 1;
        int j = 0;
        while (j <= n - m) {
            int t = ell + 1;
            while (t < m && x[t] == y[t + j]) t++;
            if (t >= m) {
                t = ell;
                while (t >= 0 && x[t] == y[t + j]) t--;
                if (t < 0) res.push_back(j);
                j += per;
            } else j += t - ell;
        }
    }
    return res;
}

Crochemore’s repetitions algorithm (1981): all maximal repetitions (runs: maximal substrings of period ) in by refining the partition of positions by length- substrings (the idea of prefix doubling for suffix arrays and of Hopcroft’s DFA minimisation); classes with positions at distance reveal repetitions. Main–Lorentz finds all squares in by divide and conquer, more easily. The runs theorem (Bannai et al., 2015): fewer than runs, all found in via Lyndon roots (Runs and Periodicity). Repetition detection is the core of LZ-family compression.

Multi-pattern matching. Commentz–Walter (1979): a trie of the reversed patterns walked right to left within a window of the shortest pattern length, with two generalized good-suffix shift functions and a bad-character table: sublinear on average, worst case , space ; used by early fgrep; superseded. Wu–Manber: with the shortest pattern length and blocks of – characters, a SHIFT table says how far the window may jump for each block ( if the block occurs in no pattern prefix of length , else its rightmost end position), a HASH table lists patterns whose length- prefix ends with the block, and a PREFIX filter rejects candidates cheaply. Average about accesses (no character inside a jump is read), worst case , tables of size . Behind agrep (by Wu and Manber), grep -F with many patterns, Snort and antivirus scanners. The shortest pattern rules everything: one 2-character pattern destroys the skipping, so real tools route short patterns to a separate Aho–Corasick pass.

ApproachRepresentativeGuaranteeWhen
automaton, left to rightAho–Corasickcontests, always: Aho–Corasick
trie + skipping, right to leftCommentz–Walternonehistorical
block hashing + skippingWu–Manbernonelong patterns, huge texts
bit-parallelBitap variantsfew short patterns
hashingmulti-pattern Rabin–Karp expectedequal-length patterns
// Wu-Manber with block size B = 2 over bytes: all (position, pattern id) occurrences.
vector<pair<int, int>> wuManber(const vector<string>& pats, const string& t) {
    const int B = 2; int m = INT_MAX; for (auto& p : pats) m = min(m, sz(p));
    vector<pair<int, int>> res;
    if (m < B) {                                         // too short to skip: plain search
        for (int id = 0; id < sz(pats); id++) for (size_t pos = t.find(pats[id]); pos != string::npos; pos = t.find(pats[id], pos + 1)) res.push_back({(int)pos, id});
        sort(all(res)); return res;
    }
    auto key = [&](const string& s, int i) { return (unsigned char)s[i - 1] * 256 + (unsigned char)s[i]; };   // block ending at i
    vector<int> shift(1 << 16, m - B + 1); vector<vector<int>> hashTab(1 << 16);
    for (int id = 0; id < sz(pats); id++) {
        for (int i = B - 1; i < m; i++) shift[key(pats[id], i)] = min(shift[key(pats[id], i)], m - 1 - i);
        hashTab[key(pats[id], m - 1)].push_back(id);
    }
    for (int i = m - 1; i < sz(t);) {                    // i = text index of the window's last character
        int k = key(t, i);
        if (shift[k] > 0) { i += shift[k]; continue; }
        int start = i - m + 1;
        for (int id : hashTab[k]) if (t.compare(start, sz(pats[id]), pats[id]) == 0) res.push_back({start, id});
        i++;
    }
    sort(all(res));
    return res;
}

Suffix trees. Weiner (1973), “the algorithm of the year” (Knuth): the first linear construction, prepending characters right to left, with per-node tables (does occur?) and (the node of ), i.e. reverse suffix links; space was the problem. Its view is that of the suffix automaton of the reversed string: the suffix-link tree of the suffix automaton of is the suffix tree of . McCreight (1976): inserts suffixes from longest to shortest with suffix links (follow the link from the parent of the last leaf instead of restarting) and skip/count (jump whole edges by length, comparing one character per edge); the head/tail bookkeeping () telescopes to , like Kasai’s LCP; with balanced children; matching statistics fall out. Ukkonen (1995) is online (left to right). Farach (1997): linear for integer alphabets by odd/even divide and conquer: pair characters, radix sort and rename, recurse on the half-length string to get the odd-suffix tree, derive the even tree in (one radix sort), then a hard “over-merge” corrected by an amortized argument: . It inspired DC3/skew (thirds instead of halves, an easy merge) and SA-IS (induced sorting replaces the merge). (The same Farach, as Farach-Colton with Bender, gave RMQ/LCA.) In contests write none of them: a suffix automaton (30 lines) or a suffix array with LCP covers almost every suffix-tree application (Suffix Tree).

YearLinear suffix structures
1973Weiner: first linear suffix tree, space
1976McCreight: linear time, space, offline
1983Blumer et al.: the DAWG / suffix automaton
1990Manber–Myers: suffix arrays in , far less memory
1995Ukkonen: online linear construction
1997Farach: linear for integer alphabets
2003Kärkkäinen–Sanders DC3: simple linear suffix array
2009SA-IS (Nong, Zhang, Chan; after Ko–Aluru): fast linear suffix array, the current standard