Select an algorithm from the sidebar
Disjoint Set Union
// description
DSU (also called Union-Find) maintains a collection of disjoint sets and supports two operations efficiently: merge two sets, and find the representative (root) of a set.
This implementation uses two classic optimisations:
- Path compression — on every
rootofcall, all nodes on the path are linked directly to the root. - Union by rank — always attach the shorter tree under the taller one to keep trees flat.
Together these give an amortised near-constant O(α(n)) per operation, where α is the inverse Ackermann function (≤ 4 for any practical input size).
#include<bits/stdc++.h> using namespace std; struct DSU { int n; vector<int> parent, rank; DSU(int n) : n(n), parent(n), rank(n) { for(int i = 0; i < n; i++) parent[i] = i; } int rootof(int x) { if(parent[x] != x) parent[x] = rootof(parent[x]); // path compression return parent[x]; } bool merge(int x, int y) { int a = rootof(x), b = rootof(y); if(a == b) return false; // already same set if(rank[a] > rank[b]) parent[b] = a; else if(rank[a] < rank[b]) parent[a] = b; else { rank[a]++; parent[b] = a; } return true; } };
Segment Tree (Lazy Propagation)
// description
A Segment Tree partitions an array into segments stored in a binary tree, enabling range queries and range updates in O(log n).
This version uses lazy propagation: instead of immediately pushing updates to all children, each node stores a pending lazy value that is pushed down only when a node is visited. This turns range updates from O(n) to O(log n).
The implementation here is built on an Euler tour of a tree, using two separate segment trees — one per parity level — to support subtree updates and point queries on trees.
struct SegTree { int lazy[MX * 4]; void update(int node, int b, int e, int i, int j, int val) { if(j < b || i > e) return; if(i <= b && e <= j) { lazy[node] += val; } int lson = node*2, rson = lson+1, mid = (b+e)/2; if(lazy[node] && b != e) { // push down lazy[lson] += lazy[node]; lazy[rson] += lazy[node]; lazy[node] = 0; } if(i <= b && e <= j) return; update(lson, b, mid, i, j, val); update(rson, mid+1, e, i, j, val); } int query(int node, int b, int e, int i) { if(i < b || i > e) return 0; int lson = node*2, rson = lson+1, mid = (b+e)/2; if(lazy[node] && b != e) { // push down lazy[lson] += lazy[node]; lazy[rson] += lazy[node]; lazy[node] = 0; } if(b == e) return lazy[node]; return query(lson, b, mid, i) + query(rson, mid+1, e, i); } };
Heavy-Light Decomposition
// description
HLD decomposes a tree into a set of vertex-disjoint chains by always extending a chain through the child with the largest subtree (the "heavy" child). Any root-to-leaf path crosses at most O(log n) chains.
Each chain is a contiguous segment in a flattened DFS order, so any path query becomes a series of range queries on a Segment Tree — one per chain boundary crossed.
This implementation:
- Uses
get_szto compute subtree sizes and parent pointers. - Builds chains via
hld()assigning new DFS names. - Embeds a sparse-table LCA for path queries between arbitrary nodes.
- Uses a max Segment Tree to answer "max edge weight on path u→v".
void get_sz(int u, int p) { sz[u] = 1; for(int v : edge[u]) if(v != p) { par[v] = u; lev[v] = lev[u]+1; get_sz(v, u); sz[u] += sz[v]; } } void hld(int u, int p, bool isHead) { newName[u] = ++Name; if(isHead) chain++, chainHead[chain] = u; chainNo[u] = chain; int bigChild = 0, mxi = -1; for(int v : edge[u]) if(v != p && sz[v] > mxi) mxi = sz[v], bigChild = v; if(bigChild) hld(bigChild, u, 0); // extend chain for(int v : edge[u]) if(v != p && v != bigChild) hld(v, u, 1); // start new chain } int query_up(int u, int v) { int ret = 0; while(1) { if(chainNo[u] == chainNo[v]) { ret = max(ret, query(1,1,n,newName[v]+1,newName[u])); break; } ret = max(ret, query(1,1,n, newName[chainHead[chainNo[u]]], newName[u])); u = par[chainHead[chainNo[u]]]; } return ret; }
Lowest Common Ancestor
// description
LCA finds the deepest node that is an ancestor of both u and v. This implementation uses binary lifting: for each node, precompute ancestors at distances 1, 2, 4, 8… (powers of two) in a sparse table.
To answer a query:
- Lift the deeper node until both are at the same depth.
- Simultaneously lift both until their ancestors diverge.
- The parent of either is the LCA.
The sparse table is built in O(n log n) with a standard DP: sp[v][j] = sp[sp[v][j-1]][j-1].
int sp[MX][23], L[MX]; // build sparse table after DFS for(int j = 1; (1<<j) <= n; j++) for(int i = 1; i <= n; i++) if(sp[i][j-1] != -1) sp[i][j] = sp[sp[i][j-1]][j-1]; int findlca(int u, int v) { if(L[u] < L[v]) swap(u, v); int diff = L[u] - L[v]; for(int i = 22; i >= 0; i--) if(diff & (1 << i)) u = sp[u][i]; // lift u to same depth if(u == v) return u; for(int i = 22; i >= 0; i--) if(sp[u][i] != sp[v][i]) u = sp[u][i], v = sp[v][i]; return sp[u][0]; // parent is LCA }
KMP — Knuth-Morris-Pratt
// description
KMP finds all occurrences of a pattern P in a text T in linear time by avoiding redundant comparisons using the failure function (LPS array).
The failure function lps[i] stores the length of the longest proper prefix of P[0..i] that is also a suffix. When a mismatch occurs at position i, instead of restarting from scratch, we jump to lps[i-1] — the longest border — and continue matching.
This implementation builds the LPS array directly as a 1-indexed char array.
char a[MX]; int lps[MX]; // build failure function (1-indexed) int cur = 1; lps[0] = -1; lps[1] = 0; for(int i = 2; i <= n; i++) { while(cur != 0 && a[cur] != a[i]) cur = lps[cur - 1] + 1; // fall back lps[i] = cur++; } // lps[i] = length of longest proper // prefix of a[1..i] that is also suffix
Z Algorithm
// description
The Z-function of a string S is an array where z[i] is the length of the longest substring starting at S[i] that is also a prefix of S.
The algorithm maintains a window [l, r] — the rightmost Z-box seen so far. For each new position i:
- If
i ≤ r, initialisez[i]from the already-known valuez[i-l](capped at r−i+1). - Then extend naively while characters match.
Each character is visited at most twice, giving O(n) total.
// zero-indexed vector<int> z_function(string s) { int n = (int)s.length(); vector<int> z(n); for(int i = 1, l = 0, r = 0; i < n; i++) { if(i <= r) z[i] = min(r - i + 1, z[i - l]); while(i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1; } return z; }
Mo's Algorithm
// description
Mo's algorithm answers offline range queries efficiently by sorting them in a specific order that minimises the total movement of two pointers [l, r].
Queries are sorted by block of left endpoint (block size √n), then by right endpoint within each block. This ensures:
- Total movement of
r: O(n√n) - Total movement of
l: O(q√n)
This implementation solves XOR range queries — maintaining a count array and a running answer that updates in O(1) per pointer move.
int SQ; struct Query { int l, r, idx; bool operator<(const Query &a) const { if(l/SQ == a.l/SQ) return r < a.r; return l/SQ < a.l/SQ; } } Q[MX]; int cnt[7000005]; long long counti = 0; inline void add(int i) { counti += cnt[ara[i] ^ k]; cnt[ara[i]]++; } inline void rem(int i) { cnt[ara[i]]--; counti -= cnt[ara[i] ^ k]; } // process queries SQ = sqrt(n); sort(Q, Q+m); int L = 0, R = 0; for(int i = 0; i < m; i++) { while(L < Q[i].l) rem(L++); while(L > Q[i].l) add(--L); while(R < Q[i].r) add(++R); while(R > Q[i].r) rem(R--); ans[Q[i].idx] = counti; }
Fast Fourier Transform
// description
FFT evaluates a polynomial at n roots of unity in O(n log n) using the Cooley-Tukey divide-and-conquer algorithm. Polynomial multiplication then becomes: FFT both, pointwise multiply, inverse FFT.
The key idea: a degree-n polynomial can be split into its even- and odd-indexed coefficients, each of degree n/2. Evaluating both halves recursively and combining takes O(n) per level over O(log n) levels.
The invert flag triggers the inverse FFT (conjugate twiddle factors + divide by n) to convert back from value representation to coefficient representation.
typedef complex<long double> base; void fft(vector<base> &a, bool invert) { int n = a.size(); // bit-reversal permutation for(int i=1,j=0; i<n; i++) { int bit = n>>1; for(; j>=bit; bit>>=1) j-=bit; j += bit; if(i < j) swap(a[i], a[j]); } for(int len=2; len<=n; len<<=1) { long double ang = 2*pi/len*(invert?-1:1); base wlen(cos(ang), sin(ang)); for(int i=0; i<n; i+=len) { base w(1); for(int j=0; j<len/2; j++) { base u = a[i+j]; base v = a[i+j+len/2] * w; a[i+j] = u + v; a[i+j+len/2] = u - v; w *= wlen; } } } if(invert) for(int i=0; i<n; i++) a[i] /= n; }
Convex Hull
// description
Finds the smallest convex polygon containing all given points. This is Andrew's monotone chain algorithm.
After sorting points lexicographically (by x, then y), the algorithm builds the lower and upper hulls separately by scanning left-to-right and right-to-left, maintaining the invariant that the hull always makes left turns.
The cross function computes the 2D cross product of vectors OA and OB:
- > 0: left turn (keep)
- = 0: collinear (remove for strict hull)
- < 0: right turn (pop and retry)
struct PT { ll x, y; bool operator<(PT p) const { return x < p.x || (x==p.x && y<p.y); } }; ll cross(PT O, PT A, PT B) { return (A.x-O.x)*(B.y-O.y) - (A.y-O.y)*(B.x-O.x); } vector<PT> convex_hull(vector<PT> P) { int n = P.size(), k = 0; vector<PT> H(2*n); sort(P.begin(), P.end()); // lower hull for(int i = 0; i < n; i++) { while(k >= 2 && cross(H[k-2],H[k-1],P[i]) <= 0) k--; H[k++] = P[i]; } // upper hull for(int i = n-2, t = k+1; i >= 0; i--) { while(k >= t && cross(H[k-2],H[k-1],P[i]) <= 0) k--; H[k++] = P[i]; } H.resize(k-1); return H; }