{"pages": [{"u": "/foundations/intro/", "t": "What a Graph Is, and How to Notice One", "s": "Vertices, edges, and the modelling habit that turns a scary statement into a graph problem.", "c": "Graphs and Models", "k": "modelling definitions easy", "b": "A graph is a pair G = (V, E) : a set of vertices and a set of edges , where each edge is a 2-element subset of V . That is the entire definition. Everything else in this chapter is bookkeeping. Definition The order is n = |V| , the size is m = |E| . The degree deg(v) of a vertex is the number of edges touching it; a vertex with deg(v) = 0 is isolated . # The handshaking lemma Theorem Handshaking ∑_v ∈ V deg(v) = 2m . In particular, the number of odd-degree vertices is even. Proof Sum degrees by counting edges : each edge contributes exactly 1 to each of its two endpoints, hence 2 to the total. # Figure G A Apple B Plum A--B C Lime A--C D Orange A--D E Kiwi A--E B--C C--D F Peach C--F D--E D--F Figure 1 Two vertices, one edge: the smallest interesting graph.", "w": 139, "h": [["The handshaking lemma", "the-handshaking-lemma"], ["Figure", "figure"]]}, {"u": "/foundations/types/", "t": "A Zoo of Graphs", "s": "The named families you should recognise on sight — and the edge counts each one gives you for free.", "c": "Graphs and Models", "k": "definitions counting easy", "b": "Competitions rarely ask you to invent a graph; they hand you a shape and expect you to know its invariants. Learn these ten, and half of all \"how many edges / what is the degree of…\" questions become reflexes. # The named families family notation |E| notes path P_n n-1 connected, exactly two vertices of degree 1 cycle C_n n 2-regular; exists only for n ≥ 3 (simple graphs) complete K_n C(n, 2) every pair adjacent; Δ = n-1 star S_n n-1 one vertex of degree n-1 ; a tree with diameter 2 wheel W_n 2(n-1) C_n-1 plus a universal hub complete bipartite K_a,b ab max edges with no triangle hypercube Q_n n 2^n-1 vertices = bitmasks of length n ; n -regular, bipartite grid / lattice G_a,b 2ab - a - b planar, max degree 4 complete multipartite K_n 1,…,n_k (1)/(2)(n^2 - ∑ n_i^2) complement of a disjoint union of cliques empty / null K_n̄ 0 n isolated vertices Key idea Count edges by double counting Every number in that column comes from the same move: count the same thing twice. Q_n has 2^n vertices each of degree n , so m = n 2^n-1 . If you can compute degrees in two ways, you never have to \"see\" the pattern. G C C A A C--A C--A D D C--D B B A--B A--B A--D B--D Figure 1 The graph that started the field: seven bridges of Königsberg, drawn as four land masses and seven edges. Multi-edges are exactly what makes Euler's degree condition the right one. # Vocabulary that decides algorithm choice Definition simple : no self-loops, at most one edge per pair. Most of this book assumes simple unless stated. multigraph : parallel edges allowed — the model for \"two flights between the same cities\". directed : edges are ordered pairs (u,v) ; see Orientations, In/Out-Degree, Strong vs Weak . weighted : each edge carries a number; only the weights matter for shortest paths, the topology is the same. self-loop (v,v) : contributes 2 to deg(v) in the undirected case, 1 to both in- and out-degree when directed. A k -regular graph has every degree equal to k ; the handshaking lemma then forces kn even, which is the standard proof that a 3-regular graph on 9 vertices cannot exist. Definition Density is (2m)/(n(n-1)) ∈ [0,1] . A graph with m > C(n-1, 2) must be connected: the maximum number of edges in a disconnected graph is a K_n-1 plus an isolated vertex. Watch out Two traps \"Graph with n vertices and n edges is a cycle\" — false; it only has exactly one cycle, somewhere, possibly with trees hanging off it (a unicyclic graph). \"Bipartite means the parts have equal size\" — no; it means V = A ⊔ B with all edges crossing. K_1,7 is bipartite. # Recognition is an algorithm Check yourself Given an adjacency matrix, O(n^2) checks settle most of the above: regular (all row sums equal), complete (all off-diagonal ones), tree (connected and m=n-1 ), bipartite (no odd cycle — BFS colouring, see Bipartite Graphs and 2-Colouring ). cpp recognise.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int n ; cin >> n ; vector < string > a ( n ); for ( auto & s : a ) cin >> s ; long long m = 0 ; bool simple = true ; vector < int > deg ( n ); for ( int i = 0 ; i < n ; i ++) for ( int j = 0 ; j < n ; j ++) if ( a [ i ][ j ] == '1' ) { if ( i == j ) simple = false ; // self-loop m += ( j > i ); deg [ i ]++; } int d0 = deg [ 0 ]; bool regular = all_of ( deg . begin (), deg . end (), [&]( int d ) { return d == d0 ; }); bool complete = m == 1LL * n * ( n - 1 ) / 2 ; bool tree = m == n - 1 && connected ( a ); // connectivity is the other half Problems for this page all warm-up core CSES 1666 Building Roads warm-up components CF 977E Cyclic Components core cycles SPOJ PT07Y Is it a tree? warm-up trees", "w": 667, "h": [["The named families", "the-named-families"], ["Vocabulary that decides algorithm choice", "vocabulary-that-decides-algorithm-choice"], ["Recognition is an algorithm", "recognition-is-an-algorithm"]]}, {"u": "/foundations/representation/", "t": "Four Ways to Store a Graph", "s": "Adjacency list, matrix, edge list, and the compressed variants — with the memory and time each one really costs.", "c": "Graphs and Models", "k": "implementation memory easy", "b": "The graph \"shape\" is a mathematical object; the representation is a trade-off you choose . Pick wrong and an O(n+m) algorithm becomes an O(n^2) one — or blows the memory limit. Definition Adjacency list : vector<int> g[n] — neighbours of each vertex. Adjacency matrix : bool a[n][n] — is (u,v) an edge? Edge list : vector<tuple<int,int,int>> — everything you need for Kruskal/flow. Incidence / compressed : rows as bitset , or edges in a hash set — used when n is small but m huge. representation build edge query iterate deg(u) memory best for adjacency list O(n+m) O(deg u) O(deg u) O(n+m) traversal, DFS/BFS, Dijkstra adjacency matrix O(n^2) O(1) O(n) O(n^2) n ≤ 2000 , Floyd–Warshall, bipartite/complement tricks bitset<MAXN> rows O(n + m) O(1) O(n/64) O(n^2/8) dense graphs, common-neighbour counts edge list O(m) — — O(m) MST, colouring, anything sorted by weight hash of edges O(m) O(1) avg O(deg u) O(m) implicit graphs (grid states, \"complement BFS\") Note The 64× trick With bitset , \"count common neighbours of u and v \" is (adj[u] & adj[v]).count() — O(n/64) , i.e. one operation per machine word. Triangle counting drops from O(n^3) to O(n^3/64) , and n=5000 becomes comfortable. # Contest-grade boilerplate cpp graph.hpp Copy 1 2 3 4 5 6 7 8 9 10 using ll = long long ; const int MAXN = 200000 + 5 ; // never guess: derive from the memory limit vector < int > adj [ MAXN ]; // undirected: push both ways vector < tuple < int , int , int >> edges ; // (w, u, v) — sort-first algorithms void add_edge ( int u , int v , int w = 1 ) { adj [ u ]. push_back ( v ); adj [ v ]. push_back ( u ); // delete this line for digraphs edges . emplace_back ( w , u , v ); } Watch out Four bugs that survive every reviewer 2m vs m . An undirected graph stored as lists has 2m entries; a memory limit stated in edges must be doubled before you size arrays. Self-loops in lists. g[u].push_back(u) makes a naive DFS \"find a cycle\" of length 1. Filter if (v == u) continue; — or keep the loop and mean it (Euler tours do). Weighted matrix init. memset(a, 0x3f, sizeof a) gives ~ 10^9 ; memset(…, -1) on int gives -1 bytewise, which is fine, but on double it is garbage. Use numeric_limits<double>::infinity() . 1- vs 0-indexed. Input vertices are 1-indexed; either subtract once at read time or size arrays n+1 . Never mix. # Reading input fast cpp io.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { ios :: sync_with_stdio ( false ); cin . tie ( nullptr ); int n , m ; cin >> n >> m ; vector < vector < int >> g ( n ); vector < int > deg ( n ); for ( int i = 0 ; i < m ; i ++) { int u , v ; cin >> u >> v ; -- u ; -- v ; g [ u ]. push_back ( v ); g [ v ]. push_back ( u ); deg [ u ]++; deg [ v ]++; } } For m ≥ 10^6 prefer scanf / fread -based readers or store the input as one vector<array<int,2>> and build lists in a second pass — pointer chasing through 2 × 10^6 small vectors dominates the runtime. Problems for this page all warm-up CSES 1192 Counting Rooms warm-up grid, components CSES 1666 Building Roads warm-up components + DSU SPOJ PT07Y Is it a tree? warm-up m = n-1 + connectivity Tip Rule of thumb n ≤ 500 : matrix, and think about O(n^3) . n ≤ 5000 : matrix as bitset . n ≥ 10^5 and m = O(n) : adjacency list only, and check whether the graph is a tree/functional/planar — that structure is worth more than any micro-optimisation. G A A D D A--D F F D--F B B B--A B--D C C B--C E E B--E C--D C--E C--F Figure 1 The running example for this chapter: a connected, undirected, unweighted graph. Its adjacency list has 2·|E| entries; its matrix is symmetric with an all-zero diagonal.", "w": 652, "h": [["Contest-grade boilerplate", "contest-grade-boilerplate"], ["Reading input fast", "reading-input-fast"]]}, {"u": "/foundations/walks/", "t": "Walks, Trails, Paths, Cycles", "s": "Four words that decide whether your algorithm is linear or exponential — plus the cycle-removal lemma.", "c": "Graphs and Models", "k": "definitions proofs easy", "b": "Informally we say \"path\" for everything. Formally the four notions differ, and the difference is exactly what makes Euler and Hamilton problems behave differently. Definition A walk of length k is a sequence v_0, e_1, v_1, …, e_k, v_k of alternating vertices and edges. Then: trail = walk with all edges distinct, path = walk with all vertices distinct (⇒ all edges distinct, except in a multigraph where a path is by definition edge- and vertex-simple), cycle = closed trail ( v_0 = v_k ) with all other vertices distinct. object repeat vertices? repeat edges? \"find the longest …\" walk yes yes matrix powers, O(n^3 log k) trail yes no Euler tour, O(n+m) path no no Hamilton path, NP-complete cycle only the first = last no girth: O(nm) ; odd cycle: BFS Lemma Cycle removal If a walk from s to t exists, then a path from s to t exists, no longer than the walk. Proof Take a shortest walk W . If some vertex x occurs twice in W , W splits as s ⇝ x , a closed piece x ⇝ x , and x ⇝ t . Deleting the closed piece yields a strictly shorter walk from s to t , contradicting minimality. So no vertex repeats: W is a path. ∎ That 4-line proof is why BFS distance is well defined : shortest walk = shortest path, and we may search only over simple paths without ever saying so. # The same lemma, weaponised Key idea Minimality arguments The proof above is the extremal principle ( The Extremal Principle ) in its purest form: choose a counterexample with the fewest edges and show the extra structure contradicts minimality. Most \"prove there exists a path with property P\" olympiad problems are this argument plus one case analysis. Corollary A connected graph contains a spanning tree: repeatedly delete an edge of a cycle; connectivity is preserved, and you stop when no cycle remains. A graph with n vertices and minimum degree δ ≥ 2 contains a cycle of length at least δ + 1 — take a longest path v_0 … v_ℓ ; all neighbours of v_0 lie on it, and the farthest one closes a cycle that long. # Directed version Everything carries over with \"walk\" meaning a walk along the arrow direction, and one extra warning: in a digraph a path between u and v need not imply one between v and u . That asymmetry is what Orientations, In/Out-Degree, Strong vs Weak is about. cpp longest-path-dag.cpp Copy 1 2 3 4 5 6 7 // Longest path in a DAG — the problem that is NP-hard on general graphs // but trivial once there are no cycles to remove. vector < int > order = topo_sort (); // see @directed/dag-toposort vector < int > dp ( n , - INF ); dp [ s ] = 0 ; for ( int u : order ) if ( dp [ u ] > - INF ) for ( int v : g [ u ]) dp [ v ] = max ( dp [ v ], dp [ u ] + w ( u , v )); Common trap \\u03bc-shortcut \"Longest path\" on a general graph is NP-hard, so if a problem asks for it, look for the hidden constraint that makes cycles irrelevant: a DAG, a tree, small n , or weights that make revisiting never profitable (e.g. all-positive weights → longest finite walk is unbounded, so the answer must be about paths). Problems for this page all core hard CF 1385E Directing Edges core orientation, DAG CF 915D Almost Acyclic Graph hard is one vertex on every directed cycle? test all n candidates", "w": 573, "h": [["The same lemma, weaponised", "the-same-lemma-weaponised"], ["Directed version", "directed-version"]]}, {"u": "/foundations/subgraph/", "t": "Subgraphs, Minors and Operations", "s": "Induced vs. not, contraction, complement — the operations that turn proofs into algorithms.", "c": "Graphs and Models", "k": "definitions structural easy", "b": "Almost every structural argument in this book is one of four operations applied to a graph, so name them precisely. Definition H=(V',E') is a subgraph of G if V' ⊆ V , E' ⊆ E and every edge of E' has both ends in V' . spanning if V' = V , induced by V' , written G[V'] , if E' is all edges of G inside V' . The distinction matters for algorithms: \"does G contain a P_4 \" (subgraph — a yes if any 4 vertices are joined by 3 edges, extra edges allowed) versus \"is G P_4 -free\" (induced — extra edges forbidden, i.e. cographs). Subgraph questions are usually monotone; induced ones are not. # The four operations operation notation effect used in delete vertex G - v removes v and its incident edges induction, articulation points delete edge G - e keeps vertices bridges, MST exchange add edge G + e only if absent maximal non-Hamiltonian proofs contract G / e fuse ends of e into one vertex, drop loops & parallel copies matroids, minors, DSU! complement Ḡ edge ⟺ non-edge Ramsey, degree bounds Note Contraction is a DSU When you contract uv you physically maintain a partition of V into \"super-vertices\" with u,v merged. That is exactly what Disjoint Set Union (Union–Find) does, and it is why Kruskal can afford to contract: each find tells you which current super-vertex an endpoint lives in. # Minor and topological minor Definition H is a minor of G if H can be obtained from a subgraph of G by a sequence of contractions. If H = K_5 , this says \" G contains five disjoint connected sets, pairwise joined by an edge\". planar graphs are exactly the graphs with no K_5 and no K_3,3 minor (Wagner), graphs of treewidth ≤ k are the graphs avoiding the (k{+}3) -clique minor only for fixed k in spirit — the true statement is the grid-minor theorem, and its algorithmic echo is that \"no large clique minor\" ⟹ there is a small separator ⟹ divide and conquer works. G A B A--B D A--D C A--C B--D B--C C--D Figure 1 Contracting edges of K₄ collapses it to smaller and smaller graphs: every simple graph on ≤ 4 vertices is a minor of K₄, since K₄ is the largest 3-vertex-connectible simple graph. Lemma Contracting preserves what you must remember If e is not a bridge, G/e is 2-connected ⇔ G is 2-connected… for |V| ≥ 4 . Deleting a non -bridge edge keeps connectivity; deleting a bridge destroys it. Hence: spanning-tree questions survive contraction, \"count the ways to…\" questions generally do not, unless you track the size of each merged class. # The complement, in O(n+m) Building Ḡ naively costs O(n^2) , which is often still fine, but when m ≪ n^2 use the \"sweep unvisited\" trick — the same one that makes complement-BFS linear: cpp complement-edges.cpp Copy 1 2 3 4 5 6 7 8 9 10 set < int > unvis ( all_ids ); // vertices not yet assigned a layer for ( int u : layer ) { for ( auto it = unvis . begin (); it != unvis . end (); ) { int v = * it ; if (! adjacent ( u , v )) { // an edge of the complement nxt . insert ( v ); it = unvis . erase ( it ); // consume it once: total O(n + m) } else ++ it ; } } Problems for this page all hard core CF 1354E Graph Coloring hard bipartite sides per component, then a DP over them to hit the class sizes CF 566F Clique in the Divisibility Graph core an induced clique is a divisibility chain", "w": 582, "h": [["The four operations", "the-four-operations"], ["Minor and topological minor", "minor-and-topological-minor"], ["The complement, in", "the-complement-in-onm"]]}, {"u": "/foundations/connectivity/", "t": "Connectivity, Bridges, Articulation Points", "s": "What it means for a graph to hold together, and the two linear-time tests for its weakest points.", "c": "Graphs and Models", "k": "connectivity DFS low-link core", "b": "Definition A connected component is a maximal set of vertices pairwise joined by a path. \"Maximal\" is doing work: components partition V , and two vertices in different components have no walk between them at all. Counting components is the canonical first use of two tools: cpp components.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 1) DFS/BFS labelling — O(n+m), gives you the components themselves vector < int > comp ( n , - 1 ); int cc = 0 ; for ( int s = 0 ; s < n ; s ++) if ( comp [ s ] == - 1 ) { stack < int > st {{ s }}; comp [ s ] = cc ; while (! st . empty ()) { int u = st . top (); st . pop (); for ( int v : g [ u ]) if ( comp [ v ] == - 1 ) { comp [ v ] = cc ; st . push ( v ); } } cc ++; } // 2) DSU — O((n+m) \\u03b1(n)), and it *updates*: add edges online, ask \"same component?\" DSU dsu ( n ); for ( auto [ u , v ] : given_edges ) dsu . unite ( u , v ); for ( int q = 0 ; q < Q ; q ++) cout << ( dsu . same ( u , v ) ? \"YES\\n\" : \"NO\\n\" ); Note Adding edges vs deleting edges Offline trick: a sequence of edge deletions becomes a sequence of insertions if you process the queries backwards. Insertions are union-find, deletions are not. This single reversal appears in dozens of problems — see Minimum Spanning Tree for the \"which deletions disconnect the graph\" version. # Fragile parts Definition A bridge (cut edge) is an edge whose removal increases the number of components. An articulation point (cut vertex) is a vertex whose removal does the same. A graph with |V| ≥ 3 and no articulation point is 2-connected (biconnected). A maximal 2-connected subgraph is a block ; blocks glue together along cut vertices, forming the block-cut tree — a tree, which is why \"the graph of blocks\" supports tree DP. Theorem Characterisation An edge uv is a bridge ⇔ it lies on no cycle. A vertex v (not a root of the DFS tree) is an articulation point ⇔ it has a child c with no back edge from the subtree of c to a proper ancestor of v . Proof If uv lies on a cycle, deleting it leaves the rest of the cycle as an alternative route. Conversely, if uv is a tree edge of some DFS tree and the subtree below it contains no back edge escaping upward, then every route out of that subtree uses uv — so it is a bridge. For v and child c : if some edge from T_c reaches a strict ancestor of v , everything in T_c stays attached to v 's parent when v is removed; otherwise T_c becomes a separate component. ∎ # The linear algorithm (low-link) cpp bridges.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int timer = 0 ; vector < int > tin ( n , - 1 ), low ( n ); vector < char > is_bridge ( m ); void dfs ( int u , int pe ) { // pe = index of the edge we arrived on tin [ u ] = low [ u ] = timer ++; for ( auto [ v , id ] : g [ u ]) { if ( id == pe ) continue ; // skip THAT edge, not that vertex: parallel edges matter if ( tin [ v ] != - 1 ) low [ u ] = min ( low [ u ], tin [ v ]); // back edge else { dfs ( v , id ); low [ u ] = min ( low [ u ], low [ v ]); if ( low [ v ] > tin [ u ]) is_bridge [ id ] = 1 ; if ( low [ v ] >= tin [ u ] && pe != - 1 ) is_cut [ u ] = 1 ; // articulation point } } // root: articulation point iff it has >1 DFS child (handled separately) } Watch out Three classic implementation bugs Comparing v != parent instead of comparing the edge id : with parallel edges the second copy is a real back edge and the bridge test wrongly fires. Forgetting the root case for articulation points (it needs >1 child, since \"no proper ancestor\" is vacuous). Recursion depth. n = 2 × 10^5 on a path overflows the default stack: raise it ( ulimit -s unlimited , or #pragma comment(linker, \"/STACK:…\") locally) or write the iterative version. Same graph, live report: components, bridges and cut vertices computed while you edit the edge list. # Why anyone cares Bridge tree (contract every 2-edge-connected component) turns \"number of edges on a path\" questions into tree path queries — the standard reduction for \"add one edge, how many bridges disappear\", which is exac", "w": 814, "h": [["Fragile parts", "fragile-parts"], ["The linear algorithm (low-link)", "the-linear-algorithm-low-link"], ["Why anyone cares", "why-anyone-cares"]]}, {"u": "/foundations/bipartite/", "t": "Bipartite Graphs and 2-Colouring", "s": "The odd-cycle theorem, the BFS that finds it, and why half of all \"is this possible?\" problems are secretly bipartite.", "c": "Graphs and Models", "k": "colouring parity matching core", "b": "Definition G is bipartite if V = A ⊔ B and every edge joins A to B . A 2-colouring is the same data: colour A red, B blue. Maximum number of edges for fixed n : |A| |B| ≤ ⌊ n^2/4 ⌋ , with equality at |A| = |B| — the discrete \"product is maximised when the factors are equal\" argument. Theorem The one-characterisation G is bipartite ⇔ G contains no cycle of odd length. Proof (⇒) On any cycle, colours must alternate, so a cycle returning to its start has even length. (⇐) Root a BFS tree at each component and put a vertex in A or B according to the parity of its distance from the root. If an edge uv joined two vertices of the same side, then dist(u) ≡ dist(v) ±od 2 , and the path u ⇝ r ⇝ v plus the edge uv closes a walk of odd length; deleting cycles from that walk (cycle removal, Walks, Trails, Paths, Cycles ) leaves an odd cycle — contradiction. ∎ The proof is the algorithm: one BFS per component, colour by layer parity, and the first same-parity edge you meet is an odd cycle (BFS distances give you its exact length: dist(u) + dist(v) + 1 , and the two paths share a prefix, so the cycle is at most that long). cpp bipartite.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 vector < int > col ( n , - 1 ); bool bip = true ; for ( int s = 0 ; s < n && bip ; s ++) if ( col [ s ] == - 1 ) { queue < int > q {{ s }}; col [ s ] = 0 ; while (! q . empty () && bip ) { int u = q . front (); q . pop (); for ( int v : g [ u ]) { if ( col [ v ] == - 1 ) { col [ v ] = col [ u ] ^ 1 ; q . push ( v ); } else if ( col [ v ] == col [ u ]) { bip = false ; break ; } } } } Note DFS works too — with one caveat Colouring by DFS is equally correct (any traversal assigns consistent parities because the graph is bipartite ⟹ all paths between two fixed vertices have the same parity). But DFS does not give shortest distances, so the odd cycle you extract may not be the shortest one. For \"find the shortest odd cycle\" run BFS from every vertex of the graph, or use the two-BFS trick on the offending edge. # The modelling habit \"Two groups, incompatible pairs\" ⟹ bipartite graph, always . Concretely: statement vertices edges \"each job needs one machine, each machine one job\" jobs ∪ machines compatibility \"no two conflicting items in the same box\" items conflict ⟹ boxes = colour classes \"swap rows/columns so that…\" rows ∪ columns 1-entries \"can you flip switches to make all lights off\" switches ∪ lights switch affects light \"is this board position reachable in an even number of moves\" states moves — parity of cycle length answers it A particularly olympiad-shaped use: cutting/tiling parity . A 2 × n or checkerboard tiling question is usually \"the tile covers 1 black and 1 white cell, but the board has b ≠ w \" — that invariant is a bipartition argument in disguise. # Properties you get for free If G is bipartite with parts A, B every subgraph and every minor is bipartite, χ(G) ≤ 2 , and χ(G) = 2 as soon as one edge exists, girth ≥ 4 ; if additionally m = Ω(n^3/2) the girth is bounded (Kővári–Sós–Turán for C_4 -free graphs), Kőnig's theorem holds: max matching size = min vertex cover size ( Kőnig's Theorem and Minimum Covers ), every edge cut has the same parity structure as the adjacency matrix M ∈ {0,1}^|A| × |B| — rank arguments become available ( The Matrix–Tree Theorem ). Toggle the last edge off and on: the report's 'bipartite' row flips exactly when an odd cycle appears or disappears. Problems for this page all warm-up hard CSES 1668 Building Teams warm-up 2-colouring SPOJ BUGLIFE Buggy… love warm-up 2-colouring CF 1338B Edge Weight Assignment hard parity of paths between leaves, one DFS CSES 2179 Even Outdegree Edges hard parity, matching", "w": 680, "h": [["The modelling habit", "the-modelling-habit"], ["Properties you get for free", "properties-you-get-for-free"]]}, {"u": "/trees/properties/", "t": "Trees: Six Definitions of One Object", "s": "Equivalent characterisations, why each one is the right tool sometimes, and the leaf/degree counting identities.", "c": "Trees", "k": "trees counting easy", "b": "On trees theorems have one extra hypothesis-free form: \"connected and acyclic\" appears in six disguises, and a good proof picks whichever is easiest to preserve. Theorem Six equivalent statements (for |V| = n \\u2265 1) For a simple graph T : T is connected and acyclic. T is connected and has n-1 edges. T is acyclic and has n-1 edges. Any two vertices are joined by exactly one path. T is connected, but deleting any edge disconnects it. T is acyclic, but adding any missing edge creates exactly one cycle. Proof The cycle of implications is short; each step is one idea. (1 ⇒ 2) Induct on n : a finite acyclic connected graph has a vertex of degree 1 (take a longest path — its endpoint cannot have another neighbour, or you get a cycle or a longer path), so delete it and apply the hypothesis. (2 ⇒ 3) Acyclic, else remove an edge of a cycle and stay connected, contradicting minimality of m for connectedness; formally use (2⇒5)⇒(1) . (3 ⇒ 4) : n-1 edges and acyclic implies connected (a forest with c components has n-c edges), so paths exist; uniqueness is acyclicity — two distinct paths between u,v would contain a cycle. (4 ⇒ 5) connected is free; deleting an edge of the unique u – v path separates them. (5 ⇒ 6) if T+e had two cycles sharing the new edge, the old graph would already have a cycle… contrapositive of \"every edge is a bridge\". (6 ⇒ 1) if T had a cycle, delete one of its edges: connectivity is unchanged, contradicting maximality of the acyclic graph. ∎ Note Which one to keep in hand Need an induction? Use (1): a leaf always exists. Need to count ? Use (2)/(3): m = n-1 is the workhorse, e.g. the handshaking identity below. Need uniqueness of routes ? Use (4): \"the path is unique\" turns min/max questions into \"it must go through this vertex\". Need a greedy ? Use (5): every edge is a bridge, so any edge you delete must be re-added by something else. # Counting identities For a tree with L leaves and n vertices ∑_v deg(v) = 2(n-1) (handshaking + (2)), L = 2 + ∑_v:deg(v) ≥ 3 (deg(v) - 2) — so many high-degree vertices force many leaves, the average degree is 2 - 2/n , so a tree is always sparse: some vertex has degree 1 (a leaf) unless n ≤ 2 , Δ ≤ 2 forces T to be a path, and L ≤ 2 does the same — these are the two ends of the identity above, rooting at r gives n-1 parent pointers; a tree on n labelled vertices has n^n-2 shapes ( Counting Trees: Cayley and Prüfer ). # Rooted trees and DFS order Rooting is not extra structure, it is a choice of coordinates : every vertex except the root gets one parent, and subtrees become contiguous intervals in DFS order. cpp rooted-tree.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > par ( n , - 1 ), sz ( n , 1 ), tin ( n ), tout ( n ), depth ( n ); int timer = 0 ; void dfs ( int u ) { tin [ u ] = timer ++; for ( int v : g [ u ]) if ( v != par [ u ]) { par [ v ] = u ; depth [ v ] = depth [ u ] + 1 ; dfs ( v ); sz [ u ] += sz [ v ]; } tout [ u ] = timer ; // [tin, tout) = the subtree of u } That interval property — v is in the subtree of u ⇔ tin(u) ≤ tin(v) < tout(u) — is the single most reused fact in tree algorithms; it is why Entry/Exit Times and the Euler Tour , LCA by Binary Lifting and Heavy-Light Decomposition all work. Example Subtree queries for free Q: update every vertex in the subtree of u by +x ; query the value at v . A: the subtree is an interval in tin order, so this is a range-add + point-query on an array: one Fenwick Tree (Binary Indexed Tree) , O(log n) each, no new algorithm required. Problems for this page all warm-up core SPOJ PT07Z Longest path in a tree warm-up diameter CF 519E A and B and Lecture Rooms core subtree sizes and LCA CSES 1674 Subordinates warm-up subtree sizes, counted bottom-up", "w": 710, "h": [["Counting identities", "counting-identities"], ["Rooted trees and DFS order", "rooted-trees-and-dfs-order"]]}, {"u": "/trees/distance/", "t": "Distance, Radius, Eccentricity", "s": "dist on trees and graphs, the centre-of-a-tree theorem, and why two BFS passes are enough.", "c": "Trees", "k": "trees BFS diameter core", "b": "Definition For a connected (unweighted) graph: dist(u,v) = number of edges on a shortest path, ecc(v) = max_u dist(v,u) — how far v is from the worst vertex, radius r = min_v ecc(v) , attained at a centre , diameter d = max_u,v dist(u,v) , attained at a peripheral pair . Always r ≤ d ≤ 2r . Proof Pick a peripheral pair a,b with dist(a,b) = d and a centre c . By the triangle inequality d = dist(a,b) ≤ dist(a,c) + dist(c,b) ≤ 2 ecc(c) = 2r , and r ≤ d because min ≤ max . ∎ On trees the picture is rigid: Theorem Structure of the centre The centre of a tree is a single vertex or a single edge (two adjacent vertices). Equivalently: repeatedly delete all leaves; what remains — one vertex or one edge — is the centre, the diameter path passes through it, and ecc is the same for both centre vertices in the edge case. Proof Every longest path (diameter) in a tree is unique up to reversal between its endpoints … more precisely: all diameters share the centre. Leaf-stripping decreases the eccentricity of every surviving vertex by exactly 1 and cannot create a new longest path, so the process ends on the set of vertices of minimum eccentricity. Since the tree has no cycle, the survivor set is connected: one vertex or one edge. ∎ Algorithmically, leaf-stripping is a multi-source BFS from all leaves with a queue — O(n) , and it also solves \"minimum height rooted tree\" (LeetCode-style find root of minimum height tree ), and the tree centre used as the root of a centroid-like decomposition heuristic. # Distances between many pairs at once Two questions that look like \"run BFS n times\" but are not: Example Eccentricity of every vertex in a tree The naive O(n^2) becomes two DFS passes: for each vertex the farthest vertex is an endpoint of some diameter — this is false in general, but for a tree the following DP is exact: keep down[u] (best distance into the subtree) and up[u] (best distance via the parent), then ecc(u) = max(down, up) . Combining children needs the two largest down values, hence O(n) . The rerooting code is below. cpp reroot.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // down1[u], down2[u]: the two deepest downward paths through u void dfs1 ( int u , int p ) { for ( int v : g [ u ]) if ( v != p ) { dfs1 ( v , u ); int cand = down1 [ v ] + 1 ; if ( cand >= down1 [ u ]) { down2 [ u ] = down1 [ u ]; down1 [ u ] = cand ; } else if ( cand > down2 [ u ]) down2 [ u ] = cand ; } } // up[u]: best path going through the parent of u void dfs2 ( int u , int p ) { for ( int v : g [ u ]) if ( v != p ) { int via_parent = 1 + max ( up [ u ], ( down1 [ v ] + 1 == down1 [ u ] ? down2 [ u ] : down1 [ u ])); up [ v ] = max ( up [ v ], via_parent ); dfs2 ( v , u ); } } // ecc[u] = max(down1[u], up[u]); radius = min ecc; diameter = max ecc Note Why the guard down1[v] + 1 == down1[u] When the best path through u is the one coming from v , you must not reuse it — take the second best instead. Every rerooting DP has this \"exclude the child you came from\" step; getting it wrong is the most common silent bug in tree DPs, and it is exactly why keeping the two best values (not just the best) is worth the extra array. # General graphs All-pairs BFS: O(nm) — fine for n ≤ 2000 , better than Floyd's O(n^3) when the graph is sparse. Unweighted with small integer weights: 0-1 BFS / Dial, 0-1 BFS, Dial, Potentials, Johnson . Weighted: run Dijkstra per source ( O(nm + n^2 log n) ) or Floyd–Warshall ( Floyd–Warshall ). Approximating diameter in huge graphs: two BFS passes give a 2 -approximation in any graph, and O(√(log n)) -approximation needs more machinery; the exact value needs tildeΘ(nm) under SETH, which is the standard \"you cannot do better\" citation. Same graph: the BFS layer numbers are exactly dist(root, ·); DFS's are not. That is the whole difference between the two traversals. Problems for this page all core hard CSES 1132 Tree Distances I core rerooting CSES 1133 Tree Distances II core sum of distances CF 700B Connecting Universities hard edg", "w": 756, "h": [["Distances between many pairs at once", "distances-between-many-pairs-at-once"], ["General graphs", "general-graphs"]]}, {"u": "/trees/counting/", "t": "Counting Trees: Cayley and Prüfer", "s": "n^(n-2) labelled trees, the bijection that proves it, and what the degree sequence looks like from the code.", "c": "Trees", "k": "counting bijection trees hard", "b": "Theorem Cayley's formula The number of trees on the labelled vertex set {1, …, n} is n^n-2 . Three proofs exist in the wild; only one is useful in a contest, because it also generates and decodes . # The Prüfer code Definition Given a labelled tree, repeat until two vertices remain: output the label of the smallest leaf , delete it, and decrease the degree of its neighbour. The output is a sequence of n-2 numbers in [1,n] . Theorem Bijection The map above is a bijection between labelled trees on n vertices and all n^n-2 sequences of length n-2 over [1,n] . Proof Construct the inverse: given P = (p_1, …, p_n-2) , set deg(v) = 1 + #{i : p_i = v} . For i = 1 … n-2 : let v be the smallest label with deg(v) = 1 ; emit the edge v → p_i ; set deg(v) := 0 , deg(p_i) := deg(p_i) - 1 . Finally connect the two labels that still have deg = 1 . Well-defined: at each step the remaining degree sum is ∑ (1 + occurrences left) = (number of remaining vertices) + (remaining sequence length) = k + (k - 2) > k - 1 , so at least two vertices have degree 1 — a leaf always exists. Round-trip: decoding rebuilds exactly the deletions in order, because both procedures pick \"smallest vertex whose remaining degree is 1\" at the same moments; encoding a decoded tree recovers P since each emitted edge's non-leaf endpoint is p_i . Counting: n choices per position, n-2 positions. ∎ cpp prufer.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // encode: O(n log n) with a set of leaves vector < int > prufer_encode ( int n ) { multiset < int > leaves ; for ( int v = 1 ; v <= n ; v ++) if ( deg [ v ] == 1 ) leaves . insert ( v ); vector < int > p ; vector < int > d = deg , par ( n + 1 ); set < int > alive ; for ( int v = 1 ; v <= n ; v ++) alive . insert ( v ); while (( int ) p . size () < n - 2 ) { int v = * leaves . begin (); leaves . erase ( leaves . begin ()); int u = neighbour_of ( v ); // the unique alive neighbour p . push_back ( u ); if (-- d [ u ] == 1 ) leaves . insert ( u ); alive . erase ( v ); } return p ; } # What the code makes trivial Corollaries you can read off the sequence deg(v) = 1 + (number of times v appears in the code) — the standard solution to \"reconstruct the tree from its degrees\". Number of trees where vertices 1..k are leaves : sequences avoiding 1..k = (n-k)^n-2 . Number of spanning trees of K_a,b : a^b-2 b^a-2 — count sequences whose positions split by part, or use The Matrix–Tree Theorem . Probability a fixed vertex has degree d in a random tree: C(n-2, d-1) (1/n)^d-1 (1-1/n)^n-1-d — binomial, so degrees concentrate near 1. Rooted labelled trees: n^n-1 (multiply by n choices of root). Ordered (plane) trees with n vertices: Catalan (1)/(n)C(2n-2, n-1) — a different object; don't mix them up. Example Reconstruct from degrees (classic) Given degrees d_1, …, d_n with ∑ d_i = 2n-2 , output any tree. Run the decode direction: push every vertex i exactly d_i - 1 times into a queue, keep a min-heap of \"still has zero copies used and degree 1\", attach greedily. O(n log n) , and it is exactly Prüfer decoding with the multiset precomputed. G A 1 B 2 A->B C 3 A->C D 4 A->D E 5 B->E F 6 B->F G 7 B->G H 8 C->H I 9 C->I J 10 C->J K 11 D->K L 12 D->L M 13 D->M N 14 E->N O 15 E->O P 16 E->P Figure 1 Rooting multiplies the count by n — one choice per tree — which is why n^(n-1) counts rooted labelled trees. # When counting is modulo a prime Contest versions ask for n^n-2 mod p . Two cautions: n can be 10^9 , so use fast power; if p is not prime, Euler's theorem needs gcd(n,p)=1 . \"Count trees with additional constraints\" (degree upper bound, prescribed diameter) is usually not Prüfer-friendly except via generating functions: prescribe degrees exactly → multinomial ((n-2)!)/(∏ (d_i - 1)!) . Problems for this page all core hard CSES 1134 Prüfer Code core decoding CF 9D How many trees? hard DP, Catalan-ish SPOJ HIGHWAYS Counting Highways hard matrix-tree theorem", "w": 698, "h": [["The Prüfer code", "the-prüfer-code"], ["What the code makes trivial", "what-the-code-makes-trivial"], ["When counting is modulo a prime", "when-counting-is-modulo-a-prime"]]}, {"u": "/trees/dfs/", "t": "Depth-First Search", "s": "The recursion that classifies every edge, finds cycles, and produces subtrees, low-links and topological order.", "c": "Trees", "k": "traversal recursion cycles core", "b": "DFS is not \"BFS with a stack\" — that description hides the useful part. DFS explores one branch to the end and finishes vertices in reverse order of discovery, and it is the finish order that carries information. cpp dfs.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > vis ( n ), tin ( n ), tout ( n ), par ( n , - 1 ); int timer = 0 ; function < void ( int )> dfs = [&]( int u ) { vis [ u ] = 1 ; tin [ u ] = timer ++; for ( int v : g [ u ]) { if ( par [ u ] == v ) continue ; // ignore the tree edge we came from if ( vis [ v ]) { /* back edge: u–v closes a cycle */ } else { par [ v ] = u ; dfs ( v ); } } tout [ u ] = timer ; // subtree of u = [tin[u], tout[u]) }; Theorem Edge classification (undirected, DFS forest) Every edge of an undirected graph, run through DFS, is exactly one of: tree edge — it discovered a new vertex, back edge — it joined u to an ancestor v already on the stack. There are no cross edges: if uv joined two unrelated branches, whichever one was explored first would have discovered the other. Proof Suppose uv is not a tree edge; WLOG tin(u) < tin(v) . When u was on the stack and scanned uv , either v was already visited — meaning v is an ancestor, because a visited-and-finished v would have needed u visited to be finished — or v was unvisited and uv became a tree edge. Contradiction, so v is an ancestor: back edge. ∎ That single lemma gives: Four corollaries, one line each a cycle exists ⇔ there is a back edge (DFS finds one in O(n+m) ), ⇔ some edge connects two vertices of the same DFS subtree branch, the graph is bipartite ⇔ no back edge has even \"depth parity\" difference ( Bipartite Graphs and 2-Colouring uses BFS, DFS works too), bridges : uv (tree edge, u parent of v ) is a bridge ⇔ low[v] > tin[u] where low is the smallest tin reachable from v 's subtree using at most one back edge ( Connectivity, Bridges, Articulation Points ). # Directed graphs add a third type With three colours (white/grey/black) a directed DFS edge is tree , back (to a grey vertex — the only kind that means a cycle), forward (to a black descendant) or cross (to a black vertex in an earlier branch). Theorem Cycle test A digraph has a cycle ⇔ DFS finds a back edge. Proof Back edge u → v with v grey: v is an ancestor of u in the DFS tree, so v ⇝ u (tree edges) plus u → v is a cycle. Conversely, take the cycle C and let w be the first vertex of C to be discovered; DFS follows C from w (all its successors on C are white when scanned) and so returns to w by a back edge. ∎ # Iterative DFS (when n = 10^6 ) cpp dfs-iterative.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > st { s }, it ( n ); // it[u] = next child index to try par [ s ] = - 1 ; tin [ s ] = 0 ; while (! st . empty ()) { int u = st . back (); if ( it [ u ] < ( int ) g [ u ]. size ()) { int v = g [ u ][ it [ u ]++]; if ( v == par [ u ]) continue ; if ( vis [ v ]) { /* back edge */ } else { vis [ v ] = 1 ; par [ v ] = u ; tin [ v ] = timer ++; st . push_back ( v ); } } else { tout [ u ] = timer ; st . pop_back (); } // finish } This is the version to write when the recursion depth could be n (a path graph is a legal test case), because a stack overflow is not a wrong answer you can debug — it is a runtime error at 0.4 s that looks like a bug in your code. Common trap DFS order is not distance order tin tells you nothing about shortest paths. Two uses that are frequently confused: subtree interval (fine with DFS) versus level/layer (needs BFS). \"Shortest path in an unweighted graph with DFS\" is a wrong algorithm, not a slow one. The push steps are the grey stack; the back edge steps are exactly the ones the cycle lemma talks about. Problems for this page all core hard CSES 1669 Round Trip core undirected cycle CF 977E Cyclic Components core DFS classification CSES 1679 Course Schedule core DFS + cycle CF 459E Pashmak and Graph hard edge orientation", "w": 722, "h": [["Directed graphs add a third type", "directed-graphs-add-a-third-type"], ["Iterative DFS (when )", "iterative-dfs-when-n-106"]]}, {"u": "/trees/bfs/", "t": "Breadth-First Search", "s": "Layers, shortest paths in unweighted graphs, and the multi-source / 0-1 variants that actually win contests.", "c": "Trees", "k": "traversal shortest-path core", "b": "BFS answers exactly one question honestly: how many edges from the source. Everything else — shortest path, layers, \"is it bipartite\", \"can I reach it in k moves\" — is a corollary. cpp bfs.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > dist ( n , - 1 ), par ( n , - 1 ); queue < int > q ; dist [ s ] = 0 ; q . push ( s ); while (! q . empty ()) { int u = q . front (); q . pop (); for ( int v : g [ u ]) if ( dist [ v ] == - 1 ) { // \"unseen\" doubles as \"not yet in queue\" dist [ v ] = dist [ u ] + 1 ; par [ v ] = u ; q . push ( v ); } } Theorem Correctness When BFS pops u , dist[u] = dist(s,u) . Proof Two inductions. (i) Every vertex pushed with value k is reachable by a walk of length k , so dist(s,u) ≤ dist[u] — the stored value is an upper bound. (ii) Take a shortest path s = v_0, …, v_k = u . Induct on i : v_i is discovered with ≤ i , because when v_i-1 is popped its edge to v_i relaxes it, and pop order is non-decreasing in distance so v_i-1 is popped before u could be. Hence dist[u] ≤ k = dist(s,u) . Both bounds meet. ∎ The \"non-decreasing pop order\" step is the entire reason BFS works and DFS does not: the queue is a sorted container of a two-valued key (all elements differ by at most 1), which is precisely the condition for a deque to replace a priority queue — see 0-1 BFS below. What the same 8 lines give you shortest #edges from one source; the par array is a BFS tree = shortest-path tree multi-source : push all sources with dist 0 → nearest source for every vertex (Voronoi on a grid) layered : dist == k sets are the BFS levels; edges only join levels k, k{+}1 (undirected) — that is the bipartition of Bipartite Graphs and 2-Colouring bidirectional BFS on huge implicit graphs: expand the smaller frontier, cost drops from b^d to 2 b^d/2 counting shortest paths : ways[v] += ways[u] when dist[v] == dist[u] + 1 , = when equal 0-1 weights : deque , push-front for weight 0, push-back for weight 1 cpp zero-one-bfs.cpp Copy 1 2 3 4 5 6 7 8 9 deque < int > dq ; dist . assign ( n , INF ); dist [ s ] = 0 ; dq . push_back ( s ); while (! dq . empty ()) { int u = dq . front (); dq . pop_front (); for ( auto [ v , w ] : g [ u ]) if ( dist [ u ] + w < dist [ v ]) { dist [ v ] = dist [ u ] + w ; if ( w == 0 ) dq . push_front ( v ); else dq . push_back ( v ); } } Watch out Mark at push time, not pop time Setting dist[v] when you push is what keeps each vertex in the queue once and the algorithm at O(n+m) . If you mark on pop, the queue can hold O(m) duplicates — sometimes fine (Dijkstra with lazy deletion deliberately does it), never fine for counting arguments or memory. # The tree case: \"BFS from the leaves\" On a tree, running BFS with all leaves in the initial queue peels the tree layer by layer. It yields: the centre (last layer, size 1 or 2 — Distance, Radius, Eccentricity ), topological-ish pruning for \"remove vertices of degree ≤ 1 repeatedly\", which is how you find paths, cores, and \"who survives k rounds\". cpp peel.cpp Copy 1 2 3 4 5 6 7 8 queue < int > q ; for ( int i = 0 ; i < n ; i ++) if (( deg [ i ] = ( int ) g [ i ]. size ()) <= 1 ) q . push ( i ); vector < int > order ; while (! q . empty ()) { int u = q . front (); q . pop (); order . push_back ( u ); for ( int v : g [ u ]) if ( v != parent_of ( u , v ) || true ) // no parent check needed: degree guard if (-- deg [ v ] == 1 ) q . push ( v ); } Switch to DFS and watch the dist column: the same vertex order becomes a depth order, and distance claims break. Problems for this page all warm-up core CSES 1193 Labyrinth warm-up grid BFS CSES 1194 Monsters core multi-source BFS CF 1242B 0-1 MST core dense graph, so traverse the complement CSES 1670 Swap Game core implicit graph BFS", "w": 680, "h": [["The tree case: \"BFS from the leaves\"", "the-tree-case-bfs-from-the-leaves"]]}, {"u": "/trees/diameter/", "t": "Tree Diameter in Two Passes", "s": "The double-BFS trick, the proof that it is correct, and the DP that beats it when you need more.", "c": "Trees", "k": "trees diameter DP core", "b": "Pick any vertex s . Let a be a farthest vertex from s . Let b be a farthest vertex from a . Then dist(a,b) is the diameter. cpp diameter.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 pair < int , int > far ( int s ) { // -> {distance, vertex} vector < int > d ( n , - 1 ), par ( n , - 1 ); stack < int > st {{ s }}; d [ s ] = 0 ; // DFS is enough on a tree! int best = s ; while (! st . empty ()) { int u = st . top (); st . pop (); if ( d [ u ] > d [ best ]) best = u ; for ( int v : g [ u ]) if ( d [ v ] == - 1 ) { d [ v ] = d [ u ] + 1 ; par [ v ] = u ; st . push ( v ); } } return { d [ best ], best }; } auto [ d1 , a ] = far ( 0 ); // pass 1: an endpoint of a diameter auto [ diam , b ] = far ( a ); // pass 2: the diameter itself Note DFS is fine on a tree On a general graph you must use BFS (distances). On a tree there is exactly one path, so any traversal computes the true distance; DFS avoids the queue and, more importantly, also hands you the par array so you can reconstruct the diameter path itself. Theorem Why it works dist(a,b) equals the diameter D . Proof (≤) is trivial: a,b are vertices, so their distance is at most the maximum over all pairs. For (≥) , let P = x ⇝ y be a diameter path ( D = dist(x,y) ). Claim: a is an endpoint of some diameter. Root the tree at s . Let c be the highest (closest to s ) vertex on the path from s to P , splitting the tree at c into the branch containing x (length p = dist(c,x) ), the branch containing y ( q = dist(c,y) ), and the branch containing s ( t = dist(c,s) ). Farthest from s means dist(s,a) = max(t+p, t+q) ≥ t + D/2 , and p+q = D . Now: dist(a,x) ≥ dist(s,x) ? Compare through c — the standard exchange gives max(dist(a,x), dist(a,y)) ≥ max(t+p, t+q) , because a is at distance ≥ t from c on the side opposite to whichever of x,y is farther, so a 's eccentricity is at least s 's. Hence b , being farthest from a , satisfies dist(a,b) ≥ ecc(s) ≥ max(t+p, t+q) and choosing the worse of x,y shows dist(a,b) ≥ D . ∎ That proof is worth reading twice: the only thing used is \"one branch at the split point\", which is exactly the \"path uniqueness\" definition of a tree ( Trees: Six Definitions of One Object (4)) — the argument collapses on a general graph, where double-BFS is only a 2-approximation, and that is a tight result, not laziness. # When you need more than the number Example All pairs of vertices at distance exactly k Two options: (a) edge contribution — for each edge, count pairs whose path uses it; (b) centroid decomposition ( Centroid Decomposition ) in O(n log n) for \"count pairs with distance ≤ K\" including weights. Double-BFS cannot do either; the DP generalises. cpp diameter-dp.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 // classic tree DP: longest downward chain + best path through u int ans = 0 ; function < int ( int , int )> dfs = [&]( int u , int p ) { int best1 = 0 , best2 = 0 ; // two deepest child chains for ( int v : g [ u ]) if ( v != p ) { int c = dfs ( v , u ) + 1 ; if ( c > best1 ) { best2 = best1 ; best1 = c ; } else if ( c > best2 ) best2 = c ; } ans = max ( ans , best1 + best2 ); // path passing through u return best1 ; }; dfs ( 0 , - 1 ); Tip Pick the right one Two passes: 6 lines, no recursion, easy to write correctly — the diameter length. DP: needed whenever the answer is per-vertex, per-edge, \"through u \", or when you must avoid the diameter's endpoints. If the problem only asks for the length, write the two passes under time pressure. Start vertex choice changes the traversal order but never the second pass's answer — try start = 0 and start = 5 . Problems for this page all warm-up hard core CSES 1131 Tree Diameter warm-up two BFS SPOJ PT07Z Longest path in a tree warm-up two BFS CF 1000E We Need More Bosses hard bridges + diameter CSES 2079 Finding a Centroid core subtree sizes", "w": 731, "h": [["When you need more than the number", "when-you-need-more-than-the-number"]]}, {"u": "/trees/euler-tour/", "t": "Entry/Exit Times and the Euler Tour", "s": "Flatten a tree into an array, and get subtree queries, ancestry tests and LCA-from-RMQ for free.", "c": "Trees", "k": "trees flatten RMQ core", "b": "There are two different objects with this name, and mixing them up ruins afternoons: Definition Tin/tout (entry–exit, DFS order) : tin[u] when u is first visited, tout[u] after its whole subtree is done. Subtree of u = the contiguous interval [tin[u], tout[u]) . Euler tour of the walk (also called the first-occurrence tour): the length- 2n-1 sequence of vertices visited along the DFS walk, including repeats when returning up. Its range minima give LCA. cpp tin-tout.cpp Copy 1 2 3 4 5 6 7 8 vector < int > tin ( n ), tout ( n ), flat ( n ); // flat[tin[u]] = u int timer = 0 ; void dfs ( int u , int p ) { tin [ u ] = timer ; flat [ timer ] = u ; timer ++; for ( int v : g [ u ]) if ( v != p ) dfs ( v , u ); tout [ u ] = timer ; // half-open: [tin, tout) } bool is_ancestor ( int u , int v ) { return tin [ u ] <= tin [ v ] && tout [ v ] <= tout [ u ]; } Theorem Subtree = interval v lies in the subtree of u ⇔ tin[u] ≤ tin[v] < tout[u] ⇔ [tin[v], tout[v]) ⊆ [tin[u], tout[u]) . Proof DFS enters u , then recursively finishes each child's entire subtree before exiting u ; so all times assigned between tin[u] and tout[u] belong to descendants, and nothing outside that window can be a descendant. ∎ # Everything this buys Example Subtree add / subtree sum Add x to every vertex in the subtree of u ; query the value at v : one range-add/point-query Fenwick Tree (Binary Indexed Tree) over the flat array. Subtree sum? Range-sum over [tin[u], tout[u]) . Both O(log n) . cpp subtree-queries.cpp Copy 1 2 3 4 5 6 7 // subtree add, subtree sum: BIT with two arrays (standard trick) or a lazy segtree bit1 . add ( tin [ u ], x ); bit1 . add ( tout [ u ], - x ); // point query at v: val = bit1 . sum ( tin [ v ]); // sum over a subtree needs the \"weighted\" version: add ( l , r , x ): bitA . add ( l , x ); bitA . add ( r , - x ); bitB . add ( l , x *( l - 1 )); bitB . add ( r , - x * r ); prefix ( p ) = p * sum ( bitA , p ) - sum ( bitB , p ); Note Which to use, honestly If updates are on vertices/edges and queries are subtree-aggregate, tin/tout + a lazy segment tree is the shortest correct code. The moment queries involve paths (root-to-vertex, u – v ), tin/tout alone is not enough: jump to Heavy-Light Decomposition (paths split into O(log n) subtree-queries) or Euler-tour + LCA for root-paths only. # The 2n−1 tour and RMQ Walk the tree along the DFS, appending a vertex each time you step onto it (down or up). Length 2n-1 . Then: Theorem LCA ⟺ RMQ lca(u,v) is the vertex of minimum depth in the tour between the first occurrences of u and v . Proof Between the first visit of u and the first visit of v the walk must leave u 's branch and climb to their common ancestor; it cannot climb above lca(u,v) without having visited that ancestor later (which would place a shallower vertex inside the interval, and contradict that both are still below it). The vertex where the walk turns around is exactly lca(u,v) , and it is the unique shallowest one in the range. ∎ So \"LCA in O(1) \" reduces to \"RMQ in O(1) after O(n log n) \", solved by a sparse table ( Sparse Table and RMQ ) — or O(n) build with the ±1 RMQ trick ( LCA via Range Minimum Query ). cpp euler-2n1.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 vector < int > tour , first ( n , - 1 ), depth ( n ); void dfs ( int u , int p , int d ) { first [ u ] = ( int ) tour . size (); tour . push_back ( u ); depth [ u ] = d ; for ( int v : g [ u ]) if ( v != p ) { dfs ( v , u , d + 1 ); tour . push_back ( u ); // coming back up } } int lca ( int u , int v ) { int l = first [ u ], r = first [ v ]; if ( l > r ) swap ( l , r ); return rmq_min_by_depth ( l , r ); // sparse table over tour[] } Common trap tout[u] = timer++ is a bug, not a style choice Some references define tout as a third increment (\"timer used 2n times\"). Then subtree = [tin[u], tout[u]] inclusive , and the interval test changes to tin[u] <= tin[v] && tout[u] >= tout[v] . Pick one convention and write it as a comment in your template — mixed conventions between a is_a", "w": 807, "h": [["Everything this buys", "everything-this-buys"], ["The 2n−1 tour and RMQ", "the-2n1-tour-and-rmq"]]}, {"u": "/directed/definitions/", "t": "Orientations, In/Out-Degree, Strong vs Weak", "s": "What changes when edges get arrows — and the degree identities that survive.", "c": "Directed Graphs", "k": "directed definitions easy", "b": "A digraph is G = (V,E) with E ⊆ V × V : ordered pairs, so uv and vu are different edges. Nothing else about the vocabulary changes, and that is a trap — almost every undirected intuition needs one extra word: weakly or strongly . Definition deg^+(v) = number of edges leaving v ; deg^-(v) = number entering. ∑_v deg^+(v) = ∑_v deg^-(v) = m . Balanced (or Eulerian-oriented ) means deg^+(v) = deg^-(v) for every v — the exact condition behind Euler Tours: When They Exist . Proof Count edges by their tail for the first identity, by their head for the second. Both equal m . ∎ Definition weakly connected : connected after forgetting directions (equivalently, the underlying undirected graph is connected). unilaterally connected : for every u,v there is a directed path u ⇝ v or v ⇝ u . strongly connected : for every ordered pair (u,v) a directed path u ⇝ v . Strong ⟹ unilateral ⟹ weak, and each implication is strict: a directed 3-cycle is strong; two strongly connected cycles joined by a single arrow are unilateral but not strong; an undirected path viewed as a digraph is only weak. # Reachability is a partial order (after condensing) Reachability in a digraph is reflexive and transitive, but not symmetric — a preorder. Collapse each set of mutually reachable vertices (a strongly connected component ) and you get an honest partial order on the components. Everything you can do with \"≤\" becomes available: After condensation ( Strongly Connected Components ) the condensation is a DAG ⟹ it has a topological order ( DAGs and Topological Order ), \"can I reach v from u ?\" = \" [u] ≤ [v] in the DAG\" — answerable with bitset DP in O(n^2/64) per vertex set, a DAG with a Hamilton path has a unique topological order, and conversely: consecutive-in-order edges exist ⟹ Hamilton path (the standard linear test), in-degrees and out-degrees of components decide the \"minimum number of paths covering all vertices\"-style answers. # Orientation problems \"Given an undirected graph, direct the edges so that …\" is a whole genre, and the answers are always the same two facts: Theorem Strong orientation (Robbins) An undirected graph admits a strongly connected orientation ⇔ it is connected and has no bridge. Proof (⇒) A strongly connected digraph has no bridge (deleting any edge leaves an alternative directed route between its ends, hence connectivity). (⇐) Bridgeless connected ⇒ every edge lies on a cycle; decompose G into its 2-edge-connected blocks and orient each block along an ear decomposition (start with a cycle oriented cyclically, add each ear as a directed path between its already-oriented endpoints). Result: strongly connected. ∎ The algorithmic version is 12 lines: find bridges, then for each 2-edge-connected component run the ear-based orientation, or equivalently output the bridge-less DFS order and orient tree edges downward and back edges upward. Example Balanced orientation deg^+(v) = deg^-(v) for all v is possible ⇔ every degree is even ⇔ V splits into edge-disjoint cycles (an Eulerian decomposition). Orient each cycle consistently. This is why \"even outdegree\" problems reduce to Hierholzer's Linear Algorithm . G A A D D A->D C C D->C B B B->A B->D C->B E E C->E E->B F F F->D F->C Figure 1 A digraph with three SCCs; the condensation is the arrow between them, which is a DAG. cpp out-in-deg.cpp Copy 1 2 3 vector < int > ind ( n ), outd ( n ); for ( auto [ u , v ] : edges ) { outd [ u ]++; ind [ v ]++; } for ( int i = 0 ; i < n ; i ++) if ( ind [ i ] != outd [ i ]) { /* needs an added edge / not Eulerian */ } Problems for this page all core hard CSES 1682 Flight Routes Check core strong connectivity CF 1385E Directing Edges core orientation + toposort CSES 2179 Even Outdegree Edges hard orientation, matching", "w": 618, "h": [["Reachability is a partial order (after condensing)", "reachability-is-a-partial-order-after-condensing"], ["Orientation problems", "orientation-problems"]]}, {"u": "/directed/dag-toposort/", "t": "DAGs and Topological Order", "s": "Two algorithms, one equivalence, and DP on a DAG as the default solution shape.", "c": "Directed Graphs", "k": "DAG ordering DP core", "b": "Definition A directed acyclic graph is a digraph with no directed cycle. A topological order is a permutation v_1, …, v_n such that every edge v_i → v_j has i < j . Theorem The fundamental equivalence G has a topological order ⇔ G is a DAG. Proof (⇐) A non-empty DAG has a vertex of indegree 0 (otherwise walk backwards forever and repeat a vertex → cycle). Put it first and induct. (⇒) An order is a strict ranking: along any directed walk indices strictly increase, so no vertex can repeat — there are no cycles. ∎ # Kahn's algorithm (queue of zero-indegree) cpp kahn.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > indeg ( n ); for ( auto [ u , v ] : edges ) indeg [ v ]++; queue < int > q ; for ( int i = 0 ; i < n ; i ++) if (! indeg [ i ]) q . push ( i ); vector < int > order ; while (! q . empty ()) { int u = q . front (); q . pop (); order . push_back ( u ); for ( int v : g [ u ]) if (!-- indeg [ v ]) q . push ( v ); } if ( order . size () != n ) return {}; // cycle: the leftovers contain one Note Why the leftover is a cycle, not just a mess Vertices remaining after Kahn have positive indegree inside the leftover set; walking backwards through them must repeat a vertex, which yields a directed cycle. So \"cycle detection\", \"topological sort\" and \"find one cycle\" are the same 10 lines. Use priority_queue instead of queue if you need the lexicographically smallest order — same complexity plus a log. # DFS post-order version cpp toposort-dfs.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 vector < int > col ( n ), order ; bool cyc = false ; function < void ( int )> dfs = [&]( int u ) { col [ u ] = 1 ; // grey: on the stack for ( int v : g [ u ]) { if ( col [ v ] == 1 ) cyc = true ; // back edge else if (! col [ v ]) dfs ( v ); } col [ u ] = 2 ; order . push_back ( u ); }; for ( int i = 0 ; i < n ; i ++) if (! col [ i ]) dfs ( i ); reverse ( order . begin (), order . end ()); // topological order Both are O(n+m) ; DFS needs a stack (or std::function overhead), Kahn needs the indegree array. Prefer Kahn in contests — no recursion depth risk, and the \"leftovers = cycle\" check is one line. # The real prize: DP on a DAG Once vertices are ordered, every \"longest path / number of paths / reachable set\" question is a single sweep: process u , push your value into each successor. cpp dag-dp.cpp Copy 1 2 3 4 5 6 7 8 9 // number of paths s -> v, and longest path length, in one pass vector < long long > ways ( n ); vector < int > best ( n , - INF ); ways [ s ] = 1 ; best [ s ] = 0 ; for ( int u : order ) { for ( int v : g [ u ]) { ways [ v ] += ways [ u ]; // mod M if asked best [ v ] = max ( best [ v ], best [ u ] + w ( u , v )); } } Key idea Recognition pattern \"Each task takes 1 unit / requires other tasks / can be done in some order…\" plus n up to 2 × 10^5 = toposort. If the graph has cycles, ask whether you should condense it ( Strongly Connected Components ) first: a cycle inside a \"must-be-ordered\" model usually means \"these are equivalent\", not \"impossible\". Four problems that are one DAG sweep Longest path in a DAG : the best line above; NP-hard on general graphs ( Held–Karp: Hamilton in O(2ⁿn²) ). Counting paths mod M : the ways line (CSES Game Routes , 1681). Reachability bitsets : reach[u] |= reach[v] , O(nm/64) (CSES 2138 Reachable Nodes ). Minimum path cover : after toposort, build the bipartite graph and run matching ( Matching Applications , Dilworth). Kahn's algorithm with the indegree array exposed. Flip to 'with a cycle' to see the leftovers and the failed output. Problems for this page all warm-up core CSES 1679 Course Schedule warm-up Kahn CSES 1681 Game Routes core DAG counting CSES 1680 Longest Flight Route core DAG DP CF 1385E Directing Edges core mixed graph", "w": 633, "h": [["Kahn's algorithm (queue of zero-indegree)", "kahns-algorithm-queue-of-zero-indegree"], ["DFS post-order version", "dfs-post-order-version"], ["The real prize: DP on a DAG", "the-real-prize-dp-on-a-dag"]]}, {"u": "/directed/scc/", "t": "Strongly Connected Components", "s": "Kosaraju and Tarjan in linear time, why the second pass needs the transpose, and the condensation as a working object.", "c": "Directed Graphs", "k": "SCC condensation 2-SAT core", "b": "Definition A strongly connected component is a maximal set C ⊆ V such that every u,v ∈ C satisfy u ⇝ v and v ⇝ u . Maximality is what makes them a partition: if two vertex sets are mutually reachable into each other, they are one component. Condensing each component into a single vertex gives the condensation G^SCC — always a DAG ( Orientations, In/Out-Degree, Strong vs Weak ). The whole game is computing it in O(n+m) . # Kosaraju: two DFS passes DFS on G ; record finish times . DFS on G^mathsf T (transpose), visiting vertices in decreasing finish time; each DFS tree is one SCC. cpp kosaraju.cpp Copy 1 2 3 4 5 6 7 8 9 int n ; vector < vector < int >> g , gt ; vector < char > used ( n ); vector < int > order , comp ( n , - 1 ); void dfs1 ( int u ) { used [ u ] = 1 ; for ( int v : g [ u ]) if (! used [ v ]) dfs1 ( v ); order . push_back ( u ); } void dfs2 ( int u , int c ) { comp [ u ] = c ; for ( int v : gt [ u ]) if ( comp [ v ] == - 1 ) dfs2 ( v , c ); } void scc () { for ( int i = 0 ; i < n ; i ++) if (! used [ i ]) dfs1 ( i ); // pass 1 on G reverse ( order . begin (), order . end ()); for ( int u : order ) if ( comp [ u ] == - 1 ) dfs2 ( u , nxt_comp ++); // pass 2 on G^T } Theorem Correctness Ordering by decreasing finish time visits the SCCs in a topological order of the condensation — sources first. Hence in G^mathsf T the first unvisited DFS from a source component S cannot reach any other unvisited component, so it collects exactly S . Proof Take two distinct components A, B with an edge A → B in the condensation. No path B ⇝ A exists (else they'd be one component). Claim: finish(A) > finish(B) , where finish(X) is the maximum finish time over X . Case 1 — DFS enters A first: it then reaches all of A and everything A reaches, including B ; so B finishes inside A 's window, strictly earlier than A 's exit. Case 2 — DFS enters B first: B 's whole search finishes without reaching A (no path B ⇝ A ), so B finishes before A even starts. In the condensation, therefore, decreasing-finish order is a valid topological order. In G^mathsf T every inter-component edge points backwards along that order, so a search starting at a source of G^mathsf T (= source component of G in topological order) has its out-edges already assigned. ∎ # Tarjan: one pass, no transpose Maintain a stack of \"still open\" vertices plus low[u] = smallest tin reachable from u 's DFS subtree through open vertices. When low[u] = tin[u] , pop the stack down to u : that is an SCC. cpp tarjan-scc.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int timer = 0 , ncomp = 0 ; vector < int > tin ( n , - 1 ), low ( n ), st , comp ( n , - 1 ); vector < char > on_st ( n ); void dfs ( int u ) { tin [ u ] = low [ u ] = timer ++; st . push_back ( u ); on_st [ u ] = 1 ; for ( int v : g [ u ]) { if ( tin [ v ] == - 1 ) { dfs ( v ); low [ u ] = min ( low [ u ], low [ v ]); } else if ( on_st [ v ]) low [ u ] = min ( low [ u ], tin [ v ]); // tin, not low — the classic mistake } if ( low [ u ] == tin [ u ]) { while ( true ) { int x = st . back (); st . pop_back (); on_st [ x ] = 0 ; comp [ x ] = ncomp ; if ( x == u ) break ; } ncomp ++; } } Kosaraju Tarjan passes 2 1 extra memory the transpose G^mathsf T stack + on_st code you can re-derive under stress yes — the proof is a case split needs the low-link invariant gives components in topological order yes (naturally, reversed) yes, in reverse topological order iterative-friendly very moderately Tip Pick by what else you need If you also need bridges/articulation points ( Connectivity, Bridges, Articulation Points ) you already have low-link machinery — use Tarjan. If you need the transpose anyway (e.g. for reachability or \"mother vertex\" arguments) Kosaraju's second DFS gives it for free. # Using the condensation Once you have comp[] , rebuild a DAG on component indices and run any DAG algorithm on it: cpp condense.cpp Copy 1 2 3 4 set < pair < int , int >> dag_edges ; for ( auto [ u , v ] : edges ) if ( comp [ u ] != comp [ v ])", "w": 867, "h": [["Kosaraju: two DFS passes", "kosaraju-two-dfs-passes"], ["Tarjan: one pass, no transpose", "tarjan-one-pass-no-transpose"], ["Using the condensation", "using-the-condensation"]]}, {"u": "/directed/cycles/", "t": "Cycles: Detection, Extraction, Feedback Sets", "s": "Find one cycle in linear time, find the shortest one, and know what \"remove the fewest vertices to make it a DAG\" costs.", "c": "Directed Graphs", "k": "cycles DFS NP-complete core", "b": "Definition Does a cycle exist? O(n+m) — DFS back edge, or Kahn's leftovers. Output one cycle? O(n+m) — keep parents, walk back. Shortest cycle (girth)? O(nm) — BFS from every vertex; O(n^ω) for dense unweighted. Longest cycle / Hamilton? NP-complete ( Held–Karp: Hamilton in O(2ⁿn²) for O(2^n n^2) ). Fewest vertices hitting all cycles (feedback vertex set)? NP-hard, FPT, O(4^k k n m) by bounded search tree. # Extracting a cycle from DFS cpp find-cycle.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector < int > col ( n ), par ( n , - 1 ), cycle ; bool dfs ( int u ) { col [ u ] = 1 ; for ( int v : g [ u ]) { if ( col [ v ] == 1 ) { // back edge u -> v cycle . push_back ( u ); for ( int x = par [ u ]; x != v && x != - 1 ; x = par [ x ]) cycle . push_back ( x ); cycle . push_back ( v ); return true ; } if (! col [ v ]) { par [ v ] = u ; if ( dfs ( v )) return true ; } } col [ u ] = 2 ; return false ; } The invariant that makes this correct: col[u] == 1 means \" u is on the current recursion path\", so the path par -chain from u back to v plus the edge u → v is a directed cycle ( Depth-First Search , edge classification). # Shortest cycle through BFS For unweighted digraphs, run BFS from each s and check every edge u → v where v is an ancestor-side vertex: the answer is min(dist[u] + 1) over edges closing a loop to s ; total O(nm) . For undirected girth, BFS with parent-tracking from each vertex finds it in the same time (careful: parallel edges and self-loops need their own check, since the \"parent exclusion\" hides them). Example Shortest cycle in an unweighted undirected graph, one BFS per vertex Stop expanding when you meet an already-visited vertex that is not your parent: candidate length = d[u] + d[v] + 1 . With n ≤ 2000 that is 4 × 10^6 per… O(nm) total, and it also gives the odd girth if you keep two copies of each vertex (parity-layered graph) — the standard trick for \"shortest odd cycle\". # Feedback: what \"almost a DAG\" buys you Key idea Small feedback set = DP on a DAG with a bag If k vertices hit every cycle, the remaining graph is a DAG. Then many NP-hard problems become O(2^k · poly(n)) : enumerate the state of the k special vertices, DP along the DAG order for the rest. Recognising this pattern — \"the input is a DAG plus a few extra edges\" — is a whole solution, not a hint. Theorem DAG + one edge Adding a single edge xy to a DAG creates exactly the cycles through xy : the number of new cycles is the number of paths y ⇝ x (computable by one DAG DP, DAGs and Topological Order ). Proof Every new cycle must use the new edge; removing xy from it leaves a directed path from y to x in the original DAG, and the correspondence is bijective. ∎ That one-line argument is the core of \"count cycles after adding edges\" problems and of the incremental-DAG trick used in transitive-closure updates. Problems for this page all core hard CSES 1678 Round Trip II core directed cycle output CSES 1757 Course Schedule II core print the cycle CF 977E Cyclic Components core which components are pure cycles CSES 2138 Reachable Nodes hard DAG reachability, bitsets", "w": 584, "h": [["Extracting a cycle from DFS", "extracting-a-cycle-from-dfs"], ["Shortest cycle through BFS", "shortest-cycle-through-bfs"], ["Feedback: what \"almost a DAG\" buys you", "feedback-what-almost-a-dag-buys-you"]]}, {"u": "/directed/tournament/", "t": "Tournaments", "s": "Every pair plays once: scores, the king chicken theorem, and the Hamiltonian path you get for free.", "c": "Directed Graphs", "k": "directed existence olympiad hard", "b": "A tournament on n vertices orients each of the C(n, 2) pairs exactly one way — a \"round robin\" where every pair plays and there are no draws. Instant facts m = C(n, 2) , and ∑_v deg^+(v) = C(n, 2) . deg^+(v) + deg^-(v) = n-1 for every v : out-degree determines in-degree. The score sequence is the multiset of out-degrees; Landau's theorem says s_1 ≤ … ≤ s_n is a score sequence ⇔ ∑_i ≤ k s_i ≥ C(k, 2) for all k , with equality at k = n . A tournament is transitive ( a → b → c ⇒ a → c ) ⇔ it has no directed 3-cycle ⇔ its score sequence is 0,1,…,n-1 . Theorem Every tournament has a Hamiltonian path There is an ordering v_1, …, v_n with v_i → v_i+1 for all i . Proof Induction. Insert a new vertex x into an existing Hamilton path v_1 → … → v_n . If v_1 arrow x , prepend; if v_n → x , append. Otherwise there is an index with v_i → x → v_i+1 : such an i exists because the predicate \" v_j → x \" is true at j=1 and false at j=n , so it flips somewhere. Splice x in at that i . ∎ The proof also gives the algorithm: binary search for the flip point, insert. O(n log n) per vertex on an adjacency matrix, or O(n) total with the \"score insertion sort\" — and note the path is not necessarily unique. Theorem King chicken (every vertex of max out-degree is a 2-king) In any tournament, a vertex c with maximum out-degree reaches every other vertex by a path of length at most 2. Proof Suppose some u has u → c and no edge c → u with a 2-hop, i.e. c → u is false and for all w with c → w we have u → w . Then every out-neighbour of c is also an out-neighbour of u , plus u → c itself, so deg^+(u) ≥ deg^+(c) + 1 — contradicting maximality of c . ∎ Example Contest-shaped use \"Given results of a round robin, find a vertex that beat-or-was-beaten-by-everyone within two steps\" — one pass for max out-degree, no search. And \"can the n players be ranked so each beat the next?\" — always yes, by the Hamiltonian path, which makes it a proof problem, not a search problem. # Strong tournaments Theorem Rédei / Camion A tournament has a Hamiltonian cycle ⇔ it is strongly connected. Proof ⇒ clear. ⇐ Take a longest cycle C . If C misses some vertex x : since the tournament is strong, there are u, v ∈ C with u → x → v (otherwise the sets {y ∈ C : y → x} and {x → y} would separate x from C ). Because C is a directed cycle, walking around it must cross from the second set to the first; the crossing pair gives u → x → v with v immediately before u on C — insert x , contradicting maximality. ∎ G A A D D A->D C C D->C B B B->A B->D C->B E E C->E E->B F F F->D F->C Figure 1 A small oriented graph: not every digraph is a tournament (some pairs play both ways here), but the same 'walk around the cycle' argument drives the strong case. Exercise Prove it yourself Show that a tournament is transitive ⇔ it has no directed 3-cycle. (Hint: induction on n using the vertex of out-degree 0.) Prove that in a regular tournament ( n odd, all out-degrees (n-1)/2 ) every vertex lies on a Hamiltonian cycle. Landau's inequality: prove the \"only if\" direction by noting that the k smallest scores must beat each other somewhere.", "w": 634, "h": [["Strong tournaments", "strong-tournaments"]]}, {"u": "/directed/functional/", "t": "Functional and Permutation Graphs", "s": "One out-edge per vertex: cycles with trees hanging off them, and the binary-lifting that navigates them.", "c": "Directed Graphs", "k": "functional graph lifting cycles core", "b": "A functional graph is a digraph where every vertex has out-degree exactly 1: f: V → V . A permutation graph additionally has in-degree 1 (so f is a bijection). These are not curiosities — \"each node points to its parent\", \"each planet has exactly one outgoing teleporter\", \"each cell moves to the next cell\" are all functional graphs, and they are the reason binary lifting is in every template. Theorem Structure Every weakly connected component of a functional graph is exactly one directed cycle with rooted in-trees hanging off its vertices (edges pointing toward the cycle). Proof Start anywhere and iterate f : after at most n+1 steps a vertex repeats, giving a closed walk x, f(x), …, f^k(x) = x ; minimality of the first repeat makes these k vertices distinct, so they form a cycle C . Every vertex reaches C (same argument), and out-degree 1 means once you are on C you never leave. Contract C to a root: the remaining edges give each vertex one parent, so each attached part is a tree oriented toward the root. ∎ For a permutation, in-degree 1 too, so the \"hanging trees\" are absent: a permutation graph is a disjoint union of directed cycles. That single sentence is why permutation problems are cycle problems. # Three tasks, three linear algorithms Every task below is linear, and the first one unlocks the other two. cpp functional-cycles.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > indeg ( n ); for ( int u = 0 ; u < n ; u ++) indeg [ f [ u ]]++; queue < int > q ; for ( int i = 0 ; i < n ; i ++) if (! indeg [ i ]) q . push ( i ); vector < char > cyc ( n , 1 ); while (! q . empty ()) { int u = q . front (); q . pop (); cyc [ u ] = 0 ; if (-- indeg [ f [ u ]] == 0 ) q . push ( f [ u ]); } // cyc[u] == 1 for exactly the vertices on cycles; walk them to get the lengths The other two questions reuse the peel: depth to the cycle (reverse the edges, BFS outward from the cycle vertices — gives both the distance and the identity of the cycle you fall into) and reachability after k steps (lifting, below). cpp depth-to-cycle.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 vector < vector < int >> rad ( n ); // reversed edges for ( int u = 0 ; u < n ; u ++) if (! cyc [ u ]) rad [ f [ u ]]. push_back ( u ); queue < int > qq ; vector < int > dep ( n , 0 ), cid ( n , - 1 ); for ( int v = 0 ; v < n ; v ++) if ( cyc [ v ] && cid [ v ] == - 1 ) { int c = nxt_cid ++, len = 0 , x = v ; do { cid [ x ] = c ; dep [ x ] = 0 ; qq . push ( x ); x = f [ x ]; len ++; } while ( x != v ); while (! qq . empty ()) { // grow trees off the cycle int u = qq . front (); qq . pop (); for ( int w : rad [ u ]) { cid [ w ] = c ; dep [ w ] = dep [ u ] + 1 ; qq . push ( w ); } } cycle_len [ c ] = len ; } # Navigating with lifting \"Where am I after k steps?\" is the same jump-pointer idea as LCA by Binary Lifting : up[v][j] = f^2 j (v) . cpp jump.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 const int LOG = 20 ; // 2^20 > 10^6 steps vector < array < int , LOG >> up ( n ); for ( int v = 0 ; v < n ; v ++) up [ v ][ 0 ] = f [ v ]; for ( int j = 1 ; j < LOG ; j ++) for ( int v = 0 ; v < n ; v ++) up [ v ][ j ] = up [ up [ v ][ j - 1 ]][ j - 1 ]; int jump ( int v , int k ) { for ( int j = 0 ; j < LOG ; j ++) if ( k >> j & 1 ) v = up [ v ][ j ]; return v ; } // pre O(n log n), query O(log n); cycle length L => reduce k mod L for huge k Note Why the modulus is legal Once you reach a cycle of length L , f^k(v) depends on k mod L . So k ≤ 10^18 is not a big-integer problem: find the entry point, the pre-period length, and L — all with the peel above or Floyd's tortoise-and-hare in O(μ + λ) time and O(1) memory. G A 1 B 2 A->B C 3 B->C C->A D 4 E 5 D->E E->D F 6 F->F Figure 1 A permutation on 12 elements: three disjoint directed cycles, which is the entire structure — decomposing into these is O(n) and answers most permutation questions. Example Inverting a permutation Write the cycles; the inverse is each cycle reversed. To get f^k for huge k as a permutation (not a query), rotate each cycle by k mod L : ", "w": 773, "h": [["Three tasks, three linear algorithms", "three-tasks-three-linear-algorithms"], ["Navigating with lifting", "navigating-with-lifting"]]}, {"u": "/directed/games/", "t": "Games on Directed Graphs", "s": "Positions, moves, and the winning/losing labelling that solves every impartial finite game.", "c": "Directed Graphs", "k": "games DP retrograde hard", "b": "A finite two-player game with perfect information is a graph: vertices are positions, edges are legal moves, and the player unable to move loses (normal play). The whole theory then fits in one labelling rule. Definition On a DAG of positions, define W(v) = (true if amp; if ∃ u : v → u with W(u) = false; false if amp; otherwise) i.e. v is winning iff you can move to a losing position; a terminal vertex (no moves) is losing. Theorem Correctness and the strategy The labelling is well defined on a DAG, W(v) is true exactly when the player to move at v has a forced win, and along a winning vertex the strategy \"move to a losing successor\" is closed: every reply lands in a winning-for-you position again. Proof Induct on the height of v in the DAG. If some successor is losing, moving there hands your opponent a position where every move goes to a winning position — by induction they lose. Conversely, if all successors are winning, every move you make gives the opponent a forced win, so you lose. Termination is guaranteed because the graph is acyclic: the token cannot revisit a position. ∎ cpp win-lose.cpp Copy 1 2 3 4 5 // process in reverse topological order: O(n + m) for ( int u : reverse_topo_order ()) { win [ u ] = false ; for ( int v : g [ u ]) if (! win [ v ]) { win [ u ] = true ; move_to [ u ] = v ; break ; } } Note Graphs with cycles: three colours, not two Draws exist once cycles are allowed. Use the retrograde algorithm: mark terminal vertices **L**; iterate a queue: a vertex with an L successor becomes W ; a vertex whose all successors are W becomes L (maintain remaining[v] = count of unlabelled successors); whatever is still unlabelled when the queue empties is **D** — both players can avoid losing forever, and the unlabelled set is closed under \"there is an escape\". Same O(n+m) , one extra array. cpp retrograde.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 queue < int > q ; for ( int v = 0 ; v < n ; v ++) if ( deg [ v ] == 0 ) { state [ v ] = L ; q . push ( v ); } while (! q . empty ()) { int v = q . front (); q . pop (); for ( int p : rev [ v ]) { if ( state [ p ] != UNK ) continue ; if ( state [ v ] == L ) state [ p ] = W ; // can move to a loss else if (-- rem [ p ] == 0 ) state [ p ] = L ; // every move is a win for them if ( state [ p ] != UNK ) q . push ( p ); } } // state[v] == UNK <=> draw # Composing games: Sprague–Grundy When the position is a disjoint sum of independent subgames (several heaps, several boards), the right value is not \"win/lose\" but a number: Definition g(v) = mex{g(u) : v → u} , where mex is the smallest non-negative integer not in the set. A position is losing ⇔ g = 0 , and g of a sum is the xor of the parts (Sprague–Grundy). The reduction is why \"Nim with heaps a_1..a_k \" is answered by a1 ^ a2 ^ … : each heap is a path graph, its g value is its size, and disjointness turns xor into the composition law. Computing g on a DAG is one reverse-toposort sweep, so any game whose state space is small ( g ≤ 512 , say) is an exercise in bounding the state, not in game theory. Example One graph, three problems Board with a token, moves as in a knight's tour, no repeats → DAG of (cell, visited) is hopeless, but with repeats allowed you need the 3-colour retrograde. Several piles of stones, take 1..3 → path graph, g(n) = n mod 4 . Green Hackenbush / geography on a tree → the XOR of subtree values; on a general graph, \"Geography\" is PSPACE-complete (this is the standard example of a graph game that is not solvable by labelling). Problems for this page all warm-up core hard CSES 1730 Nim Game I warm-up xor CSES 1729 Stick Game core Sprague-Grundy CSES 2207 Grundy's Game hard mex, splitting games CSES 1099 Stair Game hard invariant", "w": 690, "h": [["Composing games: Sprague–Grundy", "composing-games-spraguegrundy"]]}, {"u": "/proofs/extremal/", "t": "The Extremal Principle", "s": "Assume a counterexample is smallest/largest/closest and let it contradict itself — the single most useful proof move in graph olympiads.", "c": "Proof Toolkit", "k": "proofs olympiad hard", "b": "Key idea The move, stated once To prove something holds for every object, pick an object where the relevant quantity is extreme (longest path, maximal matching, vertex closest to all others, the component with most edges, the smallest counterexample) and show that the extremality forces the extra structure you want — because if it were missing, you could extend, improve, or shrink, contradicting the choice. Four worked shapes, all of them used elsewhere in this book: Example 1. Longest path ends where the degrees are In a graph with minimum degree δ , a longest path P = v_0 v_1 … v_ℓ has every neighbour of v_0 on P . Hence G contains a cycle of length ≥ δ + 1 : take the far neighbour v_i of v_0 , the cycle v_0 v_1 … v_i v_0 has length i+1 ≥ δ + 1 . Where extremality was used: \"every neighbour is on P \", otherwise P extends. Example 2. Maximal matching is a 2-approximation for maximum matching A maximal matching (cannot add an edge) has size ≥ (1)/(2)|maximum| : every edge of the maximum matching touches an edge of the maximal one, and one edge of the maximal one covers at most two of them. So the greedy \"take any edge, delete its endpoints\" is a 2-approximation — and, more usefully in contests, a maximal matching of size k bounds everything: 2k is a vertex cover, and if k < n/2 some vertex is unmatched. Example 3. Minimal counterexample buys you structure Claim: every connected graph on n vertices has at least n-1 edges. Suppose a counterexample exists and choose G with m minimal among all counterexamples. No edge of G is a bridge: deleting one keeps connectivity, so G - e is a connected graph with m-1 < n-1 edges — a smaller counterexample, contradiction. Hence every edge lies on a cycle; but a graph in which every edge lies on a cycle cannot be minimal under edge deletion, and a direct check gives the same: pick a BFS tree, it already has n-1 edges, so m ≥ n-1 . Minimality did the bookkeeping; the tree argument finished it. Example 4. Extremal vertex in a proof by contradiction about distances \"Show a graph with diameter d has at most ⌊ d/2 ⌋ … \" — always start by taking a,b with dist(a,b) = d (the diameter pair ) and root everything at a . Layers from a then contain b in the last one, and any edge can only join adjacent layers, which is exactly the structure you needed. This is the same trick as BFS-level arguments ( Breadth-First Search ), so extremal + BFS is a compound move worth memorising. # Extremal algorithm design The principle is not only for existence proofs; it generates algorithms: Tree diameter : farthest-from-anything is an endpoint — extremality, twice ( Tree Diameter in Two Passes ). Greedy MST : choose the lightest safe edge; correctness is an exchange argument over an extremal (lightest) spanning tree ( Minimum Spanning Tree ). Centroid : pick the vertex minimising the largest remaining component — extremality gives the ≤ n/2 bound that makes centroid decomposition O(nlog n) ( Centroid Decomposition ). Sweep-line / sorting : Kruskal, Prim, \"process edges by weight\" are extremal choices in a loop. Watch out Maximal vs maximum Maximal = cannot be extended. Maximum = largest possible. Almost every \"greedy is a 2-approximation\" argument gives maximal , and every NP-hard optimisation problem asks for maximum . Confusing the two produces a wrong proof that looks right, which is the worst kind of bug in a written solution. Exercise Practise the move Prove every graph with average degree bar d has a subgraph of minimum degree > bar d/2 . (Delete vertices of small degree; what does the last surviving graph look like?) Prove that in any tournament, a vertex of maximum out-degree is a \"2-king\" ( Tournaments ). Prove: if ∑_v C(deg(v), 2) > C(n, 2) then G contains a 4-cycle. (Count length-2 paths between pairs — extremal in the sense of \"too many of something\".)", "w": 678, "h": [["Extremal algorithm design", "extremal-algorithm-design"]]}, {"u": "/proofs/double-count/", "t": "Double Counting and Averaging", "s": "Count one set two ways, then compare — the source of the handshaking lemma, Turán-type bounds, and most \"prove that m ≤ …\" problems.", "c": "Proof Toolkit", "k": "proofs counting hard", "b": "Key idea The move Pick a set S of pairs (vertex, edge), (vertex, path), (edge, face)… Count |S| by summing over the first coordinate, then over the second, and equate. Every inequality you can prove about one coordinate becomes an inequality about the other. # The canonical instance Theorem Handshaking, in its useful form ∑_v deg(v) = 2m . Consequently δ n ≤ 2m ≤ Δ n , and nδ ≤ 2m ≤ n(n-1) . Proof Count S = {(v,e): v is an endpoint of e} . By vertices |S| = ∑_v deg(v) ; by edges |S| = 2m . ∎ Everything below is that sentence with a different S . Example Bipartite graphs with no 4-cycle Let G be C_4 -free and bipartite with parts A,B , |A| = n . Count pairs (v, {a_1,a_2}) where v is adjacent to both a_1, a_2 ∈ A : each pair {a_1,a_2} has at most one common neighbour (two would make a 4-cycle), so |S| ≤ C(n, 2) . Each v ∈ B contributes C(deg(v), 2) , so ∑_v ∈ B C(deg(v), 2) ≤ C(n, 2) ⇒ |B| C(bar d, 2) ≲ tfrac{n^2}{2} ⇒ m = bar d |B| = O(n^3/2). Two ingredients, both reusable: convexity of C(x, 2) (Jensen) to replace the sum by the average, and \"at most one\" coming from a forbidden subgraph. Example Euler's formula, counted twice In a simple planar graph with n ≥ 3 , count edge–face incidences: each face has ≥ 3 edges, each edge borders ≤ 2 faces, so 3f ≤ 2m . With n - m + f = 2 this gives m ≤ 3n - 6 . (Full treatment: Planarity and Euler's Formula .) Example Average degree gives a short path If m > C(k+1, 2) then some component has more than k vertices of positive degree… Simpler and standard: if δ ≥ 2 a cycle exists; if m > (k-1)n/2 then G contains a path on k+1 vertices. Proof: maximal path P ; every neighbour of an endpoint lies on P ; rotating P around its start (the classical rotation argument) yields deg(v_1) + deg(v_ℓ) ≤ |P| — a double count over the rotations, and the bound follows. # Averaging as a corollary Theorem Mean between min and max δ ≤ (2m)/(n) ≤ Δ , so some vertex has degree ≤ bar d and some has degree ≥ bar d . What people then do with it greedy colouring uses it n times: every subgraph has a vertex of degree ≤ bar d , so χ ≤ ⌊ bar d_max⌋ + 1 (degeneracy order, Graph Colouring ), independent set: some vertex has ≤ bar d neighbours, so α ≥ (n)/(bar d + 1) (Caro–Wei is the weighted version: α ≥ ∑_v (1)/(deg(v)+1) ), bipartite subgraph with ≥ m/2 edges: keep one side of a random partition, take expectation, or equivalently count the incidences (v, incident edge) and always place the smaller class, K_t -free bounds: replace \"4-cycle\" by \" K_t \" in the first example and you get the extremal number machinery of Turán. Watch out Choosing S is the whole problem If your count gives an equality you cannot bound in either direction, the set was wrong. The heuristic: S must be a relation between two very different-looking things (vertices vs edges, edges vs faces, paths vs pairs), and one of the two sums must be bounded by an obvious local argument (each edge has 2 ends; each pair has ≤ 1 common neighbour). If both sums are equally complicated, double counting will not help. Problems for this page all core warm-up CSES 1136 Counting Paths core difference array on a tree CSES 1674 Subordinates warm-up count the pairs (boss, subordinate)", "w": 639, "h": [["The canonical instance", "the-canonical-instance"], ["Averaging as a corollary", "averaging-as-a-corollary"]]}, {"u": "/proofs/contracting/", "t": "Contraction, Induction and Lifting", "s": "Shrink the graph, solve the smaller one, un-shrink — with the two rules that decide whether the move is legal.", "c": "Proof Toolkit", "k": "proofs structural induction hard", "b": "Contracting an edge uv (write G/uv ) replaces u,v by a single vertex and deletes loops and parallel copies. It is the standard way to do induction on n while keeping connectivity-type properties intact — and the standard way to build a data structure (DSU, Disjoint Set Union (Union–Find) ). Key idea The two legality rules Preservation : the property you induct on must survive contraction, or you must contract something chosen to make it survive (e.g. contract an edge of a maximum matching to preserve \"has a perfect matching\" for the smaller graph). Lifting : after you have the object in G/uv you must be able to un-shrink it into G and pay at most a constant. If the answer for G is \"answer for G/uv plus maybe one\", you have a theorem; if un-shrinking can destroy the whole construction, you have a hole. Example Every bridgeless graph has an even-degree orientation Induct on m . Take a cycle C (exists: δ ≥ 2 after removing nothing… precisely: bridgeless ⇒ each edge lies on a cycle). Orient C cyclically and contract it: G/V(C) is still bridgeless (contracting cannot create a bridge out of non-bridges), has fewer edges, so by induction it has an orientation with all degrees even; combining with the cycle orientation, every vertex of G has deg^+ = deg^- (the vertices of C gained one in and one out). ∎ Note where rule 2 lives : the merged vertex's evenness follows because the cycle contributed exactly one in and one out to each of its vertices — this is the step that fails for a path instead of a cycle. Example Colouring: add an edge, colour, delete it To prove χ(G) ≤ Δ + 1 by induction on n+m , pick non-adjacent x,y with a common neighbour of minimum degree (exists when G is not complete, else Brooks handles it). Form G' = G + xy — it has the same maximum degree and χ(G) ≤ χ(G') — colour G' by induction, then delete xy . The colouring lifts unchanged, because deleting an edge never invalidates a proper colouring. That asymmetry (add edges when colouring, delete edges when studying connectivity) is the reason the same proof technique proves completely different theorems. # Contraction in algorithms Four places the same move appears as code Kruskal : each accepted edge contracts two super-vertices; find() answers \"which class is this vertex in now\". The algorithm literally walks a sequence of contractions ( Minimum Spanning Tree ). Karger's min-cut : contract random edges until two vertices remain; the remaining cut is uniform among the cuts of the original graph. O(n^2 log n) repetitions → constant success probability ( Cuts and Flows ). Biconnected/block decomposition : contract each 2-edge-connected component; what remains is a tree (the bridge tree), and answers to \"how many edges on the path\" become tree-distance queries ( Connectivity, Bridges, Articulation Points ). Treewidth / elimination : contract (or eliminate) a low-degree vertex, recurse, then lift the solution — the reason degeneracy-ordered DP works on sparse graphs. # Induction that goes the other way: lifting \"Lift\" here means: build the object for G from the object for G - v , for a vertex v you chose. Choosing which v to remove is the entire skill: Check yourself Pick the vertex that makes the smaller instance easy need a leaf? a tree always has one ( Trees: Six Definitions of One Object ) — remove it, induct, re-attach; need degree ≤ bar d ? average-degree gives one ( Double Counting and Averaging ) — this is why greedy colouring and α ≥ n/(bar d+1) work; need \"small remaining graph\"? contract a cycle, or a maximal path; need both endpoints of the diameter? root at one and remove the other. Watch out When contraction is a trap Counting problems. \"How many spanning trees does G have?\" splits as τ(G) = τ(G-e) + τ(G/e) — a genuine recurrence, but it is exponential unless you turn it into a determinant ( The Matrix–Tree Theorem ). Similarly, counting matchings or independent sets does not survive contraction without carrying a polynomial in a variable; when you see tha", "w": 728, "h": [["Contraction in algorithms", "contraction-in-algorithms"], ["Induction that goes the other way: lifting", "induction-that-goes-the-other-way-lifting"]]}, {"u": "/euler/euler-existence/", "t": "Euler Tours: When They Exist", "s": "Euler's degree condition, why it is both necessary and sufficient, and the directed/undirected split.", "c": "Euler Tours and Hamilton Cycles", "k": "euler cycles existence core", "b": "An Eulerian tour is a closed walk using every edge exactly once; an Eulerian trail (or path) uses every edge exactly once but need not be closed. Graphs admitting them are called Eulerian / semi-Eulerian . Theorem Undirected A connected graph with at least one edge has an Eulerian tour ⇔ every vertex has even degree, an Eulerian trail from s to t ≠ s ⇔ exactly s and t have odd degree. (\"Connected\" means: connected after ignoring isolated vertices.) Proof (⇒) Each time a tour passes through v it consumes one in-arrival and one departure, contributing 2 to deg(v) ; a closed tour starts and ends at the same vertex, so even the start vertex gets 2 per visit plus a final closing. Hence all degrees even. For an open trail, the two endpoints are entered/left one extra time each — exactly the odd ones. (⇐) Induct on m . All degrees even ⇒ no vertex has degree 1, so some cycle C exists (walk until you repeat). Remove C : every degree stays even, and each component of G - C has fewer edges, so by induction each has an Euler tour. Splice: walk C , and whenever you enter a vertex of a component, detour along that component's tour and come back. All edges used exactly once. ∎ Theorem Directed A digraph has an Eulerian circuit ⇔ (a) every vertex with nonzero degree lies in one strongly connected component of the underlying graph, and (b) deg^+(v) = deg^-(v) for all v . An Eulerian path s ⇝ t exists ⇔ deg^+(s) = deg^-(s)+1 , deg^-(t) = deg^+(t)+1 , all others balanced, plus the same connectivity condition. Proof Balance is necessary by the same pass-through argument (each visit uses one in-edge and one out-edge). For sufficiency, the balanced condition makes each weak component a union of directed cycles (take a maximal walk: it cannot get stuck except at the start, since every entry leaves an unused exit; so it closes). Then contract each cycle into one vertex — Contraction, Induction and Lifting rule 1 — and repeat: the component structure and balance are preserved, so the cycle graph of the decomposition can be spliced back together. Connectivity (a) is what lets you splice at all. ∎ Note Why condition (a) is the one people forget Two disjoint directed cycles joined by a single arc x → y : degrees are unbalanced, so (b) already fails. But four cycles arranged as two balanced pairs, each pair internally strongly connected and joined one-way, passes balance everywhere and still has no tour. Always test reachability on the underlying graph and, in the directed case, that all nonzero-degree vertices are in one SCC ( Strongly Connected Components ). G C C A A C--A C--A D D C--D B B A--B A--B A--D B--D Figure 1 Königsberg: degrees 5, 3, 3, 3 — four odd vertices, so neither a tour nor a trail exists. The whole subject starts from that one line of arithmetic. # Deciding, then counting Complexity of the Euler family decide existence: O(n + m) (degree scan + one DFS/SCC), construct: O(n+m) with Hierholzer ( Hierholzer's Linear Algorithm ), count Euler tours: #P -complete in general; for directed graphs the BEST theorem gives ∏_v (deg^+(v)-1)! · ec(G, r) where ec is the number of arborescences — an The Matrix–Tree Theorem determinant, minimum number of trails covering all edges of an undirected graph: max(1, #odd/2) — one line, follows from the parity argument above. Problems for this page all core hard CSES 1691 Mail Delivery core directed Euler circuit CSES 1693 Teleporters Path core semi-Eulerian, directed CSES 2078 Eulerian Subgraphs hard subset counting", "w": 615, "h": [["Deciding, then counting", "deciding-then-counting"]]}, {"u": "/euler/hierholzer/", "t": "Hierholzer's Linear Algorithm", "s": "Build an Euler tour in O(n + m) with a stack, a pointer per vertex, and one ordering subtlety.", "c": "Euler Tours and Hamilton Cycles", "k": "euler DFS construction core", "b": "The existence proof in Euler Tours: When They Exist was constructive (\"take a cycle, splice\"), but splicing naively is quadratic. Hierholzer's insight: do the splicing implicitly — run a DFS-like walk that never reuses an edge, and emit vertices on the way back up. cpp hierholzer.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int n ; vector < vector < pair < int , int >>> g ; // (to, edge id) vector < char > used ( m ); vector < int > ptr ( n ), tour ; // ptr = \"next unused edge index\" bool euler ( vector < int >& out , int s , bool directed ) { vector < int > st { s }; while (! st . empty ()) { int u = st . back (); while ( ptr [ u ] < ( int ) g [ u ]. size () && used [ g [ u ][ ptr [ u ]]. second ]) ptr [ u ]++; if ( ptr [ u ] == ( int ) g [ u ]. size ()) { // u is a dead end: commit it out . push_back ( u ); st . pop_back (); } else { auto [ v , id ] = g [ u ][ ptr [ u ]++]; used [ id ] = 1 ; st . push_back ( v ); } } return ( int ) out . size () == m + 1 ; // every edge consumed? } Theorem Correctness If the vertex list produced (read in reverse) has length m+1 , it is an Eulerian tour of the component; combined with the degree condition of Euler Tours: When They Exist this decides and constructs simultaneously. Proof Each step either consumes a previously unused edge (so at most m forward steps happen) or permanently removes a vertex from the stack. When u is popped, all its edges are used: the loop above guarantees it. Hence the walk never gets \"stuck early\" at a vertex with unused edges, which is precisely the property that makes splicing unnecessary: whenever the recursion returns to a vertex, it has already absorbed the entire subcycle found inside. Reversing the emission order undoes the DFS post-order, and a post-order of a nested-cycle walk reversed is the cycle-splicing order — the two constructions produce the same tour. ∎ Note The one ordering subtlety Output must be reversed (or built with push_front ). A pure \"record u when you first arrive\" gives a walk that is not an Euler tour: it is the tour of the spine with the subcycles interleaved wrongly. Every wrong implementation of this algorithm I have seen is exactly this off-by-one in convention. # Recursive version (the one you should write) cpp hierholzer-rec.cpp Copy 1 2 3 4 5 6 7 8 9 10 void dfs ( int u ) { for ( int & i = ptr [ u ]; i < ( int ) g [ u ]. size (); ) { auto [ v , id ] = g [ u ][ i ++]; if ( used [ id ]) continue ; used [ id ] = 1 ; dfs ( v ); } tour . push_back ( u ); } // call dfs(s); then reverse(tour) Depth ≤ m , so on a \"path-like\" multigraph with m = 2 × 10^5 raise the stack limit or use the iterative form above. The for (int& i = ptr[u]; …) reference-into-array trick is the important part: without a per-vertex pointer, each recursion restarts scanning from index 0 and the algorithm silently becomes O(m^2) . Example De Bruijn / lock combinations are the same call \"Shortest string containing every length- k binary word as a substring\" is an Eulerian-path problem on the graph of (k-1) -bit states — De Bruijn Sequences . Same code, different vertex labels. Example Reconstructing a string from its k-mers Given all n substrings of length k of an unknown string, build vertices = the (k-1) -prefix/suffix, edges = the k -mers, find an Eulerian path. Uniqueness of the answer is equivalent to the path being forced at each step — a nice pair of ideas (existence test + reconstruction) in 30 lines. Checklist before trusting your output the graph must satisfy the degree conditions — assert it, don't assume, isolated vertices: exclude them from the \"connected\" test but keep them in n if the answer counts vertices, parallel edges: index edges, never vertices, when marking used , for the trail (not circuit) variant, start at the odd vertex s (or at any vertex with deg^+ > deg^- when directed), lexicographically smallest tour: run the same algorithm with ptr over a sorted adjacency list and a max-heap emission order ( std::priority_queue instead of ", "w": 761, "h": [["Recursive version (the one you should write)", "recursive-version-the-one-you-should-write"]]}, {"u": "/euler/debruijn/", "t": "De Bruijn Sequences", "s": "The shortest string containing every k-mer: an Eulerian cycle on (k−1)-bit states.", "c": "Euler Tours and Hamilton Cycles", "k": "euler strings construction hard", "b": "A de Bruijn sequence B(k, n) is a cyclic string over an alphabet of size k in which every length- n word appears exactly once as a (cyclic) substring. Its length is k^n , and it is optimal: k^n distinct windows need at least k^n positions. Key idea The reduction, in one picture Vertices = the k^n-1 words of length n-1 . For each vertex w = a_1 … a_n-1 and each letter b , add an edge w → a_2 … a_n-1 b labelled b . Every vertex then has out-degree k and in-degree k , and the graph is strongly connected (shift away any window). So it is Eulerian ( Euler Tours: When They Exist ), and an Eulerian circuit uses each edge once — each edge is exactly one length- n word (its label plus the tail of the source). Reading edge labels along the tour gives B(k,n) . cpp debruijn.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 int k , n , N ; // N = k^(n-1) vector < int > a ( k * n ), result ; vector < int > ptr ; // per-vertex next letter to try void dbg ( int v ) { // Hierholzer on implicit graph for ( int & c = ptr [ v ]; c < k ; c ++) { int nxt = ( v * k + c ) % N ; dbg ( nxt ); result . push_back ( c ); // append on the way BACK } } // result reversed (as built by the recursion) = the sequence, length k^n Theorem Why append-on-return is correct here dbg is Hierholzer's post-order emission ( Hierholzer's Linear Algorithm ): the letters recorded on unwinding are exactly the tour read in reverse, and reversing a cyclic Euler tour is a cyclic Euler tour. That is why no explicit reversal is needed when only the cyclic sequence matters. # Prefer the greedy (FKM) version There is a slicker construction whose output is the lexicographically smallest sequence: the Lyndon-word concatenation (Fredericksen–Kessler–Maiorana). Generate in DFS order all Lyndon words whose length divides n , concatenated: cpp fkm.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 int k , n ; vector < int > a ( k * n ); string seq ; void db ( int t , int p ) { // t = length, p = period if ( t > n && n % p == 0 ) for ( int i = 1 ; i <= p ; i ++) seq . push_back ( char ( '0' + a [ i ])); else { a [ t + p ] = a [ t ]; // extend periodically… db ( t + 1 , p ); for ( int j = a [ t ] + 1 ; j < k ; j ++) { a [ t ] = j ; db ( t + 1 , t ); } } } db ( 1 , 1 ); Same O(k^n) output size, constant memory beyond the array, and it needs no graph at all — a nice example of \"the graph was in your head the whole time\". What these are good for combination locks / brute force : to try all 4-digit PINs on a wheel you need 10^4 + 3 turns, not 4 · 10^4 — a de Bruijn sequence B(10,4) , linear instead of k -times linear, DNA sequencing / assembly : reads are exactly k -mers, and the overlap graph is a de Bruijn graph — real genome assemblers (SPAdes and descendants) build and simplify de Bruijn graphs, and \"contig = Eulerian path after compressing degree-2 chains\", testing : any k^n -long test stream hits every length- n configuration exactly once (registers, sequences, card tricks), lower bounds : the argument \"windows ↔ edges\" proves the length bound and the construction at the same time. G A 000 C 001 A->C 1 B 010 F 100 B->F 0 G 101 B->G 1 C->B 0 D 011 C->D 1 E 110 D->E 0 H 111 D->H 1 E->F 0 E->G 1 F->A 0 F->C 1 G->B 0 G->D 1 H->E 0 X->Y W->Z M 1 N 0 Figure 1 The de Bruijn graph for B(2,3) : four vertices (2-bit states), eight edges (3-bit words). Every vertex has in-degree = out-degree = 2, so an Euler circuit exists and is the sequence. Note Line version vs cycle version A linear string containing all k^n words needs k^n + n - 1 characters (take the cyclic sequence and repeat its first n-1 symbols at the end). Problems that ask for \"a string\" rather than \"a cyclic string\" are asking for that +n-1 ; forgetting it is a wrong answer, not an off-by-one style issue. Problems for this page all core warm-up CSES 1692 De Bruijn Sequence core exact output checker CSES 2205 Gray Code warm-up Hamiltonian cycle on Q_n , same framing", "w": 673, "h": [["Prefer the greedy (FKM) version", "prefer-the-greedy-fkm-version"]]}, {"u": "/euler/hamilton-theorems/", "t": "When Hamiltonian Cycles Must Exist", "s": "Dirac, Ore and Pósa — sufficient degree conditions, and the rotation-extend argument behind them.", "c": "Euler Tours and Hamilton Cycles", "k": "hamilton existence olympiad olympiad", "b": "Whereas Euler's condition is exact and cheap, Hamiltonicity is NP-complete ( The NP-complete Graph Problems Worth Knowing ). What survives is a family of sufficient conditions of the same shape — \"enough edges forces the cycle\" — and all of them are proved by one argument. Theorem Dirac (1952) If n ≥ 3 and δ(G) ≥ n/2 , then G has a Hamiltonian cycle. Proof First, G is connected: two components would have to contain a vertex of degree ≤ n/2 - 1 by the pigeonhole principle, contradicting δ ≥ n/2 . Let P = v_1 v_2 … v_ℓ be a longest path (extremal choice, The Extremal Principle ). Maximality puts every neighbour of v_1 and of v_ℓ on P — otherwise P extends. Define S = {i ≤ ℓ-1 : v_1 v_i+1 ∈ E}, T = {i ≤ ℓ-1 : v_ℓ v_i ∈ E}. Claim 1. i ∈ S ∩ T gives a cycle through all vertices of P : re-cut the path at v_1 v_i+1 to get v_i+1 v_i … v_1 v_i+2 … v_ℓ , then close it with v_ℓ v_i . Claim 2. ℓ = n . If not, the cycle from Claim 1 (or the edge v_1 v_ℓ , which likewise closes P into a cycle on V(P) ) misses some vertex; by connectedness some edge joins V(P) to a vertex outside, and cutting the cycle there yields a path on ℓ+1 vertices — contradicting the choice of P . Conclusion. Suppose no Hamiltonian cycle exists. Then v_1 v_ℓ ∉ E (else P closes), so deg(v_1) + deg(v_ℓ) ≥ n by δ ≥ n/2 , i.e. |S| + |T| ≥ n . But S, T ⊆ {1, …, n-1} are disjoint by Claim 1, so |S| + |T| ≤ n-1 . Contradiction — and the case i ∈ S ∩ T we excluded in Claim 1 is the Hamiltonian cycle. ∎ Theorem Ore (1960) — the stronger, easier-to-apply form If n ≥ 3 and deg(u) + deg(v) ≥ n for every non-adjacent pair u,v , then G is Hamiltonian. Dirac is the special case. The proof above is already the Ore proof — the only place δ ≥ n/2 was used is the bound |S| + |T| = deg(v_1) + deg(v_ℓ) ≥ n , and Ore assumes exactly that for the (necessarily non-adjacent) pair {v_1, v_ℓ} . Theorem Pósa (and the closure theorem) If for every k < n/2 at most k vertices have degree ≤ k , then G is Hamiltonian. Bondy–Chvátal: G is Hamiltonian ⇔ G + uv is, for non-adjacent u,v with deg(u)+deg(v) ≥ n . So \"repeatedly add all such edges until nothing changes; then test Dirac\" is an O(n^3) decision procedure for the closure, and the closure is unique. Note Necessity: how sharp are these? δ ≥ n/2 cannot be weakened: two copies of K_⌈ n/2 ⌉ - 1 joined by a single edge has δ = ⌈ n/2 ⌉ - 1 and no Hamiltonian cycle (the joining edge is a bridge). This \"two cliques glued at a cut edge\" and its k -clique-chain relatives are the standard counterexample generators — build them whenever you doubt a bound. Other useful guarantees every 4-connected planar graph is Hamiltonian (Tutte); 3-connected planar is not (Herschel graph, 11 vertices), every tournament with a Hamilton cycle ⇔ strongly connected ( Tournaments ), every connected vertex-transitive graph is conjectured Hamiltonian (open in general, verified for many families), line graphs: every 2-connected graph H has L(H) Hamiltonian unless H ≅ K_3 -type exceptions (Harary–Nash-Williams), G with κ(G) ≥ independence number α(G) is Hamiltonian (Chvátal–Erdős) — a connectivity version rather than a degree version, and often easier to verify. G A v 1 B v 2 A--B E v i A--E C v 3 B--C D v i-1 C--D D--E G v n D--G F v n-1 E--F F--G Figure 1 The rotation picture: v_1 reaches v_i+1 , v_ℓ reaches v_i , and re-cutting the path at that pair turns a longest path into a cycle. Exercise Prove these three (each is the same argument with one twist) δ ≥ n/2 ⇒ G is Hamiltonian connected ? — strengthen it: G has a cycle through any prescribed vertex. Every graph with δ ≥ 3 has a cycle of length at least δ + 1 , and this is tight (show K_δ+1 joined appropriately). Chvátal–Erdős: longest path has at least min(n, 2δ) vertices — deduce Dirac.", "w": 744, "h": []}, {"u": "/euler/hamilton-dp/", "t": "Held–Karp: Hamilton in O(2ⁿn²)", "s": "Exponential, but the right exponential — bitmask DP for Hamiltonian paths, cycles and the travelling salesman.", "c": "Euler Tours and Hamilton Cycles", "k": "DP bitmask TSP hard", "b": "Deciding Hamiltonicity is NP-complete, so we settle for exponential — and the exact exponent is the whole sport. The classic is O(2^n n^2) time, O(2^n n) memory, via \"which subsets, ending where?\". Definition dp[M][v] = \"true ⇔ there is a path that visits exactly the vertices of M and ends at v , with v ∈ M \". Transition: dp[M][v] = vee_{u ∈ M ∖ {v}, uv ∈ E} dp[M ∖ {v}][u] . Answer: vee_u dp[V][u] ∧ (u ∼ s) for a cycle through a fixed start s ; the path version is vee_u,v dp[V][u] (any endpoints). cpp hamilton.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const int MAXN = 20 ; bool dp [ 1 << MAXN ][ MAXN ]; int main () { int n , m ; cin >> n >> m ; vector < int > adj ( n ); // bitmask of neighbours for ( int i = 0 ; i < m ; i ++) { int u , v ; cin >> u >> v ; -- u ; -- v ; adj [ u ] |= 1 << v ; adj [ v ] |= 1 << u ; } for ( int s = 0 ; s < n ; s ++) dp [ 1 << s ][ s ] = 1 ; for ( int M = 1 ; M < ( 1 << n ); M ++) for ( int v = 0 ; v < n ; v ++) if ( dp [ M ][ v ] && ( M >> v & 1 )) { int rest = adj [ v ] & ~ M ; // only unvisited neighbours while ( rest ) { int u = __builtin_ctz ( rest ); rest &= rest - 1 ; dp [ M | 1 << u ][ u ] = 1 ; } } int all = ( 1 << n ) - 1 ; for ( int v = 0 ; v < n ; v ++) if ( dp [ all ][ v ] && ( adj [ v ] & 1 )) { cout << \"YES\\n\" ; return 0 ; } cout << \"NO\\n\" ; } Note Why the inner loop is adj[v] & ~M Iterating u over all n vertices costs 2^n n^2 ; iterating only over unvisited neighbours via ctz bit-extraction costs ∑_M ∑_v ∈ M deg_bar M(v) , which is bounded by 2^n m / 2 in the worst case but typically much smaller — and for dense graphs, the bit-OR form reach[M] |= ... reduces it to O(2^n · n / 64) per step with a bitset . # TSP, i.e. the weighted version Same state, but keep the cost and take min: dp[M][v] = min_u dp[M ∖ {v}][u] + w(u,v) . cpp tsp.cpp Copy 1 2 3 4 5 6 7 8 int dp [ 1 << 20 ][ 20 ]; // 4 * 10^6 ints: fits for ( int v = 1 ; v < n ; v ++) dp [ 1 ][ v ] = w ( 0 , v ); // start fixed at 0 for ( int M = 1 ; M < 1 << ( n - 1 ); M ++) for ( int v = 1 ; v < n ; v ++) if ( M >> ( v - 1 ) & 1 ) for ( int u = 1 ; u < n ; u ++) if (!( M >> ( u - 1 ) & 1 )) dp [ M | 1 << ( u - 1 )][ u ] = min ( dp [ M | 1 << ( u - 1 )][ u ], dp [ M ][ v ] + w ( v , u )); int ans = INF ; for ( int v = 1 ; v < n ; v ++) ans = min ( ans , dp [( 1 << ( n - 1 )) - 1 ][ v ] + w ( v , 0 )); What the numbers really allow n ≤ 20 : 2^20 · 400 ≈ 4 · 10^8 naive inner steps — tight, so use the bit tricks or drop to n ≤ 18 ; memory 2^n · n : at n = 20 that is 4 × 10^6 cells = 16 MB for int , 32 MB for long long — check the limit before choosing the state, n ≤ 25 : meet in the middle on paths, or Held–Karp with unordered -style pruning; metric TSP: Christofides gives (3)/(2) -approx in poly time (MST + min-weight perfect matching on odd-degree vertices — which is why General Matching and Blossoms matters), and the path version is even a bit better; for general weights no 2-ε approximation exists unless P = NP. Example Counting Hamiltonian cycles, not just finding one Replace bool by long long mod M and vee by + . The count mod 2 is the interesting theoretical object (it relates to the determinant of the adjacency matrix over 𝔽_2 — The Matrix–Tree Theorem has the parallel argument for spanning trees). Watch out Three state-design mistakes Forgetting to fix a start vertex for the cycle version: then every cycle is counted n times (or 2n if you care about direction) and the boolean version stays correct but the count is wrong. Transition order: iterate masks by increasing popcount, or at least ensure M ∖ {v} < M numerically (true for bitmasks, so a plain increasing loop is fine — but do not \"optimise\" by iterating vertices in the outer loop unless you keep that invariant). Using dp[M][v] = \"visits at least M \". Half-open vs exact-subset semantics changes the answer. Problems for this page all core CSES 1690 Hamiltonian Flights core bitmask DP, directed CSES 1136 Counting Paths core paths on a tree, contra", "w": 867, "h": [["TSP, i.e. the weighted version", "tsp-ie-the-weighted-version"]]}, {"u": "/euler/knight/", "t": "Knight's Tour and Warnsdorff's Rule", "s": "A Hamiltonian-cycle problem that is nevertheless constructive — closed tours on every board, and the heuristic that finds one instantly.", "c": "Euler Tours and Hamilton Cycles", "k": "hamilton heuristic construction hard", "b": "The knight's tour asks for a path on which a knight visits every square of a b × b board exactly once — a Hamiltonian path in the knight graph, hence in principle hopeless. In practice boards are structured, and there are two clean facts plus one heuristic. Theorem Existence (Schwenk, 1991) An m × n board with m ≤ n has a closed knight's tour unless at least one of the following holds: m and n are both odd — the two colour classes then differ by one square; m ∈ {1, 2, 4} ; m = 3 and n ∈ {4, 6, 8} . So 3 × 14 has a closed tour but 3 × 8 does not, and 5 × 5 has open tours only. The parity obstruction is the instructive one: the knight graph is bipartite (each move changes square colour), so a closed tour needs equally many black and white squares, i.e. an even number of cells. An open tour on an odd-area board must start and end on the majority colour. Two more obstructions worth knowing m = 1, 2 : the knight graph is disconnected / has vertices of degree < 2 — no tour, and the degree argument is the general \"if a vertex has ≤ 1 move it must be an endpoint\" test, m = 4 : cut arguments (a 4 × n board splits into two halves the knight crosses in balanced pairs) — the reason m=4 is excluded for closed tours while open ones do exist, 3 × 4, 3 × 6, 3 × 8 : no closed tour although parity is fine — the standard small exceptions; verify by hand once and you will never re-derive them under time pressure. # Warnsdorff's rule: the heuristic that is basically the algorithm From the current square, move to the neighbour with the fewest onward moves . cpp warnsdorff.cpp Copy 1 2 3 4 5 6 7 8 9 10 const int dx [ 8 ] = { 1 , 2 , 1 , - 1 , - 2 , - 1 , 1 , 2 }, dy [ 8 ] = { 2 , 1 , - 1 , - 2 , - 1 , 1 , 2 , 1 }; int deg ( int x , int y , bool used [ 8 ][ 8 ]) { int c = 0 ; for ( int k = 0 ; k < 8 ; k ++) { int a = x + dx [ k ], b = y + dy [ k ]; if ( 0 <= a && a < N && 0 <= b && b < N && ! used [ a ][ b ]) c ++; } return c ; } // O(N^2) squares, each with 8*8 lookahead -> fine up to huge boards It completes a full tour on square boards 5 ≤ N ≤ 76 for every starting square (verified computationally; a proof for all N is open — a nice example of a heuristic with an empirical track record far better than its theory). Tie-breaking matters: prefer the neighbour whose own onward-move count is minimal, or break ties by the largest distance from the board centre; either variant fixes almost all failures. Note Why greedy works here at all Squares in a corner have degree 2, edges degree 3–4, the centre degree 8. The constraint \"every square must be entered and left\" is tightest at the corners, and Warnsdorff's rule is precisely \"visit the constrained squares while they are still feasible\" — the same reason the arc-consistency heuristic works in constraint programming. Hamiltonicity is NP-complete on general graphs but the knight graph is nowhere near general: bounded degree, planar-ish, and highly symmetric. # When you must produce a tour (construction, not search) For N ≥ 5 a divide-and-conquer construction is standard: tile the board into 5 × 6 and 6 × 5 blocks (each of which has a closed tour with an exit edge), splice them along shared edges, and handle the leftover strip by symmetry. Concretely, in a contest the reliable recipe is: 1 check the three obstructions (parity, m ≤ 2 , m = 4 ); 2 if the answer is \"yes\", run Warnsdorff with O(8) lookahead from a corner — this finishes in microseconds for N ≤ 500 on an m × n board with the same tie-break, 3 if the judge demands determinism, use the block construction, since Warnsdorff is empirically (not provably) complete, 4 for \"count the tours\" on tiny boards ( N ≤ 6 ), do plain DFS with bitsets and a connectivity prune — the prune that matters: abort when the unvisited squares are disconnected (check with one BFS). Watch out The connectivity prune is not optional Naive DFS counts tours on a 5 × 5 board in tens of seconds; with \"abort if the unvisited graph becomes disconnected, or if any unvisited square dr", "w": 812, "h": [["Warnsdorff's rule: the heuristic that is basically the algorithm", "warnsdorffs-rule-the-heuristic-that-is-basically-the-algorit"], ["When you must produce a tour (construction, not search)", "when-you-must-produce-a-tour-construction-not-search"]]}, {"u": "/shortest/dijkstra/", "t": "Dijkstra's Algorithm", "s": "The greedy that works because distances only ever grow — with the proof, the four implementations, and the shapes of problem that are one Dijkstra away.", "c": "Shortest Paths", "k": "shortest-path greedy heap core", "b": "Given edge weights w: E → ℝ_≥ 0 and a source s , compute dist(s, v) for every vertex v — the length of the cheapest walk, where non-negative weights make \"cheapest walk\" and \"cheapest path\" the same number. The algorithm is greedy, the proof is two lines of bookkeeping, and the variants (state augmentation) are what contests actually test. # The algorithm in five lines of intent Keep a tentative distance d[v] for every vertex, initially ∞ except d[s] = 0 . Extract the unprocessed vertex u with minimum d[u] . u is now settled : d[u] = dist(s,u) . Relax every edge uv : if d[u] + w(u,v) < d[v] , write the better value and push (d[v], v) . Repeat. cpp dijkstra-real.cpp Copy 1 2 3 4 5 6 7 8 9 10 while (! pq . empty ()) { auto [ du , u ] = pq . top (); pq . pop (); if ( du != d [ u ]) continue ; // stale entry: lazy deletion if ( done [ u ]) continue ; done [ u ] = 1 ; for ( auto [ v , w ] : g [ u ]) if ( d [ u ] + w < d [ v ]) { d [ v ] = d [ u ] + w ; pq . emplace ( d [ v ], v ); } } Theorem Correctness If all weights are non-negative, then when u is extracted with d[u] ≠ ∞ , d[u] = dist(s,u) . Proof Induct on the extraction order. Invariant: d[v] ≥ dist(s,v) always — true initially ( ∞ ), and preserved because every update sets d[v] to the length of an actual path ( s ⇝ u optimal, by hypothesis, plus the edge uv ). Now suppose u 's extraction value were too big : take a shortest path π from s to u ; let y be its first vertex not yet settled and x its predecessor on π (settled, since s is settled and u is not). When x was settled, relaxation set d[y] ≤ dist(s,x) + w(x,y) = dist(s,y) . Since weights are non-negative, dist(s,y) ≤ dist(s,u) < d[u] . So d[y] < d[u] , and y (unsettled) would have been extracted before u — contradiction. ∎ Note Where non-negativity is used — exactly once The line dist(s,y) ≤ dist(s,u) . If an edge after y on π had negative weight, u could be closer than y , and settling u early would be wrong. This is why Bellman–Ford exists ( Bellman–Ford and Negative Weights ), and why \"Dijkstra with a re-weighting fix\" (Johnson potentials) is the honest way to handle negative weights that are known to be acyclic-safe. # The four implementations, honestly compared variant push pop time when linear scan for min O(1) O(n) O(n^2) dense: m = Θ(n^2) , n ≤ 5000 ; no heap code, best constants binary heap + lazy deletion O(log) O(log) O((n+m)log n) the default, 10 lines std::set (decrease-key) erase+insert O(log n) O((n+m)log n) when you need real decrease-key (fewer stale entries) pairing / Fibonacci heap O(1) O(log n) O(m + nlog n) theory, and m ≫ n Dial / radix heap — — O(m + nC) / O(m + nlog C) small integer weights ( 0-1 BFS, Dial, Potentials, Johnson ) cpp dijkstra-dense.cpp Copy 1 2 3 4 5 6 7 vector < ll > d ( n , INF ); vector < char > used ( n ); d [ s ] = 0 ; for ( int it = 0 ; it < n ; it ++) { int u = - 1 ; for ( int i = 0 ; i < n ; i ++) if (! used [ i ] && ( u == - 1 || d [ i ] < d [ u ])) u = i ; used [ u ] = 1 ; for ( int v = 0 ; v < n ; v ) if ( w [ u ][ v ] < INF ) d [ v ] = min ( d [ v ], d [ u ] + w [ u ][ v ]); } This O(n^2) version beats the heap version for n ≤ 2000 on dense graphs and has no stale-entry subtlety — know both. The same code, seven different answers Reconstruct the path : keep par[v] inside the relaxation; walk back from the target. Number of shortest paths : if (d[u]+w == d[v]) ways[v] += ways[u]; — but process vertices in settled order , not relaxation order (push (d[v], v) and accumulate when popping, or topologically on the shortest-path DAG). Second shortest path : state = (vertex, used-or-not one \"detour\") → run Dijkstra on 2n states. Dijkstra with a discount/coupon/special edge : state = (vertex, coupon used) — the \"layered graph\" idiom ( 0-1 BFS, Dial, Potentials, Johnson ). Bounded hops ( ≤ k edges) : dp[hops][v] relaxations = k Bellman-Ford rounds, or Dijkstra with hops in the state. Multi-source : push all sources with 0. Negative-free all-pairs : run it n times, O(nm + n^2 log", "w": 982, "h": [["The algorithm in five lines of intent", "the-algorithm-in-five-lines-of-intent"], ["The four implementations, honestly compared", "the-four-implementations-honestly-compared"]]}, {"u": "/shortest/bellman-ford/", "t": "Bellman–Ford and Negative Weights", "s": "n−1 rounds of relaxation, why that is enough, and the two extra lines that output the negative cycle.", "c": "Shortest Paths", "k": "shortest-path negative cycles core", "b": "Bellman–Ford is the only shortest-path algorithm that survives negative edges, because it never settles anything: it simply applies every relaxation n-1 times and trusts the count. Theorem Correctness After k rounds of \"relax every edge once\", d[v] ≤ the length of the shortest walk from s to v using at most k edges. Hence after n-1 rounds, if no negative cycle is reachable, d[v] = dist(s,v) . Proof Induction on k . Round k processes edge uv where u is reachable in ≤ k-1 edges, giving d[v] ≤ d^(k-1)[u] + w ≤ (best (k-1) -walk to u ) + w . Conversely every ≤ k -edge walk ends in some edge uv whose prefix is a ≤ (k-1) -edge walk, so the DP is exhaustive. A shortest walk with no negative cycle can be taken simple, hence has ≤ n-1 edges (cycle removal, Walks, Trails, Paths, Cycles ) — which is exactly the value that makes n-1 rounds sufficient. ∎ cpp bellman-ford.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct Edge { int u , v ; ll w ; }; vector < ll > d ( n , INF ); d [ s ] = 0 ; for ( int it = 0 ; it < n - 1 ; it ++) for ( const auto & e : edges ) if ( d [ e . u ] < INF && d [ e . u ] + e . w < d [ e . v ]) d [ e . v ] = d [ e . u ] + e . w , par [ e . v ] = e . u ; // one extra round: an improvement means a reachable negative cycle int x = - 1 ; for ( const auto & e : edges ) if ( d [ e . u ] < INF && d [ e . u ] + e . w < d [ e . v ]) { x = e . v ; par [ x ] = e . u ; break ; } if ( x != - 1 ) { // walk back n times to land inside the cycle for ( int i = 0 ; i < n ; i ++) x = par [ x ]; vector < int > cyc { x }; for ( int y = par [ x ]; y != x ; y = par [ y ]) cyc . push_back ( y ); cyc . push_back ( x ); reverse ( cyc . begin (), cyc . end ()); cout << \"NO\\n\" ; // or print cyc return ; } Note The for (i = 0; i < n; i++) x = par[x] trick x is a vertex whose distance still improves, so its parent chain must pass through the negative cycle. Following par n times from any vertex of a graph with a cycle of length ≤ n lands on the cycle — after that, walking parents until you return to x enumerates exactly the cycle. 8 lines instead of a separate DFS. # SPFA: the queue version, and why to distrust it cpp spfa.cpp Copy 1 2 3 4 5 6 7 8 9 10 queue < int > q ; vector < char > inq ( n ); d [ s ] = 0 ; q . push ( s ); inq [ s ] = 1 ; while (! q . empty ()) { int u = q . front (); q . pop (); inq [ u ] = 0 ; for ( auto [ v , w ] : g [ u ]) if ( d [ u ] + w < d [ v ]) { d [ v ] = d [ u ] + w ; if (! inq [ v ]) { inq [ v ] = 1 ; q . push ( v ); } } } // negative cycle detected by: cnt[v] = cnt[u] + 1 > n-1 (or \"v enqueued > n times\") SPFA is Bellman–Ford with a worklist: only vertices whose value changed are scanned. It is O(nm) in the worst case and usually linear-ish in practice — which means someone can construct a test that kills it (grid graphs with back-edges and negative edges are the classic anti-SPFA generator). Use it when negative weights are guaranteed and nm fits; otherwise re-weight ( 0-1 BFS, Dial, Potentials, Johnson , Johnson) and run Dijkstra. What Bellman–Ford is genuinely the best answer to negative weights with n ≤ 500 , m ≤ 10^4 — 5 × 10^6 relaxations, no thinking, detect/print a negative cycle (the parent trick above), \"cheapest route with at most k edges\" — run exactly k rounds, and the answer is d^(k)[t] : a whole family of problems (\"flight with at most k stops\", CSES-style) is just this truncated Bellman–Ford, difference constraints : x_v - x_u ≤ c for edges u → v with weight c — feasible ⇔ no negative cycle, and the distances are a solution, arbitrage / currency exchange : maximise ∏ rates ⟹ minimise ∑ log(1/r) ⟹ negative cycle. Example At most k edges, in 6 lines cpp Copy 1 2 3 4 5 for ( int step = 0 ; step < k ; step ++) { auto nd = d ; // note: snapshot, not in-place! for ( const auto & e : edges ) nd [ e . v ] = min ( nd [ e . v ], d [ e . u ] + e . w ); d = nd ; } Copying d is what makes the round count exact. Updating in place mixes \"up to k edges\" with \"up to k+1 \" and silently accepts", "w": 786, "h": [["SPFA: the queue version, and why to distrust it", "spfa-the-queue-version-and-why-to-distrust-it"]]}, {"u": "/shortest/floyd-warshall/", "t": "Floyd–Warshall", "s": "Three nested loops, every pair of vertices, and a dozen problems that are \"Floyd, but the operation is not min\".", "c": "Shortest Paths", "k": "all-pairs DP matrix core", "b": "Definition After the outer loop has reached k , d[i][j] is the shortest distance from i to j using only intermediate vertices from {0, …, k-1} . cpp floyd-warshall.cpp Copy 1 2 3 4 5 6 7 8 9 10 vector < vector < ll >> d ( n , vector < ll >( n , INF )); for ( int i = 0 ; i < n ; i ++) d [ i ][ i ] = 0 ; for ( auto [ u , v , w ] : edges ) { d [ u ][ v ] = min ( d [ u ][ v ], ( ll ) w ); d [ v ][ u ] = min ( d [ v ][ u ], ( ll ) w ); // delete for digraphs; keep min() for parallel edges } for ( int k = 0 ; k < n ; k ++) for ( int i = 0 ; i < n ; i ++) if ( d [ i ][ k ] < INF ) for ( int j = 0 ; j < n ; j ++) d [ i ][ j ] = min ( d [ i ][ j ], d [ i ][ k ] + d [ k ][ j ]); Proof Consider a shortest walk i ⇝ j whose intermediates lie in {0..k} . Either it avoids k — then d^(k)[i][j] = d^(k-1)[i][j] — or it passes through k , and the two halves use only {0..k-1} (no negative cycle ⇒ the walk can be taken simple, so k appears once). That is precisely the update d^(k-1)[i][k] + d^(k-1)[k][j] . Induction over k , and k = n allows every vertex. ∎ Note Why the loops must be in that order k is the stage index, so it belongs outermost. Putting k inside is a different (wrong) algorithm: one pass of \" i,j outer, k inner\" only discovers paths whose intermediates appear in increasing index order. The invariant above is the only thing separating a correct 6-line solution from a wrong one, so state it in a comment in your template. # What you get for the same cost Six uses of the same three loops negative cycle detection : after the algorithm, any d[i][i] < 0 means a negative cycle through i — O(n) extra, no parent chasing, transitive closure of a digraph : replace ( min, + ) by (or, and): d[i][j] |= d[i][k] & d[k][j] , with a bitset row this is O(n^3/64) , widest/bottleneck path : d[i][j] = max(d[i][j], min(d[i][k], d[k][j])) , girth (shortest cycle) : run Floyd only for k ≤ c and, before each stage, check min over i<j<c of d[i][j] + w(i,c) + w(c,j) — O(n^3) total, minimax / \"minimise the maximum edge on the path\" : d[i][j] = min(d[i][j], max(d[i][k], d[k][j])) , recover the path : keep nxt[i][j] and update it exactly where you update d — 2 extra lines, and it beats running n Dijkstras when you need all pairs anyway. cpp floyd-reconstruct.cpp Copy 1 2 3 4 5 vector < vector < int >> nxt ( n , vector < int >( n , - 1 )); for ( auto [ u , v , w ] : edges ) d [ u ][ v ] = min ( d [ u ][ v ], w ), nxt [ u ][ v ] = v ; // inside the k-loop, when you improve: if ( d [ i ][ k ] + d [ k ][ j ] < d [ i ][ j ]) { d [ i ][ j ] = d [ i ][ k ] + d [ k ][ j ]; nxt [ i ][ j ] = nxt [ i ][ k ]; } // walk: v = i; while (v != j) v = nxt[v][j]; Example Floyd as a semiring Every row above is the same program with (min, +) replaced by another closed semiring: (or, and) for reachability, (max, min) for capacity, (min, max) for the bottleneck, matrix multiplication over (+,×) for walk counting ( Counting Walks with Matrix Powers ). Recognising \"this is Floyd over a different semiring\" collapses several problem types into one loop you already have memorised. # When not to use it n ≥ 1000 : 10^9 operations, too slow; run Dijkstra per source if the graph is sparse ( Dijkstra's Algorithm ) or Johnson ( 0-1 BFS, Dial, Potentials, Johnson ), memory n^2 : n = 2000 with long long is 32 MB — fine; n = 5000 is 200 MB — not, if you only need one source, O(nm) BFS/Dijkstra always beats O(n^3) . Problems for this page all warm-up hard core CSES 1672 Shortest Routes II warm-up straight Floyd CSES 1705 Forbidden Cities hard closure + SCC CF 601A The Two Routes core Floyd on the complement", "w": 696, "h": [["What you get for the same cost", "what-you-get-for-the-same-cost"], ["When not to use it", "when-not-to-use-it"]]}, {"u": "/shortest/sparse-tricks/", "t": "0-1 BFS, Dial, Potentials, Johnson", "s": "What to run when the weights are small, zero-one, or negative — with the invariant that makes each one correct.", "c": "Shortest Paths", "k": "shortest-path deque potentials hard", "b": "Dijkstra's heap is an oracle for \"extract minimum\". Whenever the distances you generate are almost sorted, a cheaper container suffices — and each special case below is exactly that observation. # Weights in {0, 1}: 0-1 BFS Note The invariant While the deque is non-empty, the distances it stores take at most two consecutive values, and the container is sorted non-decreasing from front to back. Push a weight-0 relaxation to the front , a weight-1 relaxation to the back : the front insertion keeps the two-valued order, so popping always yields a currently-minimal vertex — a Dijkstra where extract-min costs O(1) . Total O(n + m) . cpp zero-one-bfs.cpp Copy 1 2 3 4 5 6 7 8 9 deque < int > dq ; d . assign ( n , INF ); d [ s ] = 0 ; dq . push_back ( s ); while (! dq . empty ()) { int u = dq . front (); dq . pop_front (); for ( auto [ v , w ] : g [ u ]) if ( d [ u ] + w < d [ v ]) { d [ v ] = d [ u ] + w ; if ( w == 0 ) dq . push_front ( v ); else dq . push_back ( v ); } } Where weights 0/1 come from in disguise \"buy a ticket / walk for free along existing tracks\": add 1 to a new edge, 0 to an existing one, grid problems where a straight step costs 0 and a turn costs 1 (run BFS on directed states (cell, direction)), \"minimum number of changes\" formulations: keep = 0, change = 1, the complement-graph BFS (CSES/CF 1242B style): edges of weight 1 in the complement = non-edges of G , and iterating unvisited vertices with a set makes it O(n + m) overall. # Small integer weights: Dial's algorithm Bucket by distance: an array of nC + 1 vectors, where C = max_e w(e) , plus a cursor walking forward. Each insertion is O(1) , total O(nC + m) . Use it when C ≤ 100 or so; for larger C but monotone keys, a radix heap gives O((n + m)log C) with tiny constants and no comparator. cpp dial.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int C = max_w , K = n * C + 2 ; vector < vector < int >> bucket ( K ); vector < int > d ( n , INF ); int cur = 0 ; d [ s ] = 0 ; bucket [ 0 ]. push_back ( s ); for ( int seen = 0 ; seen < n ; ) { while ( cur < K && bucket [ cur ]. empty ()) cur ++; if ( cur >= K ) break ; int u = bucket [ cur ]. back (); bucket [ cur ]. pop_back (); if ( d [ u ] != cur ) continue ; // stale seen ++; for ( auto [ v , w ] : g [ u ]) if ( d [ u ] + w < d [ v ]) { d [ v ] = d [ u ] + w ; bucket [ d [ v ]]. push_back ( v ); } } # Negative weights without Bellman–Ford: potentials Theorem Re-weighting (Johnson) Let p: V → ℝ be any function and define w'(uv) = w(uv) + p(u) - p(v) . Then for every path P from s to t , len_w'(P) = len_w(P) + p(s) - p(t). So all s → t paths keep their relative order: shortest paths are unchanged, and w' is non-negative whenever p is a feasible potential, e.g. p(v) = dist(v_0, v) for a super-source v_0 . Proof Sum ∑_uv ∈ P (w(uv) + p(u) - p(v)) — the p -terms telescope, leaving p(s) - p(t) . For non-negativity: w'(uv) ≥ 0 ⇔ p(v) ≤ p(u) + w(uv) , which is exactly the triangle inequality satisfied by any distance vector. ∎ cpp johnson.cpp Copy 1 2 3 4 5 6 // 1) super-source with 0-weight edges to everyone; 2) Bellman-Ford for p // 3) re-weight every edge; 4) Dijkstra from each source; 5) undo with d[u] - p(s) + p(t) vector < ll > p ( n , 0 ); for ( int it = 0 ; it < n ; it ++) // n rounds: one extra to catch negative cycles for ( auto [ u , v , w ] : edges ) if ( p [ u ] + w < p [ v ]) p [ v ] = max (- 1e15 , p [ u ] + w ); auto wt = [&]( int u , int v , ll w ) { return w + p [ u ] - p [ v ]; }; Cost and payoff all-pairs with negative weights: O(nm + n^2 log n) instead of Floyd's O(n^3) — the same bound as n Dijkstras, but valid with negatives, A\\* / min-cost flow : reduced costs w + pot[u] - pot[v] are exactly this re-weighting; keeping them non-negative is what lets Min-Cost Max-Flow use Dijkstra instead of Bellman–Ford in every augmentation, LP duality : a feasible potential is a dual solution; \"no negative cycle\" is feasibility of the difference-constraint system ( Bellman–Ford and Negative Weights ). Watch out Potent", "w": 836, "h": [["Weights in {0, 1}: 0-1 BFS", "weights-in-0-1-0-1-bfs"], ["Small integer weights: Dial's algorithm", "small-integer-weights-dials-algorithm"], ["Negative weights without Bellman–Ford: potentials", "negative-weights-without-bellmanford-potentials"]]}, {"u": "/matrices/adjacency/", "t": "Adjacency, Incidence and Laplacian", "s": "Three matrices for one graph, what each row/column product means, and the properties you can read off in O(1).", "c": "Matrices", "k": "matrix spectrum invariants core", "b": "Definition For a simple graph on V = {1, …, n} : adjacency A ∈ {0,1}^n × n , A_ij = 1 ⇔ ij ∈ E . Symmetric, zero diagonal, deg(i) = ∑_j A_ij . incidence B ∈ {0,1}^n × m (undirected: B_ie = 1 iff i is an endpoint of e ) or {-1, 0, 1} for digraphs, with -1 at the tail. Laplacian L = D - A , where D = diag(deg) . Equivalently L = B B^mathsf T for the oriented incidence matrix. What each one is for A : walk counting, spectra, \"is there an edge\" queries ( Counting Walks with Matrix Powers ), and the semiring DP of Floyd–Warshall , B : cuts and flows — a column has exactly one -1 and one +1 , so the flow conservation equations are B x = 0 and a cut is a row-space projection, L : everything involving spanning trees, random walks, and connectivity — x^mathsf T L x = ∑_uv ∈ E (x_u - x_v)^2 ≥ 0 , so L ≽ 0 always. Theorem The Laplacian's kernel tells you the components rank(L) = n - c , where c is the number of connected components, and ker L is spanned by the component indicator vectors. Proof x^mathsf T L x = ∑_uv ∈ E(x_u - x_v)^2 = 0 ⇔ x is constant on every edge, hence constant on each component; so ker L has dimension exactly c . ∎ That one line is the reason the second-smallest eigenvalue of L ( λ_2 , the algebraic connectivity / Fiedler value) measures how well connected G is: λ_2 > 0 ⇔ connected, and Cheeger's inequality 2√(λ_2) ≥ h ≥ λ_2/2 ties it to the sparsest cut — which is the theoretical basis of spectral clustering and of \"the Fiedler vector splits the graph well\". Note Signed vs unsigned incidence With B ∈ {0,1}^n× m (no signs), BB^mathsf T = A + D — not L . The signs are what make L = BB^mathsf T work, and they are also what make the flow equations exact. For bipartite graphs the unsigned version equals 0 & M; M^mathsf T & 0 -style structure that makes matching arguments transparent ( Matching: Definitions and Duality ). # Reading properties off A in O(1)-ish Example Common neighbours, triangles, degree sequence cpp bitset-adj.cpp Copy 1 2 3 4 5 6 7 8 9 const int MAXN = 5000 ; bitset < MAXN > adj [ MAXN ]; int common ( int u , int v ) { return ( adj [ u ] & adj [ v ]). count (); } // O(n/64) ll triangles () { // O(n^3/64) ll t = 0 ; for ( int u = 0 ; u < n ; u ++) for ( int v : g [ u ]) if ( v > u ) t += ( adj [ u ] & adj [ v ] & gt [ v ]). count (); return t / 3 ; // each triangle found at all 3 vertices } tr(A^3) = 6 · (# triangles) in a simple graph, and more generally tr(A^k) = ∑_i λ_i^k counts closed walks, which is how one proves two graphs with different spectra are non-isomorphic ( Trees: Six Definitions of One Object ). Figure 1 The adjacency matrix of a small graph: symmetric, 0–1, row sums = degrees. Block structure visible = a candidate for bipartition or for a quotient matrix. # Quotient matrices, an underrated tool If the partition V = V_1 ⊔ … ⊔ V_k is equitable (every vertex of V_i has the same number b_ij of neighbours in V_j ), then the k × k matrix B = (b_ij) has the property that the largest eigenvalue of B equals that of A , and eigenvectors of B lift to A -eigenvectors constant on the parts. This is how one computes spectra of distance-regular graphs, Johnson graphs and the hypercube Q_n (whose quotient is the path with self-loops on 0..n ) in three lines instead of 2^n . Problems for this page all hard core CSES 2138 Reachable Nodes hard transitive closure as a bitset DP CSES 1134 Prüfer Code core degree sequences from a matrix-free encoding", "w": 656, "h": [["Reading properties off A in O(1)-ish", "reading-properties-off-a-in-o1-ish"], ["Quotient matrices, an underrated tool", "quotient-matrices-an-underrated-tool"]]}, {"u": "/matrices/walks-powers/", "t": "Counting Walks with Matrix Powers", "s": "A^k counts walks, and the trick of exponentiating a graph's matrix solves \"in exactly k steps\" problems in O(n³ log k).", "c": "Matrices", "k": "matrix exponentiation counting core", "b": "Theorem The fundamental identity Let A be the adjacency matrix of a graph (weighted: put the weights in the entries). Then (A^k)_ij = #{walks of length exactly k from i to j}. For a weighted graph the sum ∑_P ∏_e ∈ P w(e) runs over the same walks. Proof Induction on k , expanding the matrix product: (A^k)_ij = ∑_m (A^k-1)_im A_mj . A walk of length k from i to j is uniquely a walk of length k-1 from i to some m followed by an edge m → j . The base case k=1 is the definition of A , and k = 0 gives A^0 = I — the empty walk, from a vertex to itself only. ∎ Example Exactly k steps, huge k n ≤ 100 vertices, k ≤ 10^18 , count walks 1 → n modulo M : binary-exponentiate A in O(n^3 log k) — 100^3 · 60 = 6 · 10^7 , instant. \"At most k steps\"? Either append a self-loop at the target, or exponentiate the block matrix A & I; 0 & I whose powers accumulate ∑_i ≤ k A^i . cpp walk-count.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Mat = vector < vector < ll >>; Mat mul ( const Mat & a , const Mat & b , ll mod ) { int n = ( int ) a . size (); Mat c ( n , vector < ll >( n )); for ( int i = 0 ; i < n ; i ++) for ( int k = 0 ; k < n ; k ++) if ( a [ i ][ k ]) // skip zeros: 2-5x on sparse A for ( int j = 0 ; j < n ; j ++) c [ i ][ j ] = ( c [ i ][ j ] + a [ i ][ k ] * b [ k ][ j ]) % mod ; return c ; } Mat mpow ( Mat a , ll e , ll mod ) { int n = ( int ) a . size (); Mat r ( n , vector < ll >( n )); for ( int i = 0 ; i < n ; i ++) r [ i ][ i ] = 1 ; for (; e ; e >>= 1 ) { if ( e & 1 ) r = mul ( r , a , mod ); a = mul ( a , a , mod ); } return r ; } Five variants of the same multiplication parity / bipartiteness : (A^k)_ii = 0 for all odd k ⇔ no odd closed walk ⇔ bipartite ( Bipartite Graphs and 2-Colouring ), girth : the smallest k with tr(A^k) > 0 beyond the trivial contributions; tr(A^3) = 6t counts triangles, tr(A^4) counts 4-cycles plus degenerate ones (subtract them: 2m + 4∑_i C(deg i, 2) ), reachability within k : work over the boolean semiring, or over (min,+) with \" + \" = ordinary addition (that is exactly min-plus matrix power = \"shortest walk with ≤ k edges\" — Bellman–Ford and Negative Weights viewed differently), expected hitting times : replace A by the transition matrix P = D^-1A and solve a linear system ( Random Walks and Cover Times ), absorbing chains : ∑_k ≥ 0 Q^k = (I - Q)^-1 for the submatrix Q over transient states. Watch out Modular arithmetic and 'exactly' Over a modulus, \"is zero\" no longer means \"there are none\" — tr(A^4) ≡ 0 ±od 2 can hide real cycles. And \"walk\" is not \"path\": A^k happily counts walks that revisit vertices. If the problem says simple path of length k , matrix powers are useless — that is Held–Karp: Hamilton in O(2ⁿn²) territory ( O(2^n n^2) ) or it is NP-complete. Problems for this page all core hard CSES 1136 Counting Paths core the tree case: difference array, no powers CSES 2181 Counting Tilings hard transfer matrix, exponentiate over width", "w": 572, "h": []}, {"u": "/matrices/matrix-tree/", "t": "The Matrix–Tree Theorem", "s": "Counting spanning trees with a determinant — plus Gaussian elimination mod p, which is the algorithm you actually type.", "c": "Matrices", "k": "determinant counting matrix olympiad", "b": "Theorem Kirchhoff (matrix–tree) Let L = D - A be the Laplacian of a connected graph, and L^(r) the matrix obtained by deleting row and column r . Then τ(G) = det L^(r), the number of spanning trees — independent of which row/column you removed. Proof Two ingredients. Cauchy–Binet. L = B B^mathsf T where B is the (n-1) × m oriented incidence matrix with row r removed. Hence det L^(r) = det(B B^mathsf T) = ∑_S det(B_S)^2 , summing over all (n{-}1) -edge subsets S of columns. A square incidence submatrix is 0 , ± 1 . B_S is nonsingular ⇔ the edges S contain no cycle and connect everything, i.e. ⇔ S is a spanning tree; then det(B_S) = ± 1 . Summing det(B_S)^2 over subsets therefore counts exactly the spanning trees. ∎ Note What the proof gives for free Multigraphs : parallel edges contribute additively, since L just accumulates them — \"three bridges between the same islands\" is one L[u][v] -= 3 , no new theory, Weighted count : with A_uv = w_uv , the determinant is ∑_T ∏_e ∈ T w_e , the generating function of trees by weight — this is why \"minimum spanning tree\" and \"determinant of a Laplacian\" are cousins, not siblings, Disconnected graphs : det L^(r) = 0 , matching the fact that there is no spanning tree. # The arithmetic, carefully cpp determinant-mod-p.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ll det_mod ( vector < vector < ll >> a , ll p ) { // p prime int n = ( int ) a . size (); ll res = 1 ; for ( int col = 0 ; col < n ; col ++) { int piv = - 1 ; for ( int r = col ; r < n ; r ++) if ( a [ r ][ col ]) { piv = r ; break ; } if ( piv == - 1 ) return 0 ; if ( piv != col ) { swap ( a [ piv ], a [ col ]); res = ( p - res ) % p ; } // row swap flips the sign res = res * a [ col ][ col ] % p ; ll inv = powmod ( a [ col ][ col ], p - 2 , p ); for ( int r = col + 1 ; r < n ; r ++) if ( a [ r ][ col ]) { ll f = a [ r ][ col ] * inv % p ; for ( int j = col ; j < n ; j ++) a [ r ][ j ] = ( a [ r ][ j ] - f * a [ col ][ j ]) % p ; if ( a [ r ][ col ] < 0 ) a [ r ][ col ] += p ; } } return res % p ; } For \"exact integer\" answers instead of modular ones, use fraction-free Gaussian elimination (Bareiss) : it keeps every intermediate an integer, dividing by the previous pivot — O(n^3) big-integer multiplications and no gcd blow-up. That is what SPOJ HIGHWAYS-style problems with n ≤ 60 need; a modular answer with one prime is only valid if the question says \"mod p\". Example Two counts you can now do instantly K_n : L = nI - J + I = (n)I - J restricted, whose eigenvalues are n with multiplicity n-1 (and 0 ), so τ(K_n) = n^n-2 — Cayley's formula, reproved by linear algebra ( Counting Trees: Cayley and Prüfer ). K_a,b : eigenvalue bookkeeping gives a^b-1 b^a-1 . Spectral shortcuts for Laplacians τ(G) = (1)/(n) ∏_i=2^n λ_i for any connected G ( λ_i = nonzero Laplacian eigenvalues) — handy when the spectrum is known by symmetry (cycles: τ(C_n) = n , complete multipartite, hypercube: τ(Q_n) = 2^2 n - n - 1 ∏... , better looked up than derived at 11pm), λ_2 = 0 ⇔ disconnected, and λ_2 bounds expansion (Cheeger), which is the quantitative version of \"is this graph well connected\" ( Cuts and Flows ), effective resistance between u,v equals Ω_uv = (L^+)_uu + (L^+)_vv - 2 (L^+)_uv : \"expected commute time\" of a random walk is 2m · R_uv ( Random Walks and Cover Times ) — one pseudo-inverse, three different subjects. Watch out Three failure modes Forgetting the sign flip on a row swap (determinant is then wrong by -1 , which mod 2 looks \"fine\" and mod 10^9{+}7 looks random). Deleting the wrong row/column pair — you must delete the same index from both (any one, but one of each). Building L for a digraph: the theorem needs the symmetric Laplacian. For directed graphs the analogue is the matrix-tree theorem for arborescences (BEST theorem, Euler Tours: When They Exist ) with L_ij = -a_ij for i≠ j and L_ii = deg^-(i) , and then you delete row/column of the root . Problems for this page all hard core SPOJ HIGHWAYS Counting Highways hard exact dete", "w": 732, "h": [["The arithmetic, carefully", "the-arithmetic-carefully"]]}, {"u": "/matrices/recurrences/", "t": "Linear Recurrences from Graphs and Matrices", "s": "Turn a DP with constant-size state into a matrix, exponentiate, and answer n = 10^18 questions.", "c": "Matrices", "k": "recurrence exponentiation DP hard", "b": "Every DP of the form \"the state at step i is a fixed-size vector, and each entry is a linear combination of the previous step's entries\" is a matrix power in disguise — so anything with n ≤ 10^18 and a small state is solvable. Example Fibonacci, properly binom{F_k+1}{F_k} = C(1, 1)C(1, 0)^kC(1, 0) — i.e. M = 1 & 1; 1 & 0 , M^k gives F_k+1 in O(log k) . The point is not Fibonacci; it is that the transition matrix is the recurrence . 1 Write the DP state as a vector v_i of constant size k (window of the last values, or the \"profile\" of a board column, or the automaton state count). 2 Write v_i+1 = T v_i by reading the recurrence's coefficients — row j of T says how v_i+1[j] is formed. 3 Answer = entry of T^ n - i_0 v_i 0 , computed by binary exponentiation mod M in O(k^3 log n) . 4 If there are several \"queries with different n \", precompute T^2 0 , …, T^{2^60} once: O(k^3 log n) memory, O(k^2 log n) per query. cpp linear-recurrence.cpp Copy 1 2 3 4 5 6 7 8 // k-th order recurrence f(n) = c[0] f(n-1) + ... + c[k-1] f(n-k), n up to 1e18 using Mat = vector < vector < ll >>; Mat T ( k , vector < ll >( k )); for ( int j = 0 ; j < k ; j ++) T [ 0 ][ j ] = c [ j ]; // companion matrix for ( int i = 1 ; i < k ; i ++) T [ i ][ i - 1 ] = 1 ; Mat P = mpow ( T , n - ( k - 1 ), MOD ); ll ans = 0 ; for ( int j = 0 ; j < k ; j ++) ans = ( ans + P [ 0 ][ j ] * base [ j ]) % MOD ; // base = f(k-1), ..., f(0) Recurrences that come from graphs walks of length ≤ k between two vertices : adjacency powers ( Counting Walks with Matrix Powers ); number of independent sets / matchings in a path or cycle graph : transfer matrix of width 2–3, which is also the \"domino tiling of a 3 × n board\" trick (state = which cells of the current column are already covered — 2^b states for height b , so b ≤ 12 is comfortable), expected first hitting time in a Markov chain : solve (I - Q)x = rhs , i.e. a linear system rather than a power ( Random Walks and Cover Times ), graph power queries \"is there a path of length exactly k in a graph with self-loops at every vertex\": A^k with A + I . Note When the order is not constant: Berlekamp–Massey If you can compute f(0), f(1), …, f(2L) by DP for small arguments (say L = 200 ), Berlekamp–Massey recovers the minimal linear recurrence of order ≤ L in O(L^2) over a field, and you then exponentiate as above. This is a legitimate competitive-programming technique for \"count tilings/walks modulo 10^9+7 with n ≤ 10^9 and a state you can enumerate\": guess-and-prove is replaced by a theorem (the recurrence is minimal, hence unique mod p for the sequence's first 2L terms). Example Sparse T , huge k If T is sparse (typical for automaton transitions), do matrix–vector exponentiation instead: precompute T^2 i is O(k^3) each, but applying bits to a vector is O(k^2) — total O(k^3 log n + k^2 log n) . For k = 100 and one query it does not matter; for 10^5 queries with the same T the precompute amortises and you never multiply two matrices again. Watch out Two modulus traps T^n mod M with composite M : no problem, but if you instead want to divide (e.g. closed form with √5 ), you need M prime and an inverse — or lift to ℤ_M[x]/(x^2 - 5) . Recurrences whose natural state has O(n) entries ( n = problem size): the matrix is then n × n and O(n^3 log k) beats the O(nk) DP only for k ≫ n . Do the arithmetic before writing code — that check is the skill. Problems for this page all hard CSES 2181 Counting Tilings hard transfer matrix, 2^b states Exercise Do these by hand, then code them f(n) = f(n-1) + 2 f(n-3) with f(0)=f(1)=f(2)=1 : write the 3× 3 transition matrix and compute f(10) by hand-modulo-1000, then verify against the DP. Number of ways to tile a 3 × n rectangle with dominoes: derive the 8-state (or 4-state) transfer matrix, and the order-2 recurrence a_n = 4a_n-2 - a_n-4 you get after eliminating states.", "w": 804, "h": []}, {"u": "/structures/heap/", "t": "Binary Heap and priority_queue", "s": "The array-shaped tree, why it is fast in practice, and the six uses beyond Dijkstra.", "c": "Data Structures on Trees", "k": "heap greedy priority core", "b": "A binary heap is an array holding a complete binary tree with the heap property: a[i] <= a[2i] , a[i] <= a[2i+1] . Completeness is what makes it array-indexed (children of i are 2i , 2i+1 ; parent is i/2 ), and what makes it cache-friendly — a pointer-based structure with the same guarantees would be 3–5× slower. The operations, with their real costs push , pop (extract-min): O(log n) worst case — sift down/up along one root-to-leaf path of length ⌈ log_2 n ⌉ , build from an array : O(n) , not O(nlog n) — sift every internal node down, bottom-up; the total is ∑_h frac{n}{2^h+1} h = O(n) , which is the standard \"count nodes at each height\" argument, top : O(1) , decrease-key : not supported by std::priority_queue — either erase+reinsert with a std::set (real decrease-key, O(log n) ) or push a duplicate and skip stale entries on pop (the lazy version Dijkstra uses), merge: O(n + m) by concatenating and rebuilding — which is why \"small-to-large merging of heaps\" ( Small-to-Large Merging ) is cheap; a leftist/pairing heap gives O(log n) merge if you truly need it. cpp heap-core.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void sift_down ( vector < int >& a , int i ) { // 0-indexed array, size n int n = ( int ) a . size (); for (;;) { int l = 2 * i + 1 , r = l + 1 , m = i ; if ( l < n && a [ l ] < a [ m ]) m = l ; if ( r < n && a [ r ] < a [ m ]) m = r ; if ( m == i ) break ; swap ( a [ i ], a [ m ]); i = m ; } } void build ( vector < int >& a ) { // O(n) for ( int i = ( int ) a . size () / 2 - 1 ; i >= 0 ; i --) sift_down ( a , i ); } # std::priority_queue, honestly cpp pq.cpp Copy 1 2 3 4 5 priority_queue < int , vector < int >, greater < int >> pq ; // min-heap priority_queue < pair < int , int >> pq2 ; // max by .first then .second priority_queue < tuple < int , int >, vector < tuple < int , int >>, greater <>> pq3 ; struct Cmp { bool operator ()( Job & a , Job & b ) const { return a . t > b . t ; } }; // NOTE: inverted! priority_queue < Job , vector < Job >, Cmp > pq4 ; priority_queue is a max -heap under less<> , so a custom comparator must return a > b to get a min-heap — the inversion is the #1 source of \"my greedy processed the largest instead of the smallest\" bugs. Writing greater<> (transparent comparator, C++14+) removes the doubt. Example Lazy deletion, the pattern you want in Dijkstra cpp Copy 1 2 3 4 5 while (! pq . empty ()) { auto [ d , v ] = pq . top (); pq . pop (); if ( d != dist [ v ]) continue ; // stale: a better key was pushed after this one ... } It keeps each relaxation in the heap (up to m entries) instead of n , so memory is O(m) and time O(m log m) — and it is the only sane way to do decrease-key with priority_queue . # Five non-textbook uses Greedy with a changing key K-way merge / \"next event\" : k sorted lists, push each head, pop min, push the successor — O(N log k) . Median of a stream : two heaps (max-heap lower half, min-heap upper half) balanced to size difference 1 — the standard sliding-window median needs the lazy-deletion variant plus erase by value, which is why a multiset/policy-tree is often shorter. Scheduling : earliest-deadline-first, and Huffman's two-smallest rule ( Huffman Coding ) — \"extract two minima, push their sum\" is one loop. Dijkstra / A\\ * — see Dijkstra's Algorithm , where the heap key is the tentative distance. Top-k / \"keep the k smallest\" : a max-heap of size k , evict the top when a smaller element arrives — O(n log k) and O(k) memory, which is what \"k nearest / k shortest\" subroutines do. Note Why not a balanced BST? std::set supports erase-by-iterator, lower_bound , and iteration in order, at the same O(log n) — so it wins whenever you need removal of an arbitrary element or successor queries . Heaps win on constants (contiguous array) and on push -only workloads. The decision is about which operations you need, not about asymptotics. Example n-log-n lower bound, one paragraph Sorting reduces to \"insert all, extract all\" on a heap, so a comparison-based heap-sort is O(n ", "w": 779, "h": [["std::priority_queue, honestly", "stdpriority-queue-honestly"], ["Five non-textbook uses", "five-non-textbook-uses"]]}, {"u": "/structures/dsu/", "t": "Disjoint Set Union (Union–Find)", "s": "Two heuristics, one inverse-Ackermann bound, and the offline tricks that make it a problem-solving tool rather than a data structure.", "c": "Data Structures on Trees", "k": "DSU connectivity Kruskal core", "b": "Definition Maintain a partition of {0, …, n-1} under two operations: unite(a, b) (merge the two classes) and find(a) (return a representative of a 's class). Nothing else is supported — that limitation is why it is O(α(n)) instead of O(log n) . cpp dsu.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 struct DSU { vector < int > par , sz ; DSU ( int n ) : par ( n ), sz ( n , 1 ) { iota ( par . begin (), par . end (), 0 ); } int find ( int v ) { while ( v != par [ v ]) v = par [ v ] = par [ par [ v ]]; return v ; } // path halving bool unite ( int a , int b ) { a = find ( a ); b = find ( b ); if ( a == b ) return false ; // already together: the useful return value if ( sz [ a ] < sz [ b ]) swap ( a , b ); par [ b ] = a ; sz [ a ] += sz [ b ]; return true ; } }; Theorem Union by size alone: O(log n) After any sequence of unions, the depth of every vertex is at most log_2 n . Proof A vertex's depth increases only when its root becomes a child of another root, and by the size rule that can only happen when the other tree is at least as large — so the subtree containing v at least doubles each time v goes one level deeper. Starting at size 1, at most log_2 n doublings fit inside n . ∎ Theorem With path compression: O(α(n)) amortised Union by size (or rank) plus path compression gives O(α(n)) amortised per operation, where α is the inverse Ackermann function — at most 4 for n up to 2^65536 . The proof (Tarjan; and the simpler O((m+n)log^* n) version) charges the cost of each find walk to \"how many times did this vertex's parent change\" and uses the doubling argument above to bound it by log^* n , then a two-level partition of ranks improves log^* to α . In practice the two heuristics differ like this: without compression, adversarial unions build Θ(log n) chains; without union by size, compression alone is O((m+n)log n) amortised and can degrade badly on \"union(0,1), union(1,2), union(2,3), …\" with interleaved finds. Use both; they are one line each. Path halving vs full compression par[v] = par[par[v]] (halving, as above) is iterative, allocation-free and within a few percent of full two-pass compression — it is what you should type in a contest. Full compression recursively re-points every visited node; it wins when finds are extremely repeated, and it is the version to prove things about. # Extensions that are each two lines Example Rollback DSU (no path compression) For divide-and-conquer over time (\"edge active on interval [l, r] \") you need undo: record (child, parent_size) on a stack and revert. Depth is still O(log n) from union-by-size only, so each operation is O(log n) and queries are O(log^2 n) with segment-tree-over-time. Total O(m log m log n) , and it is the standard answer to \"offline dynamic connectivity\". cpp dsu-rollback.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct RollbackDSU { vector < int > par , sz ; vector < tuple < int , int , int >> hist ; int find ( int v ) { while ( par [ v ] != v ) v = par [ v ]; return v ; } // no compression! bool unite ( int a , int b ) { a = find ( a ); b = find ( b ); if ( a == b ) { hist . emplace_back (- 1 , - 1 , - 1 ); return false ; } if ( sz [ a ] < sz [ b ]) swap ( a , b ); hist . emplace_back ( b , a , sz [ a ]); par [ b ] = a ; sz [ a ] += sz [ b ]; return true ; } void rollback () { // undo one recorded step auto [ b , a , sza ] = hist . back (); hist . pop_back (); if ( b == - 1 ) return ; par [ b ] = b ; sz [ a ] = sza ; } }; Extra payloads size / sum / max / count per class: keep an array indexed by the root and merge on unite — the pattern behind \"largest connected component\", \"is this class all the same colour\", bipartite-with-parity, parity DSU (weighted DSU with d[x] ∈ {0,1} from parent): the 10-line solution to \"constraints of the form x ⊕ y = c \" ( Matching Applications , 2-SAT for the harder version), DSU on successor lists : nxt[i] skipping already-used positions — the \"colour the first uncoloured position in [l,r] \" trick, O(n α(n)) for m paint operations, Krus", "w": 803, "h": [["Extensions that are each two lines", "extensions-that-are-each-two-lines"]]}, {"u": "/structures/fenwick/", "t": "Fenwick Tree (Binary Indexed Tree)", "s": "Prefix sums in one loop, with the two-line tricks for range updates and \"find smallest index with prefix ≥ x\".", "c": "Data Structures on Trees", "k": "prefix-sum inversion offline core", "b": "Definition t[i] stores the sum over the half-open range (i - lowbit(i), i] , where lowbit(i) = i & -i . That single choice of ranges makes both update and query a \"walk the set bits\" loop. cpp fenwick.cpp Copy 1 2 3 4 5 6 7 struct BIT { int n ; vector < ll > t ; BIT ( int n ) : n ( n ), t ( n + 1 ) {} void add ( int i , ll v ) { for (; i <= n ; i += i & - i ) t [ i ] += v ; } ll sum ( int i ) { ll s = 0 ; for (; i > 0 ; i -= i & - i ) s += t [ i ]; return s ; } ll range ( int l , int r ) { return sum ( r ) - sum ( l - 1 ); } // 1-indexed, inclusive }; Why it beats a segment tree when it applies ~half the memory and ~3× better constants (one array, no recursion), kth search in O(log n) with a bit-lifting loop (below) — segment trees need the same trick, so no loss, it supports any invertible monoid (sum, xor, count). Not min/max-with-decrease: there is no inverse, which is the whole \"when do I need a segment tree instead\" answer. cpp bit-lifting-kth.cpp Copy 1 2 3 4 5 6 7 // smallest i with sum(i) >= target, assuming all values >= 0 int kth ( ll target ) { int idx = 0 ; for ( int pw = 1 << __lg ( n ); pw ; pw >>= 1 ) if ( idx + pw <= n && t [ idx + pw ] < target ) { target -= t [ idx + pw ]; idx += pw ; } return idx + 1 ; } # Range add + range sum: two trees cpp two-bits.cpp Copy 1 2 3 4 5 // add x on [l, r]: B1: +x at l, -x at r+1 ; B2: +x*(l-1) at l, -x*r at r+1 // prefix(p) = p * B1.sum(p) - B2.sum(p) void range_add ( int l , int r , ll x ) { b1 . add ( l , x ); b1 . add ( r + 1 , - x ); b2 . add ( l , x * ( l - 1 )); b2 . add ( r + 1 , - x * r ); } ll prefix ( int p ) { return p * b1 . sum ( p ) - b2 . sum ( p ); } ll range_sum ( int l , int r ) { return prefix ( r ) - prefix ( l - 1 ); } The formula comes from writing ∑_i ≤ p a_i after the difference-array substitution and collecting terms: ∑_i ≤ p pre_i = ∑_j ≤ i x_j ⇒ (p - j + 1) x_j , so you need ∑ x_j and ∑ j · x_j — two Fenwicks, one for each. (Same algebra as lazy segment tree, but with zero lines of push code.) Example Inversions in 8 lines — the canonical BIT problem cpp Copy 1 2 3 4 5 6 7 // count pairs i < j with a[i] > a[j], values compressed to [1, n] BIT bit ( n ); ll inv = 0 ; for ( int i = n - 1 ; i >= 0 ; i --) { inv += bit . sum ( a [ i ] - 1 ); // elements to the right that are smaller bit . add ( a [ i ], 1 ); } Read it as a sweep: bit holds the suffix multiset, and sum(k) answers \"how many seen values are ≤ k \". Coordinate compression ( sort + unique + lower_bound ) is the standard preprocessing when values reach 10^9 . The four offline patterns count pairs with condition on both coordinates : sort by one, BIT over the other (inversions, \"smaller elements to the left\", dominance counting), k -th smallest in a static range : BIT over value-sorted positions + binary search, or a persistent segment tree for queries online, \"number of distinct values in [l,r] \" : sort queries by right end, keep 1 only at the last occurrence of each value, answer with a range sum — a two-line reduction, subtree queries on a tree ( Entry/Exit Times and the Euler Tour ): the flat array is a Fenwick's job description. Note Fenwick of Fenwicks, and when to stop 2D point update / rectangle sum is a BIT over x whose nodes hold a BIT over y — O(log^2 n) per operation with O(n log n) memory (offline: collect each node's y -values first). It is genuinely useful for n ≤ 2 × 10^5 , but if the second dimension is also dynamic, a segment tree of treaps or plain divide-and-conquer is usually the shorter path. Problems for this page all core hard CSES 1734 Distinct Values Queries core offline + last occurrence CSES 2169 Nested Ranges Count hard sort + BIT CSES 1188 Bit Inversions hard two BITs, careful algebra", "w": 699, "h": [["Range add + range sum: two trees", "range-add-range-sum-two-trees"]]}, {"u": "/structures/segment-tree/", "t": "Segment Tree", "s": "One combining function, three shapes (plain, lazy, persistent), and the invariant that makes every variant obvious.", "c": "Data Structures on Trees", "k": "range queries lazy structure core", "b": "Definition Store, for every node v covering a range [l,r) , the aggregate of that range. Combine children on the way up, and on a query keep two accumulators (left result, right result) while descending — the result is not commutative-safe unless you say so. cpp segtree-iterative.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Seg { // n = power of two, [l, r) int n ; vector < ll > t ; Seg ( int m ) { n = 1 ; while ( n < m ) n *= 2 ; t . assign ( 2 * n , 0 ); } void build ( const vector < ll >& a ) { for ( int i = 0 ; i < ( int ) a . size (); i ++) t [ n + i ] = a [ i ]; for ( int i = n - 1 ; i > 0 ; i --) t [ i ] = t [ 2 * i ] + t [ 2 * i + 1 ]; } void setv ( int p , ll x ) { for ( t [ p += n ] = x ; p > 1 ; p >>= 1 ) t [ p >> 1 ] = t [ 2 * p ] + t [ 2 * p + 1 ]; } ll query ( int l , int r ) { // half-open ll s = 0 ; for ( l += n , r += n ; l < r ; l >>= 1 , r >>= 1 ) { if ( l & 1 ) s += t [ l ++]; if ( r & 1 ) s += t [-- r ]; } return s ; } }; Note Why 4n (or 2·pow2 ), and why the iterative version is 2n The recursive layout needs up to 4n nodes because ranges split unevenly; the bottom-up layout packs a complete tree of pow2 leaves into 2n cells with no gaps. The iterative version is faster (no recursion, no function calls), needs no build recursion and no push logic — and it cannot do lazy propagation, which is the only reason to prefer the recursive one. Everything is the same code with a different combine combine query extra needed + range sum lazy for range add min / max range min/max lazy for range add (shift both) gcd range gcd gcd(a_l, suffix differences) — the standard trick count of ones \"first position with value ≥ x\" descend by comparing t[2v] matrix linear recurrences Linear Recurrences from Graphs and Matrices or of bitsets reachability O(n^2/64) memory, careful # Lazy propagation, without the folklore Note The invariant that makes lazy correct t[v] is always the correct answer for v 's range as if all pending updates on the path to v were applied ; lazy[v] is what still has to be pushed to v 's children. cpp segtree-lazy.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 struct Lazy { int n ; vector < ll > t , lz ; Lazy ( int m ) { n = 1 ; while ( n < m ) n *= 2 ; t . assign ( 2 * n , 0 ); lz . assign ( 2 * n , 0 ); } void apply ( int v , int l , int r , ll x ) { t [ v ] += x * ( r - l ); if ( r - l > 1 ) lz [ v ] += x ; } void push ( int v , int l , int r ) { if (! lz [ v ]) return ; int m = ( l + r ) >> 1 ; apply ( 2 * v , l , m , lz [ v ]); apply ( 2 * v + 1 , m , r , lz [ v ]); lz [ v ] = 0 ; } void upd ( int v , int l , int r , int ql , int qr , ll x ) { if ( r <= ql || qr <= l ) return ; if ( ql <= l && r <= qr ) return apply ( v , l , r , x ), void (); push ( v , l , r ); int m = ( l + r ) >> 1 ; upd ( 2 * v , l , m , ql , qr , x ); upd ( 2 * v + 1 , m , r , ql , qr , x ); t [ v ] = t [ 2 * v ] + t [ 2 * v + 1 ]; } ll ask ( int v , int l , int r , int ql , int qr ) { if ( r <= ql || qr <= l ) return 0 ; if ( ql <= l && r <= qr ) return t [ v ]; push ( v , l , r ); int m = ( l + r ) >> 1 ; return ask ( 2 * v , l , m , ql , qr ) + ask ( 2 * v + 1 , m , r , ql , qr ); } }; Watch out The three bugs that actually happen Forgetting push before descending in both upd and ask — the children then answer with stale values while the parent looks right. Applying the update to t[v] without multiplying by the length (sum) or by the count of affected elements (chmax/chmin need the \"second maximum\" trick, not a length). Using one lazy value for two different operations (add and assign): assign must override , so lz needs a tag type plus a \"set\" flag, and the ordering is set-then-add, never the reverse. Example Segment tree beats, in one sentence For \"range chmin + range sum/max\", store max and second-max per node; a chmin(x) with second-max < x ≤ max only touches the top value, so you can update it in O(1) and push lazily — amortised O(log^2 n) per operation. The general lesson: sto", "w": 933, "h": [["Lazy propagation, without the folklore", "lazy-propagation-without-the-folklore"]]}, {"u": "/structures/trie/", "t": "Trie (Prefix Tree) and Its Relatives", "s": "Bitwise tries, binary tries for xor, suffix automaton neighbours, and the Aho–Corasick step up.", "c": "Data Structures on Trees", "k": "trie strings xor core", "b": "Definition A rooted tree whose edges are labelled by alphabet symbols, such that each stored string is the label of a root-to-node path. Sharing prefixes is the entire point: total nodes ≤ total length of all strings + 1. cpp trie.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct Trie { static const int A = 26 ; vector < array < int , A >> ch ; vector < int > cnt ; Trie () : ch ( 1 ), cnt ( 1 ) {} // node 0 = root void insert ( const string & s ) { int v = 0 ; for ( char c : s ) { int x = c - 'a' ; if (! ch [ v ][ x ]) { ch . push_back ({}); cnt . push_back ( 0 ); ch [ v ][ x ] = ( int ) ch . size () - 1 ; } v = ch [ v ][ x ]; cnt [ v ]++; } } int count_prefix ( const string & p ) { // how many words start with p int v = 0 ; for ( char c : p ) { if (! ch [ v ][ c - 'a' ]) return 0 ; v = ch [ v ][ c - 'a' ]; } return cnt [ v ]; } }; Watch out Memory is the constraint, not time array<int,26> per node is 104 bytes; 10^6 nodes is 104 MB — over most limits. Three fixes, in order of preference: compress the alphabet : map symbols to [0,σ) first (letters → 26, digits → 10, or bits → 2), map / unordered_map per node: slower and worse memory unless the branching factor is genuinely ~1, store edges in one flat array of (node, char, child) sorted by (node,char) and walk with a pointer: cache-friendly and σ -free. # The bitwise trie: max xor in O(30) Key idea Greedy on bits To maximise x ⊕ y over a set S , walk from the highest bit down and always take the child whose bit is ¬ of x 's bit when it exists. This is optimal because the highest differing bit dominates: 2^k > ∑_i<k 2^i . cpp xor-trie.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct Node { int ch [ 2 ]; }; vector < Node > t ( 1 ); void insert ( int x ) { int v = 0 ; for ( int i = 30 ; i >= 0 ; i --) { int b = x >> i & 1 ; if (! t [ v ]. ch [ b ]) { t [ v ]. ch [ b ] = ( int ) t . size (); t . push_back ({}); } v = t [ v ]. ch [ b ]; } } int query ( int x ) { // max x ^ y over inserted y int v = 0 , res = 0 ; for ( int i = 30 ; i >= 0 ; i --) { int b = x >> i & 1 ; if ( t [ v ]. ch [ b ^ 1 ]) { res |= 1 << i ; v = t [ v ]. ch [ b ^ 1 ]; } else v = t [ v ]. ch [ b ]; } return res ; } Applications that are \"just\" this: maximum xor subarray (insert prefix xors — a_l ⊕ … ⊕ a_r is a prefix xor pair), maximum xor pair in a set, and with a persistent xor trie you get \"max xor in a range [l,r] \" in O(30) per query (CSES-style \"XOR Queries\" family). # Autocomplete, deletion, and counts The three bookkeeping rules store cnt[v] = number of words passing through v , and end[v] = multiplicity of the word ending exactly here, deletion = decrement along the path and optionally free the leaf-ward suffix — with cnt you can prune empty subtrees so memory stays O(distinct prefixes) , prefix listing : DFS from the node of the prefix; output order is lexicographic for free, which is why tries sort strings in O(total length) . Example When a trie is the wrong tool \"Does any pattern occur in the text?\" over many patterns needs Aho–Corasick : build the trie, add failure links ( fail[v] = longest proper suffix of v 's path that is a node — computed by BFS from the root, same recurrence as KMP's prefix function), then a single text scan gives all occurrences in O(|text| + ∑|patterns| + matches) . \"Longest repeated substring / distinct substrings\" is suffix-array or suffix-automaton territory, not trie. G B a C b E am B->E m D s F ba C->F a G be C->G e H so D->H o I bad F->I d A A->B a A->C b A->D s Figure 1 Four words, seven nodes: the shared prefixes are the whole saving, and cnt[] on each node is what makes prefix queries O(|prefix|). Problems for this page all core hard CSES 1731 Word Combinations core trie + DP over prefixes CSES 1753 String Matching core KMP / automaton, the trie's cousin CSES 2102 Finding Patterns hard many patterns, many texts", "w": 682, "h": [["The bitwise trie: max xor in O(30)", "the-bitwise-trie-max-xor-in-o30"], ["Autocomplete, deletion, and counts", "autocomplete-deletion-and-counts"]]}, {"u": "/structures/sparse-table/", "t": "Sparse Table and RMQ", "s": "O(1) range minimum queries on idempotent operations, with the log table that makes the proof two lines.", "c": "Data Structures on Trees", "k": "RMQ static sparse table core", "b": "Definition st[j][i] = min of the 2^j elements starting at i . Query [l, r] : take k = ⌊ log_2 (r - l + 1) ⌋ and answer min(st[k][l], st[k][r - 2^k + 1]) — two overlapping blocks. cpp sparse-table.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 vector < int > lg ( n + 1 ); for ( int i = 2 ; i <= n ; i ++) lg [ i ] = lg [ i / 2 ] + 1 ; int K = lg [ n ] + 1 ; vector < vector < int >> st ( K , vector < int >( n )); st [ 0 ] = a ; for ( int j = 1 ; j < K ; j ++) for ( int i = 0 ; i + ( 1 << j ) <= n ; i ++) st [ j ][ i ] = min ( st [ j - 1 ][ i ], st [ j - 1 ][ i + ( 1 << ( j - 1 ))]); auto ask = [&]( int l , int r ) { // inclusive int k = lg [ r - l + 1 ]; return min ( st [ k ][ l ], st [ k ][ r - ( 1 << k ) + 1 ]); }; Theorem Why overlap is allowed min is idempotent ( min(x,x) = min(x) ) and associative, so counting an element twice changes nothing; and 2^k ≤ r-l+1 < 2^k+1 guarantees the two blocks cover the whole interval. Which operations work, and which do not work : min , max , gcd , bitwise and , bitwise or (all idempotent), do not work : + (double counting), xor (cancels!), parity of a count — for these use prefix sums or a segment tree, count of minimum / \"leftmost minimum\" : works with a custom combine that keeps (value, tie-break) ; the idempotence requirement is on the whole pair , so store which side wins consistently. Note Costs, honestly Build O(n log n) time and memory: at n = 10^6 that is 2 × 10^7 ints = 80 MB — plan for it or switch to a segment tree ( O(n) build, O(log n) query) or to the Cartesian-tree + Euler ±1-RMQ linear solution ( LCA via Range Minimum Query ) when n is huge and queries are many. # Beyond min: three standard upgrades Example 1. Static range sum, O(1) Prefix sums: O(n) memory, O(1) query — strictly better than a sparse table, because + is invertible. The general rule: invertible → prefix sums; idempotent → sparse table; neither → segment tree. Example 2. Next greater element, in O(1) after O(n) \"First position to the right with value > a[i] \" is not an aggregate over a set but a search. Precompute nxt[i] with a monotone stack ( O(n) ), then binary lift over nxt for \"the k -th next greater\", O(log n) per query. The stack handles the structure, lifting handles the repetition — a pattern that recurs in Functional and Permutation Graphs and LCA by Binary Lifting . Example 3. Sparse table over a monoid, for 'is this range periodic' st[j][i] with combine = \"hash of the concatenation\" and non -overlapping queries gives you substring hashes at fixed length 2^j in O(1) — the standard building block for LCP queries and \"count distinct rotations\" problems. Problems for this page all warm-up core CSES 1647 Static Range Minimum Queries warm-up direct application CSES 1137 Subtree Queries core Euler order + static vs dynamic contrast", "w": 511, "h": [["Beyond min: three standard upgrades", "beyond-min-three-standard-upgrades"]]}, {"u": "/complexity/classes/", "t": "P, NP, and Reductions — What You Actually Need", "s": "The definitions in contest language, how a reduction is written, and what \"NP-complete\" does and does not forbid.", "c": "Hardness and Escape Routes", "k": "complexity reductions core", "b": "Definition **P**: decidable in time polynomial in the input length. NP : every yes instance has a certificate verifiable in polynomial time. (Vertex cover: the certificate is the set itself; check size and coverage in O(n+m) .) NP-complete : in NP, and every NP problem reduces to it. If any NP-complete problem is in P, then P = NP. Note What the definition does not say It does not say a problem is slow for the given n . \"Vertex cover with n ≤ 20 \" is trivial; \"with n ≤ 10^5 and the graph is a tree\" is a 6-line DP ( Trees: Six Definitions of One Object ). NP-completeness is a statement about uniform algorithms across all inputs. A contest problem is never \"solve NP-complete instances fast\" — it is \"solve this NP-complete problem under a promise, or exactly because n is small, or approximately\". # Reductions, mechanically To show B is NP-hard: pick a known NP-complete A , and give a polynomial map f with x ∈ A ⇔ f(x) ∈ B . Direction matters: you reduce the known-hard problem to the new one. Then a fast algorithm for B would give one for A . Example Independent set ⇄ vertex cover (the cleanest pair) S is an independent set ⇔ V ∖ S is a vertex cover. So α(G) + τ(G) = n , and an algorithm for either solves the other with the same running time — a reduction that is one line and also a theorem. Note it converts maximisation to minimisation , which is why the \"minimum\" version is the one usually stated as complete. Example 3-SAT → 2-SAT is impossible (unless P = NP), and what you can do instead A reduction must keep the problem class: 2-SAT is in P ( 2-SAT ), so no polynomial reduction from 3-SAT to it exists unless P = NP. If a problem looks like 3-SAT but each variable appears in at most 2 clauses, that is a promise you can exploit: the constraint graph has max degree 2, so it is paths and cycles and can be solved by DP — the typical shape of a \"3-SAT that is actually easy\" problem. Four habits that make reduction-writing fast Prove membership in NP first : if you cannot state the certificate, you are aiming at the wrong class (NP-hard vs FP vs search), choose the source problem by structure match : numbers → Subset Sum / Partition; sets and conflicts → Independent Set / Clique; assignment → 3-SAT; ordering → Betweenness; partition into groups → 3-D Matching, keep the construction local : gadgets of constant size, with the \"iff\" checked in both directions for each gadget, finish by stating what the reduction forbids : \"hence no polynomial exact algorithm unless P = NP\" — and then immediately ask which escape route the constraints suggest ( Escape Routes: What To Do When It Is NP-Hard ). Watch out Two confusions that cost real points NP-hard vs NP-complete : optimisation versions (minimum vertex cover) are NP-hard but not decision problems, so they are not \"NP-complete\" strictly speaking. Say \"NP-hard\" and be safe. decision vs search : a decision oracle for \" τ(G) ≤ k ?\" gives the actual set with n extra queries (binary search then greedy inclusion), so algorithmically the two are equivalent up to a factor n . Knowing this saves you from \"but my problem asks for the set\" panic. Exercise Three reductions to write out in full Clique ≤_m Independent Set via the complement graph — state the gadget and both directions of the equivalence. Explain why the identity α(G) + τ(G) = n turns a maximisation into a minimisation , and why the analogous statement for matchings ( ν(G) ≤ τ(G) , with equality only for bipartite graphs per Kőnig's Theorem and Minimum Covers ) gives you an algorithm on bipartite graphs but not on general ones. Subset Sum ≤_m \"partition a tree's edge weights so both halves have equal sum\" — build the tree as a star and note what the reduction costs in bits.", "w": 674, "h": [["Reductions, mechanically", "reductions-mechanically"]]}, {"u": "/complexity/npc-graphs/", "t": "The NP-complete Graph Problems Worth Knowing", "s": "A table of the classics with their reduction sources, plus the exact promise that makes each one easy again.", "c": "Hardness and Escape Routes", "k": "NP-complete recognition core", "b": "In a contest you never prove hardness; you recognise it in 20 seconds and then read the constraints to find the escape route. This page is the recognition table. problem decide / optimise source of hardness polynomial when… Hamiltonian cycle / path decision 3-SAT (via gadget circuits), or from TSP graph is a DAG ( DAGs and Topological Order ), degree-2 chains, bounded treewidth, n ≤ 20 ( Held–Karp: Hamilton in O(2ⁿn²) ) Travelling salesman optimisation Hamiltonian cycle (set weights 1/2) metric (2-approx, Christofides 3/2 ), n ≤ 20 , small treewidth Vertex cover min Independent set / 3-SAT bipartite (Kőnig, Kőnig's Theorem and Minimum Covers ), tree (DP), FPT in k : O(1.2738^k + kn) Independent set / Clique max 3-SAT, and each other via complement perfect graphs, bipartite (= n − matching), interval graphs Graph colouring ( k -colouring, k ≥ 3 ) decision 3-SAT k = 2 = bipartiteness ( Bipartite Graphs and 2-Colouring ), chordal (greedy on perfect elimination order), planar (four colour theorem is decision-free : always yes for k=4 ) Subgraph isomorphism / motif decision CLIQUE (as special case) pattern is a tree (DP), pattern size ≤ 4 (colour-coding) Feedback vertex / arc set min Vertex cover, 3-SAT DAG already ( 0 ), bipartite tournaments, bounded treewidth Bandwidth / minimum degree spanning tree opt Hamiltonian path / exact-degree degree bound 2 = Hamiltonian; else NP-hard, use MST-style heuristics Max-cut opt Not-All-Equal 3-SAT bipartite ( = all edges), planar dual ↔ perfect matching (exact! via General Matching and Blossoms ) Densest k -subgraph opt CLIQUE k tiny, or approximation O(√n) Steiner tree opt Exact cover / SAT terminals ≤ 15 : Dreyfus–Wagner O(3^t n + 2^t nlog n) Disjoint paths (2 pairs) decision — polynomial for fixed number of pairs (Robertson–Seymour), NP-complete when the number is part of input Edge-disjoint paths max number opt — it is a flow problem → poly ( Max Flow: Ford–Fulkerson, Dinic, Push–Relabel ). Recognising this one is a free 30 minutes Partition into triangles / exact cover by 3-sets decision X3C every vertex degree ≤ 2 → paths/cycles Chromatic polynomial evaluation at k ≥ 3 #P — treewidth small: transfer DP along a tree decomposition Note Three that look hard but are not 2-SAT is linear ( 2-SAT ) — one implication graph and SCCs, bipartite matching / vertex cover / max flow / min-cost flow are polynomial (chapters Matching: Definitions and Duality and Cuts and Flows ), and a lot of \"hard-looking\" contest problems are these with a modelling layer on top, cycle detection, topological order, bridges, articulation points, SCC, Euler tours are all O(n+m) — statements like \"find a permutation satisfying pairwise constraints\" are DAGs and Topological Order , not SAT. Example Reading a problem's constraints as a confession n ≤ 20 and \"visit all cities\" → Held–Karp. n ≤ 40 → meet in the middle. \"each number appears at most twice\" → degree-2 structure. k ≤ 15 in \"choose k vertices\" → brute force with bitsets or colour-coding. Sum of n over tests ≤ 3 · 10^5 and \"tree\" in the statement → the intended solution is a tree DP and the NP-hard-looking part is a red herring. This is the single most transferable contest skill in the book: the constraints are the intended algorithm's complexity, printed in the statement. Watch out The trap of 'but maybe my greedy works' Vertex cover by \"take the highest-degree vertex\" is a ln n -approximation and can be Ω(log n) off; maximal matching gives a clean 2-approximation ( The Extremal Principle ). If you cannot prove your greedy is exact, submit the one with a provable factor — problems that ask for any answer within a bound are common precisely because exact is hopeless. Problems for this page all hard CF 1198C Matching vs Independent Set hard the reduction is the solution: greedily take edges, the leftovers are independent CSES 2181 Counting Tilings hard the \"hard\" problem with a small width promise", "w": 644, "h": []}, {"u": "/complexity/escape/", "t": "Escape Routes: What To Do When It Is NP-Hard", "s": "Small n, meet in the middle, branch and bound, FPT by a parameter, and approximation — with the running-time arithmetic for each.", "c": "Hardness and Escape Routes", "k": "exponential DP branch and bound hard", "b": "Five techniques, ranked by how the constraints tell you which one is intended. # 1. DP over subsets — n ≤ 22 dp[M][v] : 2^n · n states, O(n) (or O(deg) ) each. cpp subset-dp.cpp Copy 1 2 3 4 5 6 for ( int M = 1 ; M < ( 1 << n ); M ++) for ( int v = 0 ; v < n ; v ++) if ( M >> v & 1 ) { int P = M ^ ( 1 << v ); for ( int u = 0 ; u < n ; u ++) if ( P >> u & 1 && adj [ v ][ u ]) dp [ M ][ v ] = min ( dp [ M ][ v ], dp [ P ][ u ] + w ( u , v )); } Arithmetic to check first: 2^22 · 22 ≈ 9 · 10^7 states — fine; 2^26 · 26 ≈ 1.7 · 10^9 — too slow and memory 2^26 · 26 bytes = 1.7 GB, too big. Memory is usually the binding constraint: iterate masks and keep only dp[mask][v] as int (4 bytes), or drop the [v] dimension entirely when the transition depends only on M (then O(2^n) memory). Variants of the same 8 lines Hamiltonian path/cycle and TSP ( Held–Karp: Hamilton in O(2ⁿn²) ), assignment problem with n ≤ 20 (faster than Hungarian for tiny n ), \"minimum number of groups with a constraint on each\" — iterate subsets, dp[M] = min over sub ⊆ M , which is O(3^n) total: enumerate submasks with for (sub = M; sub; sub = (sub-1) & M) , graph colouring in O(2^n n) : precompute which subsets are independent, then \"cover V by ≤ k independent sets\" = subset DP, or use inclusion–exclusion for the chromatic polynomial, Steiner tree: dp[M][v] over terminal subsets with Dijkstra between layers, O(3^t n + 2^t n log n) . # 2. Meet in the middle — n ≤ 44 Split into two halves, enumerate 2^n/2 subsets each, then combine by sorting + two pointers or binary search. cpp meet-in-middle.cpp Copy 1 2 3 4 5 6 7 8 // subset sum to target T with n <= 40 vector < ll > A , B ; // all 2^(n/2) subset sums of each half sort ( B . begin (), B . end ()); ll best = - 1 ; for ( ll a : A ) { auto it = lower_bound ( B . begin (), B . end (), T - a ); if ( it != B . end () && a + * it == T ) { best = T ; break ; } } 2 · 2^20 = 2 · 10^6 elements instead of 2^40 — the entire trick is that a sorted list can be searched, so pairs become cheap. # 3. Branch and bound / backtracking with a real prune — n ≤ 40 – 60 on nice tests The prunes that matter, in order of value: 1 order the branching so that the strongest constraint is decided first (largest degree, smallest domain); 2 bound with a fast relaxation (LP-free: fractional knapsack bound, MST bound for TSP, greedy colour count) and cut the subtree when the bound cannot beat the incumbent, 3 forward checking : after assigning, remove impossible values; fail immediately on an empty domain, 4 memoise on a canonical state (a bitmask of decided vertices) — the moment you memoise, branch and bound becomes DP and the bound stops mattering, 5 restart / randomise the order if a fixed order blows up on one adversarial test. Example Maximal independent set in a graph with n = 50 Branch on a vertex v : either take it (delete N[v] ) or forbid it (delete v ). Recurrence T(n) = T(n-1) + T(n-d-1) ; with d ≥ 2 that is ≈ 1.32^n . Measure-and-conquer analyses push it to 1.1996^n by handling low-degree vertices with dedicated rules — which is exactly what the \"branch on the degree-2 vertex and contract it\" line in real code is doing. # 4. FPT in a parameter you noticed \"Exponential in k , polynomial in n \" — kernelise then branch: vertex cover: bounded search tree O(2^k k) , or O(1.2738^k + kn) with branching rules on degree-3+ vertices; LP relaxation gives a 2k -vertex kernel, treewidth k : solve any MSO/DP problem in O(f(k) n) once you have a tree decomposition (and for planar graphs, k = O(√n) , so \"exponential in √n \" is a real planar algorithm), dominating set on planar graphs: O(2^O(√n) n) via the same, \"at most k edges to delete to become bipartite\": iterative compression or O(4^k · n) . # 5. Approximation and randomisation Provably-good answers you can code in 5 lines maximal matching → 2-approx for vertex cover and for maximum matching ( The Extremal Principle ), random partition, keep the better side → ≥ m/2 edges in a bipartite subgraph; de", "w": 930, "h": [["1. DP over subsets —", "1-dp-over-subsets-n-le-22"], ["2. Meet in the middle —", "2-meet-in-the-middle-n-le-44"], ["3. Branch and bound / backtracking with a real prune — – on nice tests", "3-branch-and-bound-backtracking-with-a-real-prune-n-le-4060-"], ["4. FPT in a parameter you noticed", "4-fpt-in-a-parameter-you-noticed"], ["5. Approximation and randomisation", "5-approximation-and-randomisation"]]}, {"u": "/lca/problem/", "t": "The LCA Problem", "s": "What the lowest common ancestor is, the seven distances it answers, and how to choose among four algorithms.", "c": "Lowest Common Ancestor", "k": "trees queries core", "b": "Definition Root the tree at r . The lowest common ancestor lca(u,v) is the deepest vertex that is an ancestor of both. It is the unique vertex where the paths r → u and r → v stop agreeing, and the junction of the three paths u , v , r . Everything one LCA answers dist(u,v) = depth(u) + depth(v) - 2depth(lca) , dist on an edge-weighted tree: same formula with prefix sums from the root, the k -th vertex on the path u ⇝ v (walk up from u , then from v — LCA by Binary Lifting ), lca of a set S (the \"virtual tree\" root) = the vertex minimising ∑_x ∈ S dist(x, y) over S 's Steiner closure — computed from lca of the min- and max- tin elements ( Virtual Trees ), whether u is an ancestor of v : lca(u,v) = u , or the cheaper tin / tout test ( Entry/Exit Times and the Euler Tour ), \"jump j steps up from v \", \"the node just below the LCA on the path\", \"the centroid of a path\", subtree-union queries: lca gives the boundary vertex, then a Fenwick Tree (Binary Indexed Tree) over tin gives the count. # Choosing an algorithm method preprocess query memory offline? notes naive climb O(n) O(n) worst O(n) — fine on balanced trees; a path graph kills it binary lifting O(n log n) O(log n) O(n log n) no the default; also does \"jump k steps\" Euler tour + sparse table O(n log n) O(1) O(n log n) no LCA via Range Minimum Query Euler tour + ±1 RMQ (Bender–Farach-Colton) O(n) O(1) O(n) no LCA via Range Minimum Query , the \"too clever\" row Tarjan offline — O(n + q α) total O(n+q) yes Tarjan's Offline LCA ; DSU only Heavy-light O(n log n) or O(n) O(log n) O(n) no pays off when you need path aggregates anyway ( Heavy-Light Decomposition ) Note The honest recommendation Write binary lifting. It is 12 lines, never wrong, gives you k -th ancestor for free, and O(n log n) with n = 2 × 10^5 is 3.6 million integers — 14 MB, well inside any limit. Move to O(1) RMQ only when q ≥ 10^6 and the constant actually shows up in the clock, or to Tarjan when the queries are offline and you already have DSU in the solution. # Two conventions that decide your code's shape Pick once, write in a comment root : fix r = 0 (or 1) and never change it; \"depth\" is measured from it, lca(v,v) = v — the identity case must be handled by the algorithm, not by an if at the call site, 0-indexed up[v][0] = parent(v) , with parent(r) = r (self-loop at the root) is the least error-prone: it makes lifting past the root idempotent instead of returning -1 that you must check everywhere, log bound: LOG = __lg(n) + 1 (or 20 for n ≤ 10^6 , 19 for 10^5 … compute it, don't guess), forest : if the input is not connected, LCA is undefined across components — either assert same-component with DSU first, or add a super-root (which makes every pair comparable and often simplifies the code). G A 1 B 2 A->B C 3 A->C D 4 A->D E 5 B->E F 6 B->F G 7 B->G H 8 C->H I 9 C->I J 10 C->J K 11 D->K L 12 D->L M 13 D->M N 14 E->N O 15 E->O P 16 E->P Figure 1 A rooted tree: lca of the two deepest leaves is their branch point, and depth differences are what the lifting steps below measure.", "w": 592, "h": [["Choosing an algorithm", "choosing-an-algorithm"], ["Two conventions that decide your code's shape", "two-conventions-that-decide-your-codes-shape"]]}, {"u": "/lca/binary-lifting/", "t": "LCA by Binary Lifting", "s": "The 12-line precomputation, the two-phase query, and the family of problems it solves beyond LCA.", "c": "Lowest Common Ancestor", "k": "lifting queries trees core", "b": "Definition up[v][j] = the 2^j -th ancestor of v . Then up[v][j] = up[up[v][j-1]][j-1] — \"halfway up, then halfway again\", which is the whole idea. cpp binary-lifting.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const int LOG = 20 ; // 2^20 > 10^6 vertices vector < array < int , LOG >> up ( n ); vector < int > depth ( n ); void dfs ( int v , int p ) { up [ v ][ 0 ] = p == - 1 ? v : p ; for ( int j = 1 ; j < LOG ; j ++) up [ v ][ j ] = up [ up [ v ][ j - 1 ]][ j - 1 ]; for ( int to : g [ v ]) if ( to != p ) { depth [ to ] = depth [ v ] + 1 ; dfs ( to , v ); } } int lift ( int v , int k ) { // k-th ancestor of v (k <= depth[v]) for ( int j = 0 ; j < LOG ; j ++) if ( k >> j & 1 ) v = up [ v ][ j ]; return v ; } int lca ( int a , int b ) { if ( depth [ a ] < depth [ b ]) swap ( a , b ); a = lift ( a , depth [ a ] - depth [ b ]); // phase 1: equalise depths if ( a == b ) return a ; for ( int j = LOG - 1 ; j >= 0 ; j --) // phase 2: descend together if ( up [ a ][ j ] != up [ b ][ j ]) { a = up [ a ][ j ]; b = up [ b ][ j ]; } return up [ a ][ 0 ]; // one step above both } Theorem Correctness After phase 1, depth(a) = depth(b) , and lca(a,b) = lca(original a, original b) is unchanged (lifting an ancestor-side vertex to the other's depth cannot pass the LCA). In phase 2 the invariant is \" a and b have equal depth and lca(a,b) is a proper ancestor of both\"; each accepted jump preserves it, and after the loop a and b are the two children just below the LCA. Proof Phase 1: lifting a to depth depth(b) keeps the LCA because depth(lca) ≤ depth(b) , so we stop at or below it. Phase 2: if up[a][j] ≠ up[b][j] , both those ancestors are strictly below the LCA (otherwise, having equal depth and one common ancestor at that level, they would coincide) — so the jump cannot overshoot. Conversely, for every j with up[a][j] = up[b][j] the LCA is at or above that ancestor, so skipping those jumps loses nothing. Descending j from LOG-1 to 0 therefore accumulates exactly depth(a) - depth(lca) - 1 on each side. ∎ Note Iterate j from high to low, and say why Largest-jump-first is a binary representation greedy: depth difference = ∑_j b_j 2^j , so taking big jumps whenever they fit is exactly reading the bits. Low-to-high also works for lift (it is the same sum) but not for phase 2 — the invariant \"still strictly below the LCA\" is only maintained when you never overshoot, which requires descending powers. This asymmetry is the bug in most broken implementations. # Distances and the k-th vertex on a path cpp path-tools.cpp Copy 1 2 3 4 5 6 7 8 int dist ( int a , int b ) { return depth [ a ] + depth [ b ] - 2 * depth [ lca ( a , b )]; } // k-th vertex on the simple path a -> b (0 = a), k <= dist(a,b) int kth_on_path ( int a , int b , int k ) { int c = lca ( a , b ), up_len = depth [ a ] - depth [ c ]; return k <= up_len ? lift ( a , k ) : lift ( b , dist ( a , b ) - k ); } // vertex just below c on the path c -> x (needed for \"which subtree contains x\"): int below ( int c , int x ) { return lift ( x , depth [ x ] - depth [ c ] - 1 ); } The same table answers far more k -th ancestor / \"who is d steps up from v \" — lift , O(log n) , diameter of a set of vertices: lift-based farthest-pair queries reduce to max over a set with LCA distances, jump through a functional graph with cycle handling ( Functional and Permutation Graphs ) — identical code, up[v][0] = f[v] , binary lifting on a dynamic DSU (\"successor after deletions\", \"the day two nodes became connected\" — offline parallel binary search), max edge on a path : add a mx[v][j] array alongside up , combining with max — this is the same table, one more dimension, and it is the reason Heavy-Light Decomposition is only needed when updates are involved, tree isomorphism-ish queries and \"is u within distance K of v \" reduce to two lifts. Both phases visible: the depth-equalising lifts, then the pairs of jumps that stop one level below the answer. Toggle u and v to find a case where phase 2 does noth", "w": 893, "h": [["Distances and the k-th vertex on a path", "distances-and-the-k-th-vertex-on-a-path"]]}, {"u": "/lca/rmq-lca/", "t": "LCA via Range Minimum Query", "s": "Reduce LCA to RMQ on an Euler tour, answer in O(1), and see why the reduction is exact.", "c": "Lowest Common Ancestor", "k": "sparse table reduction hard", "b": "Theorem The reduction List the vertices in the order a DFS re-visits them (append v on entry and after each child returns): E has length 2n-1 . For u ≠ v , lca(u,v) = arg min_i ∈ [pos[u], pos[v]] depth(E[i]), pos[x] = first index of x . Proof Between the first appearances of u and v , the tour walks up from u to their common ancestor and down to v ; it cannot go above lca(u,v) (that vertex separates them) and must visit it (the only way to get from one side to the other). Depth is minimal exactly at the LCA among visited vertices, and every vertex on the up-path has depth ≥ its depth. Hence the minimum over the interval is the LCA. The depth of the LCA is attained, so the argmin works even with ties broken arbitrarily (no vertex above the LCA is present, and a tie between two occurrences of the LCA is harmless). ∎ cpp euler-rmq-lca.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 vector < int > E , dep , pos ( n , - 1 ); void tour ( int v , int p , int d ) { pos [ v ] = E . size (); E . push_back ( v ); dep . push_back ( d ); for ( int to : g [ v ]) if ( to != p ) { tour ( to , v , d + 1 ); E . push_back ( v ); dep . push_back ( d ); } } // Sparse table over dep[], storing the index of the minimum: build O(m log m), query O(1) int lca ( int u , int v ) { if ( pos [ u ] > pos [ v ]) swap ( u , v ); int l = pos [ u ], r = pos [ v ], k = __lg ( r - l + 1 ); return E [ rmq ( l , r , k )]; // rmq = argmin over [l, l+2^k) vs [r-2^k+1, r] } Here m = 2n-1 , so the preprocess is O(n log n) and memory O(n log n) — the same order as binary lifting but with O(1) instead of O(log n) queries, because min is idempotent and the overlapping blocks are allowed ( Sparse Table and RMQ ). Watch out Two implementation traps pos must be the first occurrence. Using the last (or any) breaks the interval, since the tour between two occurrences of u may dip below and above v 's ancestor chain. The RMQ compares depths , not vertex ids: taking min E[i] is wrong, and the tree must be the one the tour was built on. # Linear time, if you really want it The tour's depth array changes by exactly ± 1 between neighbours — a ±1 RMQ , which is strictly easier than general RMQ: Cut E into blocks of size b = ⌊ log n / 2 ⌋ . A block's shape is determined by 2^b-1 up/down steps — O(n) blocks but only 2^b-1 = O(√n) distinct types . Precompute the in-block answer for every type by brute force: O(√n · b^2) = O(n) . Sparse table over block minima: O((n/b) log(n/b)) , query O(1) . Answer a query as (suffix of block of l ) ⊕ (whole blocks between) ⊕ (prefix of block of r ) using the type table. This is Bender–Farach-Colton: O(n) preprocess, O(1) query, O(n) space — the theoretical optimum. Note Do not do this in a contest The O(1) -query advantage is worth ~4–6× over binary lifting only when q ≳ 10^6 and you are memory-bound rather than compute-bound; the general sparse table variant is already O(1) with 10 lines and no case analysis. If you are offline with no updates, Tarjan's algorithm ( Tarjan's Offline LCA ) is O((n+q)α) with less memory than either, and is the actual fastest option in practice for large q . Why the reduction matters beyond LCA It is the canonical example of reducing a tree problem to a sequence problem : the same move turns subtrees into intervals ( Entry/Exit Times and the Euler Tour ) and makes dynamic trees hard (linking changes the tour). It proves LCA ≤_m RMQ, and RMQ ≤_m LCA too (via Cartesian trees): the two problems are equivalent , so a linear-time RMQ and a linear-time LCA preprocess stand or fall together. Cartesian tree construction is the reduction in the other direction, and it is 8 lines with a stack ( Segment Tree ).", "w": 716, "h": [["Linear time, if you really want it", "linear"]]}, {"u": "/lca/tarjan-offline/", "t": "Tarjan's Offline LCA", "s": "Answer all LCA queries in near-linear time with a DFS and a disjoint-set union — no log factor, no table.", "c": "Lowest Common Ancestor", "k": "dsu offline queries hard", "b": "Definition \"All queries are known in advance and may be reordered.\" That licence is what makes this algorithm possible — and what makes it useless when a query depends on an earlier answer, or when vertices are added one by one. Theorem Tarjan's algorithm Run a DFS. When a vertex v finishes, mark it black and union it with each black child, setting the child set's representative to v . For every query (v, u) with u already black, the answer is find(u) . Proof When v is finishing and u is black, u lies entirely inside the finished part of the tree, and the union chain has pulled every black vertex whose \"first not-yet-finished ancestor\" is a = lca(u,v) into a 's set: a vertex is unioned upward exactly when its own DFS finishes, so it stops at the highest ancestor that is still open — and a is still open (it is an ancestor of v ), while everything on the u path below a is closed. No vertex outside a 's subtree is in the set, since such a vertex would have had to be unioned through an ancestor of a , which is not yet finished. So find(u) = a . ∎ cpp tarjan-lca.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector < vector < pair < int , int >>> adj ( n ); // neighbours, tree vector < vector < pair < int , int >>> qs ( n ); // qs[v] = {(other, query id)} vector < int > dsu ( n ), anc ( n ), state ( n ); // state: 0 white, 1 grey, 2 black vector < int > ans ( q ); int find ( int v ) { return dsu [ v ] == v ? v : dsu [ v ] = find ( dsu [ v ]); } void dfs ( int v ) { state [ v ] = 1 ; anc [ v ] = dsu [ v ] = v ; for ( int to : adj [ v ]) if ( state [ to ] == 0 ) { dfs ( to ); dsu [ find ( to )] = v ; anc [ find ( v )] = v ; // pull the child set up to v } state [ v ] = 2 ; // black for ( auto [ to , id ] : qs [ v ]) if ( state [ to ] == 2 ) ans [ id ] = anc [ find ( to )]; } Watch out The one line that breaks people anc[find(v)] = v; must be written after dsu[find(to)] = v; , and it must use find(v) — the representative after the merge. The anc array is the actual answer payload: dsu alone only tracks sets, and find returns an arbitrary member. Forgetting the anc update gives answers that are correct on stars and wrong on paths, which is the worst kind of bug. Complexity and why it is near-linear O(n + q) DFS work plus O((n+q) α(n)) for the find calls: each tree edge causes one union, each query causes at most two finds, with union by size/rank the inverse-Ackermann factor is a constant ≤ 4 for any real n , memory O(n + q) : no log n table (compare LCA by Binary Lifting 's n log n ), stack depth n : iterative version or pthread for n = 10^6 ( Walks, Trails, Paths, Cycles ). Example When offline wins outright \" n,q ≤ 2× 10^6 \", memory 64 MB, and each query is dist(u,v) . Binary lifting needs 2× 10^6 × 21 × 4 B = 168 MB — over the limit. Tarjan needs dsu + anc + state + depth + tin ≈ 40 MB and answers all queries in one pass. This is not hypothetical: it is the standard intended difference on such problems, and the offline constraint is forced on you by memory, not stated in the problem. Note Generalising the trick The pattern \"answer queries at the moment a vertex finishes, using the DSU state of closed subtrees\" is Tarjan's offline framework , and it also solves: closest-pair-of-marked-nodes per subtree, \"for each query vertex, the nearest ancestor satisfying P\" (with a stack instead of DSU), and the offline version of \"smallest subtree containing all query vertices\" ( Virtual Trees ). Whenever a query's answer is determined by an ancestor relationship and everything relevant is already closed, offline DSU beats any table. Problems for this page all warm-up core CSES 2079 Finding a Centroid warm-up the answer is decided at finish time — one DFS, no table CSES 1135 Distance Queries core re-solve it offline: one DFS + DSU instead of a lifting table", "w": 698, "h": []}, {"u": "/lca/hld-lca/", "t": "LCA by Heavy-Light Decomposition", "s": "Climbing chains instead of bits — same asymptotics, half the memory, and the same code you need for path queries.", "c": "Lowest Common Ancestor", "k": "hld queries trees hard", "b": "Definition Decompose the tree into vertex-disjoint heavy paths (each vertex continues the path of its largest-subtree child, Heavy-Light Decomposition ), and store for each vertex its chain's head h[v] and depth. Then: cpp hld-lca.cpp Copy 1 2 3 4 5 6 7 int lca ( int u , int v ) { while ( h [ u ] != h [ v ]) { if ( depth [ h [ u ]] < depth [ h [ v ]]) swap ( u , v ); u = parent [ h [ u ]]; // jump whole chains, always the deeper head } return depth [ u ] < depth [ v ] ? u : v ; } Theorem O(log n) chains, without a log-size table Each time the loop replaces u by parent(h[u]) , the subtree of the new u has size at least twice the subtree of the old u 's chain head: h[u] was a light child of its parent, and a light child has size ≤ half its parent's. Hence at most log_2 n chain jumps. Proof size(h[u]) ≤ tfrac12 size(parent(h[u])) because the parent's heavy child has the largest subtree, so any light child carries at most half of the parent's vertices (the parent itself is the remainder, making it strictly less than half + 1). Composing along the loop gives size ≥ 2^#jumps , so the count is at most log_2 n . Termination is clear (heads strictly ascend), and correctness is the invariant \"the LCA is an ancestor of both current vertices\": the deeper head cannot be above the LCA (an ancestor of u that is deeper than lca is strictly below it), so jumping it is safe. ∎ Binary lifting vs. chain climbing Memory : O(n) vs O(n log n) — one array per vertex vs 20, the reason to prefer HLD when n = 10^6 and memory is tight; Speed : the loop usually runs 1–3 iterations on real trees (the log n bound is worst case), so it is faster than lifting for random trees and slower on adversarial \"all-light\" ones; What lifting does that this cannot : k -th ancestor, \"jump j steps\", max-edge-on-path without a segment tree, functional graphs. Chain heads give you \"the head\" but not \"the j -th up\"; What HLD does that lifting cannot : path aggregates with point updates (the chains are contiguous in pos , so a segment tree over them answers sum/max/min on a path), and subtree updates with lazy propagation; Preprocess : one DFS + one loop, both O(n) — no table build, so HLD's total setup is linear vs nlog n . Note So which one? If the problem is only LCA: binary lifting (shorter, self-contained, gives you lift ). If it is LCA plus anything on paths: HLD, and use its lca as a by-product — writing both is 10 wasted lines. The hybrid \"heavy-light + lifting on the chain forest\" exists (jump between chains with lifting) but almost never wins in contests. Exercise Two small exercises Modify lca above to return dist(u,v) in the same loop, using an additional wsum[v] = root-distance: which comparisons change? Show that \"climb the deeper head\" can be replaced by \"climb the head with larger pos \" only if the HLD numbering assigns pos in DFS order along chains. Find a tree where the naive pos comparison is wrong.", "w": 552, "h": []}, {"u": "/lca/virtual-tree/", "t": "Virtual Trees", "s": "Build the minimal subtree spanning k marked vertices in O(k log k) with a stack and LCA — the trick behind a hundred \"query on a subset of vertices\" problems.", "c": "Lowest Common Ancestor", "k": "stack queries trees lca hard", "b": "Definition Given a rooted tree T and a set S of k marked vertices, the virtual tree widehat T(S) is the tree on the closure overline S = S ∪ {lca(u,v) : u,v ∈ S} , with an edge a → b whenever a is an ancestor of b and no element of overline S lies strictly between them. Path lengths become dist in T , so every question of the form \"answer something about the subgraph induced by the paths between marked vertices\" can be asked on widehat T(S) instead — on O(k) vertices rather than n . Lemma Size and structure |overline S| ≤ 2k - 1 , and every non-marked vertex of widehat T(S) has at least two children. So widehat T(S) has O(k) vertices and edges. Proof Root widehat T(S) ; its leaves are all marked (a leaf that was added as an LCA would have ≥ 2 children by definition). A tree whose m leaves are marked and whose internal nodes have ≥ 2 children has m-1 internal nodes: count edges = nodes - 1 and nodes = m + i with ∑ deg ≥ 2i + m = 2(m+i-1) forcing equality and deg = 2 for every internal node in the \"chain-collapsed\" sense — by induction on m the claim is immediate (merge two sibling subtrees, each LCA added joins at least two components). Hence i ≤ k-1 and |overline S| = k + i ≤ 2k-1 . ∎ # The stack algorithm Key idea Sort S by tin and scan it, maintaining the right spine of the virtual tree built so far. Consecutive elements in tin order share their LCA at the point where the spine must bend; pop until the new vertex fits. cpp virtual-tree.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // vertices[0..k) marked, sorted by tin; uses tin/tout, lca from binary lifting vector < int > build ( vector < int > v ) { sort ( v . begin (), v . end (), [&]( int a , int b ) { return tin [ a ] < tin [ b ]; }); v . erase ( unique ( v . begin (), v . end ()), v . end ()); vector < int > st { v [ 0 ] }, nodes { v [ 0 ] }; for ( size_t i = 1 ; i < v . size (); i ++) { int x = v [ i ], a = lca ( x , st . back ()); if ( a == st . back ()) { st . push_back ( x ); nodes . push_back ( x ); continue ; } while ( st . size () > 1 && depth [ st [ st . size ()- 2 ]] >= depth [ a ]) { add_edge ( st [ st . size ()- 2 ], st . back ()); // edge of the virtual tree st . pop_back (); } if ( st . back () != a ) { // a splits the top edge add_edge ( a , st . back ()); st . pop_back (); if ( nodes . empty () || nodes . back () != a ) nodes . push_back ( a ); if ( st . empty () || st . back () != a ) st . push_back ( a ); } st . push_back ( x ); nodes . push_back ( x ); } while ( st . size () > 1 ) { add_edge ( st [ st . size ()- 2 ], st . back ()); st . pop_back (); } return nodes ; // the O(k) vertices, in tin order } Theorem Correctness and cost The algorithm outputs widehat T(S) in O(k log k) time ( O(k log k) for the sort, O(k) for the scan: each vertex is pushed and popped once, and each lca call is O(log n) ). Proof Invariant: st is the path in T from v_0 's side down to the most recently processed vertex, restricted to overline S and read from top to bottom — i.e. the right spine. Processing x with a = lca(x, top) : every spine vertex strictly below a is finished (no later marked vertex can be in its subtree, since tin order means all of a 's subtree containing x comes after, and the processed vertices are in earlier child-subtrees of a ). So edges from those vertices to their spine predecessors are final — emitting them on the way out is correct, and a must be inserted because it is now an LCA of two processed marks. When a is already on the spine, popping to it and pushing x keeps the invariant; when a is new (strictly below the second-from-top), splitting the top edge and inserting a does. Finally, draining the stack closes the remaining spine edges, and the last edge lands on the root of widehat T(S) . For the bound: at most 2k-1 vertices are ever pushed, so O(k) pushes/pops and O(k) emitted edges. ∎ Watch out Four things that break virtual-tree solutions Duplicate marks and marks that are ancestors of other marks: deduplicate before th", "w": 1127, "h": [["The stack algorithm", "the-stack-algorithm"]]}, {"u": "/advanced-tree/mst/", "t": "Minimum Spanning Tree", "s": "Kruskal, Prim, and Borůvka from one lemma; the cut property, the exchange argument, and what each algorithm is actually for.", "c": "Advanced Tree Algorithms", "k": "mst greedy dsu heap core", "b": "# The cut property Definition For a connected weighted undirected graph, a minimum spanning tree is a spanning tree T minimising ∑_e ∈ T w(e) . Write MST(G) for its weight. With equal weights it is just \"any spanning tree\"; with {0,1} weights it is \"the fewest expensive edges\". Theorem The cut property (the only lemma you need) Let ∅ ≠ S ⊊ V and let e = (u,v) be a minimum-weight edge crossing the cut (S, bar S) . Then some MST contains e . If e is the unique lightest crossing edge, every MST contains it. Proof Take any MST T . If e ∈ T , done. Otherwise T + e has exactly one cycle, and that cycle crosses the cut at least twice, so it contains another edge f crossing (S,bar S) with w(f) ≥ w(e) . Then T' = T - f + e is a spanning tree (removing f breaks the only cycle) with wt(T') ≤ wt(T) , so T' is minimum and contains e . If w(f) > w(e) , T was not minimal — contradiction, which proves the uniqueness claim. ∎ Note Every MST algorithm is this lemma, scheduled differently Kruskal considers edges by increasing weight and keeps every edge that connects two components: when it accepts e joining A,B , e is lightest across the cut (cup A, V ∖ cup A) among remaining edges, so the lemma applies to that cut — accept. Prim grows one component S and takes the lightest edge leaving it: exactly the lemma with that S . Borůvka takes, for every component, its lightest outgoing edge: 2^i rounds merge components pairwise, since each accepted edge is the lightest across its component's cut. Reverse-delete (sort decreasing, delete an edge if the graph stays connected, else it is in every MST by the cycle property) is the dual argument. # Kruskal, and why it is the default cpp kruskal.cpp Copy 1 2 3 4 5 6 7 struct Edge { int u , v , w ; }; sort ( e . begin (), e . end (), []( auto & a , auto & b ) { return a . w < b . w ; }); DSU dsu ( n ); long long mst = 0 ; int cnt = 0 ; for ( auto &[ u , v , w ] : e ) if ( dsu . unite ( u , v )) { mst += w ; if (++ cnt == n - 1 ) break ; } // mst is the answer iff cnt == n - 1, else the graph was disconnected # The four algorithms compared The three, compared time needs wins on Kruskal O(m log m) sort + DSU sparse graphs, parallel/external MST, \"second-best MST\", MST of a graph given as an edge list Prim (binary heap) O(m log n) adjacency + priority_queue dense-ish graphs; simple to write Prim (array, no heap) O(n^2) adjacency matrix dense graphs, m = Θ(n^2) — the heap only adds log factors there Borůvka O(m log n) , O(m) with linear-time min-search per round components only the choice when edges are generated implicitly (e.g. nearest-neighbour MST in a metric, Euclidean MST via Voronoi) — each round scans all edges once with no sorting # Two more properties Theorem Two properties worth memorising Cycle property : on any cycle, the heaviest edge (if unique) is in no MST. Proof: exchange it with a lighter edge of the cycle. MST sensitivity / replacement edges : for a non-tree edge e , adding it to T and deleting the heaviest edge on the T -path between its endpoints gives the best tree using e ; so \"the second-best MST\" = minimum of that over non-tree edges, computable in O(m log n) with Heavy-Light Decomposition or O(n^2) with a path-max table. Bottleneck property : an MST minimises the maximum edge on the path between every pair of vertices among all spanning trees, so MST answers minimax-path queries: the lightest possible \"capacity ceiling\" from s to t is the max edge on the MST path (Kruskal also gives you this as \"the weight of the edge that first connected s and t \"). Switch between Kruskal and Prim on the same weighted graph. The rejected edges are the ones the cut property forbids: watch a heavy edge get skipped because its endpoints are already connected — that is the cycle property doing the rejecting. Example Kruskal's real superpower: the union tree While running Kruskal, when edge e merges components A and B , create a new node x with children root(A), root(B) and weight w(e) , and make x the merg", "w": 970, "h": [["The cut property", "the-cut-property"], ["Kruskal, and why it is the default", "kruskal-and-why-it-is-the-default"], ["The four algorithms compared", "the-four-algorithms-compared"], ["Two more properties", "two-more-properties"], ["Directed MST is another problem", "directed-mst-is-another-problem"]]}, {"u": "/advanced-tree/small-to-large/", "t": "Small-to-Large Merging", "s": "Why merging the smaller container into the larger one gives O(n log n) total work, and the dozen problems where that one line is the whole solution.", "c": "Advanced Tree Algorithms", "k": "trees maps amortised hard", "b": "Definition When combining the data of several children into their parent, always iterate over the smaller structure and insert into the largest one (then rename/merge pointers), never the reverse. Theorem Each element moves O(log n) times In a DFS that merges children's sets into the parent's, each vertex (element) is re-inserted at most log_2 n times, so the total number of insertions is O(n log n) , and O(n log^2 n) with std::set / map (log per insertion) or O(nlog n) with unordered_map . Proof When an element is moved, it goes from a container of size a into one of size b ≥ a , so the container holding it at least doubles. Its size can double at most log_2 n times before reaching n . Multiply by the cost of one insertion. ∎ cpp small-to-large.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // \"for every vertex v: how many distinct colours appear in v's subtree?\" vector < unordered_map < int , int >*> cnt ( n ); // colour -> occurrences vector < int > ans ( n ); int dfs ( int v , int p ) { auto best = new unordered_map < int , int >(); int bestsz = 0 , idbest = - 1 ; for ( int to : g [ v ]) if ( to != p ) { int sz = dfs ( to , v ); if ( sz > bestsz ) { bestsz = sz ; idbest = to ; } } if ( idbest != - 1 ) { delete best ; best = cnt [ idbest ]; } // steal the big one (* best )[ colour [ v ]]++; for ( int to : g [ v ]) if ( to != p && to != idbest ) { for ( auto &[ c , k ] : * cnt [ to ]) (* best )[ c ] += k ; // small into large delete cnt [ to ]; cnt [ to ] = nullptr ; } cnt [ v ] = best ; ans [ v ] = best -> size (); return best -> size (); } What the trick solves distinct colours / values per subtree (the above), \"the most frequent colour in each subtree\" (CF 600E Lomsat gelral , the canonical statement), subtree set intersection queries: \"does subtree u contain a vertex of colour c ?\" → answer with maps built once, O(1) per query after O(nlog n) , merging tries: each node's subtree trie built by insertion-merge ⇒ O(n log n · L) for \"maximum xor of two values in the same subtree\", DSU-on-tree ( Small-to-Large Merging ) is the memory-light variant: keep one global array, add/remove subtrees, and exploit the same \"light subtrees are touched log times\" count, polynomial/convolution merging on trees (\"count pairs at distance d in each subtree\"): merging small-to-large turns an O(n^2) DP into O(n log^2 n) . Note Small-to-large vs. DSU on tree vs. centroid memory supports cost merge containers (here) O(n) structures, but allocation-heavy per-vertex answers for all subtrees nlog n inserts, simple DSU on tree (sack) one global array per-vertex answers, with add/remove semantics; easy to also handle \"path to root\" same count, but only O(n) memory and cache-friendly centroid decomposition ( Centroid Decomposition ) O(n log n) pairs across the whole tree with a distance constraint, or global queries nlog n with different bookkeeping Watch out Three ways to lose the log Merging by map::merge /insert loop without first picking the largest child as the base — then the big map is the destination only by accident; the bound is O(n^2) on a path. Copying instead of moving: auto m = *child; silently doubles the work; keep pointers (or std::move ) and always leave the biggest child's container in place. Forgetting that std::map::merge is O(size · log) per element , so total is nlog^2 n — fine at n ≤ 2×10^5 ( ≈ 6×10^7 ), fatal at 10^6 . unordered_map + reserve wins by 3–4×; a global array + DSU-on-tree wins by 10×. Problems for this page all core warm-up CF 600E Lomsat gelral core the standard small-to-large statement CSES 1137 Subtree Queries warm-up the flattening alternative — compare the two approaches on the same data", "w": 654, "h": []}, {"u": "/advanced-tree/centroid/", "t": "Centroid Decomposition", "s": "The divide-and-conquer that turns \"count pairs at distance d\" into O(n log n) — plus the centroid itself, proved, and the traps that make implementations slow.", "c": "Advanced Tree Algorithms", "k": "trees divide and conquer paths hard", "b": "Definition Recursively: find the centroid c of the current component, record it, delete it, and recurse on the remaining components. Making each component's centroid the parent of the other components' centroids gives the centroid tree on the same n vertices, of depth ≤ log_2 n . Theorem Depth O(log n), construction O(n log n) Every root-to-leaf path in the centroid tree has length ≤ log_2 n + 1 , and the total work is O(nlog n) if each level's component sizes are summed over that level (each vertex participates in ≤ log n levels, and finding a centroid within a component costs time linear in its size). Proof By the centroid property, the component containing any vertex v after removing its centroid has size ≤ half of the current component. So along a centroid-tree root-to-leaf path the component sizes decrease geometrically, giving ≤ log_2 n levels, and each vertex is \"worked on\" once per level it appears in. ∎ cpp centroid-decomp.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int sub [ n ], dead [ n ]{}, par_in_ctree [ n ]; int subtree_sizes ( int v , int p , int total , int & best_mx , int & best ) { sub [ v ] = 1 ; int mx = 0 ; for ( int to : g [ v ]) if ( to != p && ! dead [ to ]) { int s = subtree_sizes ( to , v , total , best_mx , best ); mx = max ( mx , s ); sub [ v ] += s ; } mx = max ( mx , total - sub [ v ]); // the \"upward\" part if ( mx < best_mx ) { best_mx = mx ; best = v ; } // first strict improvement return sub [ v ]; } int find_centroid ( int entry , int total ) { int best_mx = n + 1 , best = entry ; subtree_sizes ( entry , - 1 , total , best_mx , best ); return best ; } int count_component ( int v , int p ) { // size of v's live component int s = 1 ; for ( int to : g [ v ]) if ( to != p && ! dead [ to ]) s += count_component ( to , v ); return s ; } void build ( int entry , int parent ) { int total = count_component ( entry , - 1 ); int c = find_centroid ( entry , total ); par_in_ctree [ c ] = parent ; dead [ c ] = 1 ; // \"delete\" it for ( int to : g [ c ]) if (! dead [ to ]) build ( to , c ); } Note Iterate, don't recurse blindly build recurses ≤ log n deep, so it is safe; the inner sz DFS is O(component) deep — on a path of 10^6 vertices that is a stack overflow. Either make the size DFS iterative, or keep the habit of stating n ≤ 2· 10^5 and adding ulimit -s unlimited to the harness ( Walks, Trails, Paths, Cycles ). # The counting pattern Key idea Every pair (u,v) has a unique highest centroid-tree node c that separates them — the first centroid whose removal puts u and v in different components (or equals one of them). So \"count pairs satisfying a distance property\" decomposes as: at each centroid, count pairs through it, subtract pairs that actually lie inside one child component (they belong to a lower level). cpp count-pairs-distance.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 long long ans = 0 ; int freq [ 2 * K + 1 ]{}; // freq[d] = #vertices at distance d so far int sub [ n ], dead [ n ], dist_tmp [ n ]; void collect ( int v , int p , int d , vector < int > & out ) { out . push_back ( d ); for ( int to : g [ v ]) if ( to != p && ! dead [ to ]) collect ( to , v , d + w ( v , to ), out ); } void solve ( int entry ) { int total = count_component ( entry , - 1 ); int c = find_centroid ( entry , total ); dead [ c ] = 1 ; vector < int > all { 0 }; // {0} = the centroid itself freq [ 0 ]++; for ( int to : g [ c ]) { if ( dead [ to ]) continue ; vector < int > ds ; collect ( to , c , w ( c , to ), ds ); for ( int d : ds ) if ( K - d >= 0 ) ans += freq [ K - d ]; // pairs through c only for ( int d : ds ) { freq [ d ]++; all . push_back ( d ); } // then absorb this child } for ( int d : all ) freq [ d ]--; // leave the array clean for ( int to : g [ c ]) if (! dead [ to ]) solve ( to ); } Each unordered pair (u,v) is counted exactly once: at the first centroid whose removal separates them, where dist(u,c) + dist(v,c) = dist(u,v)", "w": 1136, "h": [["The counting pattern", "the-counting-pattern"]]}, {"u": "/advanced-tree/hld/", "t": "Heavy-Light Decomposition", "s": "Path queries with updates in O(log^2 n) — the decomposition, why light edges are few, the segment tree layout, and lazy propagation on chains.", "c": "Advanced Tree Algorithms", "k": "trees segment tree paths queries hard", "b": "# Chains: heavy and light Definition Root the tree. For each vertex, the heavy child is the child with the largest subtree; all other children are light . The heavy paths are the maximal chains obtained by always following the heavy child. Store for each vertex: head[v] (top of its chain), pos[v] (index in the base array), obtained by a DFS that visits the heavy child first . Theorem At most log n light edges per root path On any path from the root to a leaf there are at most log_2 n light edges, hence any root-to-vertex path meets at most log_2 n + 1 chains, and any u – v path meets at most 2log_2 n + 1 . Proof If (p, c) is light then size(c) < tfrac12 size(p) : p 's heavy child has the largest subtree, so if a light child had more than half, the heavy one would have less than the light one — contradiction. Walking upward from any vertex, each light edge at least doubles the subtree size, and sizes are bounded by n , so there are ≤ log_2 n of them. A u – v path is two root paths minus their common prefix. ∎ # Implementation cpp hld.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // ---- build: two DFS, both O(n) ---- int sub [ n ], dep [ n ], par [ n ], head [ n ], pos [ n ], timer ; int dfs_sz ( int v , int p ) { sub [ v ] = 1 ; par [ v ] = p ; int best = - 1 , bestsz = 0 ; for ( int to : g [ v ]) if ( to != p ) { dep [ to ] = dep [ v ] + 1 ; int s = dfs_sz ( to , v ); sub [ v ] += s ; if ( s > bestsz ) { bestsz = s ; best = to ; } } if ( best != - 1 ) head [ best ] = head [ v ]; // heavy child continues the chain return sub [ v ]; } void dfs_decompose ( int v ) { // heavy child FIRST -> chain is contiguous pos [ v ] = timer ++; int best = - 1 , bestsz = 0 ; for ( int to : g [ v ]) if ( to != par [ v ] && sub [ to ] > bestsz ) { bestsz = sub [ to ]; best = to ; } if ( best != - 1 ) dfs_decompose ( best ); for ( int to : g [ v ]) if ( to != par [ v ] && to != best ) { head [ to ] = to ; dfs_decompose ( to ); } } // usage: head[v] initialised to v before dfs_sz for the root, and to the child itself otherwise. // ---- query: walk chains bottom-up ---- int path_query ( int u , int v ) { // sum of values on the path u-v int res = 0 ; while ( head [ u ] != head [ v ]) { if ( depth [ head [ u ]] < depth [ head [ v ]]) swap ( u , v ); res += seg . query ( pos [ head [ u ]], pos [ u ]); // [head..u] is contiguous! u = par [ head [ u ]]; } if ( depth [ u ] > depth [ v ]) swap ( u , v ); res += seg . query ( pos [ u ], pos [ v ]); // same chain: include the LCA once return res ; } # What it buys you The four things you can now do path query / path update (sum, max, min, count-of-Z, xor…): O(log^2 n) with a segment tree over pos — Segment Tree for the tree itself, edge weights : store each edge's value at its deeper endpoint , then a path query becomes query(pos[lca]+1, pos[x]) — the \" +1 \" is the single most common HLD bug, subtree query / update : pos order from a heavy-first DFS is still a DFS order, so a subtree is an interval [pos[v], pos[v] + sub[v]) — the same as Entry/Exit Times and the Euler Tour , so HLD gives both path and subtree intervals for free, LCA in the same loop ( LCA by Heavy-Light Decomposition ), and k-th vertex on a path by descending chain by chain with lengths. Note Why O(log^2 n) and why people still ship it log n chains × O(log n) segment-tree work each. The log from the tree is cache-friendly and small ; in practice 3–5 chains per path, so the loop body runs a handful of times. A \"top tree / link-cut tree\" achieves O(log n) but costs 100+ lines and a debugging session you will not finish in a contest. HLD is the correct complexity/performance trade for a contest, and the standard answer in interviews when asked \"how would you support path sums with updates\". # Failure modes Watch out The five failure modes Heavy child by depth, not by subtree size — the bound dies (a \"broom\" tree gives Θ(n) chains per path). dfs_decompose visiting children in input order ins", "w": 1003, "h": [["Chains: heavy and light", "chains-heavy-and-light"], ["Implementation", "implementation"], ["What it buys you", "what-it-buys-you"], ["Failure modes", "failure-modes"], ["Two named reductions", "two-named-reductions"]]}, {"u": "/advanced-tree/tree-hashing/", "t": "Tree Hashing and Isomorphism", "s": "A canonical form for unordered rooted trees, a randomised hash that is one line, and the collision you must not ignore.", "c": "Advanced Tree Algorithms", "k": "hashing trees isomorphism hard", "b": "Definition Two rooted trees are isomorphic if a bijection between vertices preserves the root and adjacency. Unrooted: they become isomorphic after rooting at their respective centers ( Centroid Decomposition gives the center(s)). Theorem Canonical form, bottom-up Define φ(v) = \"(\" + sort({φ(c) : c child of v}) + \")\" . Then φ(u) = φ(v) as strings iff the rooted subtrees at u and v are isomorphic. Comparing subtrees is thus reduced to comparing hashes of φ , and the tree isomorphism class is φ(root) . Proof Induction on height. Two children multisets are equal (as isomorphism classes) iff their sorted φ sequences are equal, since φ is by the induction hypothesis a complete invariant for the children. The parenthesis wrapper makes the encoding prefix-free, so concatenation cannot create ambiguity, and the sort kills the arbitrary order of children. Hence equality of strings ⟺ equality of unordered child-class multisets ⟺ isomorphism. ∎ cpp tree-hash.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // AHU-style canonical labelling in O(n log n) without strings: // level the tree by height, then map each vertex's sorted child-label list to a fresh id. vector < int > hgt ( n ), lab ( n ); { vector < vector < int >> by_height ( n + 1 ); function < void ( int , int )> dfs = [&]( int v , int p ) { for ( int to : g [ v ]) if ( to != p ) { dfs ( to , v ); hgt [ v ] = max ( hgt [ v ], hgt [ to ] + 1 ); } by_height [ hgt [ v ]]. push_back ( v ); }; dfs ( root , - 1 ); map < vector < int >, int > ids ; int nxt = 0 ; for ( int h = 0 ; h <= n ; h ++) { for ( int v : by_height [ h ]) { vector < int > kids ; for ( int to : g [ v ]) if ( to != par [ v ]) kids . push_back ( lab [ to ]); sort ( kids . begin (), kids . end ()); auto [ it , ok ] = ids . try_emplace ( kids , nxt ); if ( ok ) nxt ++; lab [ v ] = it -> second ; } } } // two rooted trees are isomorphic iff their roots get the same label id (run both through one map) Note The 6-line randomised version hash(v) = 1 + ∑_c child (P(hash(c)) mod M) with P a random-looking polynomial (e.g. P(x) = x · A + B mod M , A,B random 64-bit) and M = 2^61-1 , using unsigned long long with a Mersenne reduction ( Linear Recurrences from Graphs and Matrices ). The sum is order-insensitive , which is exactly what unordered children need. Same class: xor instead of sum (careful: xor of equal child hashes cancels — a star with two identical leaves hashes the same as the bare center, a real bug). Watch out Collisions are not a rounding error A sum of random hashes mod 2^64 has collision probability per pair ≈ 2^-64 only if the child hashes are independent — an adversary who knows your constants can construct collisions. Seed the bases at runtime ( chrono::steady_clock ) rather than hard-coding them ( Trie (Prefix Tree) and Its Relatives ). Two-tree comparison by maximum matching of children : with hashing you get it for free, but \"the trees are isomorphic\" statements should be double-hashed with two moduli, or verified by an explicit canonical form when n ≤ 10^6 (the O(nlog n) version above is deterministic and exact). Weighted/labelled variants: include the label in the hash before the polynomial, i.e. hash(v) = P(label[v], ∑_c hash(c)) , otherwise relabellings collide. Unrooted trees and forests Unrooted : compute φ at both centers ( Centroid Decomposition has at most two) and take the minimum/sum of the two — the canonical form of an unrooted tree is \"(\" + min(φ(c_1), φ(c_2)) \")\" appropriately; Rooted at every vertex : \"for which roots is the tree isomorphic to a given pattern\" — reroot the hash in O(n log n) total with φ_all(v) = P(all neighbour hashes) (subtree + complement hashes); Counting non-isomorphic trees on n vertices: the generating-function (Pólya/Otter) route, not hashing — the sequence is OEIS A000055; Tree isomorphism as a graph isomorphism special case : linear time deterministic (Hopcroft–Wong for bounded degree, or the AHU algorithm above) — unlike general GI, which is", "w": 724, "h": []}, {"u": "/advanced-tree/huffman/", "t": "Huffman Coding", "s": "Greedy tree-building for optimal prefix codes, the sibling property, and the same algorithm hiding in \"merge stones\", \"minimum cost tree\", and optimal alphabetic coding.", "c": "Advanced Tree Algorithms", "k": "greedy heap trees dp core", "b": "Definition An assignment of binary strings (codewords) to n symbols such that no codeword is a prefix of another. Decoding is then instantaneous: walk the bit stream down the code tree, output at each leaf, restart. Equivalently: a full binary tree with the symbols at its leaves, cost = ∑_i w_i · depth(ℓ_i) . Theorem Huffman's algorithm is optimal Repeatedly take the two minimum-weight items, merge them into a parent whose weight is the sum, and push it back. The resulting tree minimises ∑_i w_i depth(ℓ_i) over all prefix codes. Proof Two steps. (i) Deepest-pair siblings. In an optimal tree, let a,b be two deepest leaves; they are siblings at the same depth (otherwise move a shallower \"uncle\" subtree up, which strictly improves the cost — the standard sibling property). So an optimal tree exists where the two minimum weights w_1 ≤ w_2 are siblings at maximum depth: if x,y occupy that deepest sibling pair, swapping x rightarrow the symbol of weight w_1 changes the cost by (w_1 - w_x)(d - d_x) ≤ 0 , and similarly for w_2 . (ii) Induction. Merging w_1,w_2 into w_1 + w_2 turns the problem into the same problem on n-1 weights: ∑_i w_i d_i for the original = ∑_i>2 w_i d_i' + (w_1 + w_2)(d' + 1) for the reduced instance, i.e. it differs by the constant w_1 + w_2 . So an optimal reduced tree extends to an optimal original tree, and the greedy choice is safe. ∎ cpp huffman.cpp Copy 1 2 3 4 5 6 7 8 9 10 priority_queue < long long , vector < long long >, greater < long long >> pq ; for ( long long f : freq ) pq . push ( f ); long long cost = 0 ; while ( pq . size () > 1 ) { long long a = pq . top (); pq . pop (); long long b = pq . top (); pq . pop (); cost += a + b ; // every merge pays the new node's weight pq . push ( a + b ); } // cost == sum of internal node weights == sum w_i * depth_i Note The identity that makes 'merge stones' the same problem ∑_i w_i · depth_i = ∑_internal x wt(x) . Each leaf i contributes w_i to exactly the internal nodes on its path to the root, of which there are depth_i . So \"merge two piles at cost = their sum, minimise total cost\" is Huffman — and the greedy proof above is the proof for both. Four disguises of the same algorithm Minimum Cost Tree From Leaf Values (LeetCode 1130 / CF-style): \"merge adjacent\" is not Huffman — adjacency forces the optimal alphabetic variant, solved by Hu–Tucker in O(nlog n) or DP in O(n^2) / Garsia–Wachs in O(nlog n) . Huffman without adjacency would ignore the order and be wrong; Huffman with bounded depth (e.g. \"code lengths ≤ L \"): the package-merge algorithm (van Leeuwen) O(nL) , or \"MOPT-Merge\" — do not try to patch the greedy with a heap trick; k -ary Huffman : merge the k smallest at a time; pad with k - 1 - ((n-2) mod (k-1)) zero-weight symbols first so the last merge also takes exactly k — forgetting the padding is the classic k -ary bug; Optimal merge pattern / file merging , carpenter's board , \"connect ropes with minimum cost\" : exactly the binary case, cost = total merge weight. Example Emitting the code Keep node ids in the heap, build the tree with explicit children, then DFS assigning 0 / 1 . Real formats use a canonical code instead: compute the lengths ℓ_i , sort symbols by (ℓ_i, i) , and set code_i = (code_i-1 + count(ℓ_i-1)) ≪ (ℓ_i - ℓ_i-1), so transmitting the length table suffices to rebuild the code — the reason DEFLATE and JPEG send lengths rather than codewords, and the general lesson that a tree you can serialise in O(n) bits of \"shape plus counts\" is worth more than the tree itself ( Counting Trees: Cayley and Prüfer 's Prüfer bijection is the same economy). Watch out Two facts to state when asked Huffman is optimal among prefix codes with per-symbol integer lengths ; it is not optimal among all codes for a source — arithmetic coding beats it by using fractional effective lengths, so \"Huffman is optimal\" always needs the qualifier. Ties matter: with equal weights different trees give different lengths (same cost). If the problem asks for a lexicogra", "w": 811, "h": []}, {"u": "/flow/cuts/", "t": "Cuts and Flows", "s": "The definitions, the capacity bound, and why \"flow\" and \"cut\" are the same optimisation seen from two sides.", "c": "Cuts and Flows", "k": "flow cuts duality core", "b": "Definition A directed graph with source s , sink t , and capacities c(u,v) ≥ 0 . A flow is f : E → ℝ_≥ 0 with capacity : f(u,v) ≤ c(u,v) , conservation : ∑_v f(v,u) = ∑_w f(u,w) for every u ∉ {s,t} . Its value is |f| = ∑_v f(s,v) - ∑_v f(v,s) . With antisymmetric f (the convention in code: f(u,v) = -f(v,u) ), conservation becomes ∑_v f(v,u) = 0 for internal u and the value is just the net out-flow of s . Definition An s – t cut is a partition (S, bar S) with s ∈ S , t ∈ bar S ; its capacity is c(S, bar S) = ∑_u ∈ S, v ∉ S c(u,v) — edges forward only . Backward edges do not count: they carry flow the other way, which helps , so ignoring them is the correct accounting. Lemma Weak duality For every flow f and every s – t cut (S,bar S) : |f| ≤ c(S, bar S) . Proof |f| = ∑_u ∈ S, v ∉ S f(u,v) - ∑_u ∉ S, v ∈ S f(u,v) — conservation makes all internal terms cancel when you sum the balance equations over S . Dropping the (non-negative) second sum and applying f ≤ c on the first gives |f| ≤ ∑_u∈ S, v∉ S c(u,v) . ∎ Theorem Max-flow min-cut max_f |f| = min_(S,bar S) c(S,bar S) , and both are attained. (Proof: Max Flow: Ford–Fulkerson, Dinic, Push–Relabel — the min cut is read off the residual graph after saturation.) The vocabulary you will need for every modelling problem residual capacity r(u,v) = c(u,v) - f(u,v) + f(v,u) ; an edge with r > 0 is a residual edge , an augmenting path is an s ⇝ t path of residual edges; augmenting along it by the bottleneck increases |f| by exactly that amount, a flow is maximum iff the residual graph has no s ⇝ t path (integrality: if all capacities are integral, the algorithm keeps f integral — this is why flow counts things ), the min cut is S = vertices reachable from s in the final residual graph; it is the smallest such S (source side) and its complement, the vertices that can reach t , is the largest — knowing which side is which settles a whole class of \"which side does vertex x belong to\" questions. Note What the theorem buys in modelling terms Flow algorithms are only as useful as the encoding : \"the min cut of a network you built\" equals the answer of a combinatorial problem. The recurring encodings, with their proofs of correctness all being \"a cut is exactly a valid object here\": project selection / maximum weight closure ( Min-Cut Models ), minimum vertex cover in bipartite graphs = maximum matching (Kőnig, Kőnig's Theorem and Minimum Covers ), \"delete fewest edges/vertices so that s and t disconnect\" — Menger, made algorithmic by unit capacities ( Connectivity, Bridges, Articulation Points ), binary energy minimisation with submodular pairwise terms — the graph cut of computer vision, Dilworth's theorem as a flow on a poset DAG ( DAGs and Topological Order 's chain-cover remark, made algorithmic in Bipartite Matching (Kuhn's Algorithm) ). G A A D D A->D C C D->C B B B->A B->D C->B E E C->E E->B F F F->D F->C Figure 1 A directed network is the same shape as any digraph: only the edge capacities are new. Given this graph, an s–t cut is a choice of which side each vertex lands on, and its cost is the sum of capacities of the edges that point across, forward only. Problems for this page all core CSES 1694 Download Speed core max flow on a small dense graph — parallel edges must be summed CSES 1695 Police Chase core edge-disjoint paths = unit-capacity max flow", "w": 640, "h": []}, {"u": "/flow/maxflow/", "t": "Max Flow: Ford–Fulkerson, Dinic, Push–Relabel", "s": "Three augmenting strategies, why each terminates, Dinic's O(V^2 E) proof, and how to read the min cut out of the residual graph.", "c": "Cuts and Flows", "k": "flow graphs optimisation hard", "b": "Definition While a path s ⇝ t exists in the residual graph G_f , take one and push the bottleneck capacity along it. The flow stays feasible, strictly increases, and stops exactly when no such path exists. Theorem Termination and correctness If capacities are integral, the algorithm terminates with a maximum flow, and |f| = c(S,bar S) where S is the set of vertices reachable from s in the final G_f . Proof Each augmentation increases |f| by at least 1 (integral bottlenecks), and |f| ≤ ∑_v c(s,v) bounds it above — so it terminates. At termination S is well defined and t ∉ S . Every edge out of S is saturated (else its head would be reachable), and every edge into S carries zero flow (else the reverse residual edge would make its tail reachable). Hence c(S,bar S) = ∑_u∈ S,v∉ S c(u,v) = ∑ f(u,v) = |f|, and weak duality ( Cuts and Flows ) then makes f maximum and (S,bar S) minimum. ∎ # Three ways to pick the path algorithm choice bound practical Ford–Fulkerson (DFS) arbitrary O(|f| · E) — unbounded with irrational capacities never use; can be exponentially slow with \"bad\" paths Edmonds–Karp shortest (BFS) O(V E^2) reliable, V E^2 up to a few 10^7 Dinic blocking flow in the level graph O(V^2 E) , O(E√V) unit-capacity, O(V^2/3E) unit-network the default; usually 10–100× the bound Push–relabel (FIFO/highest) local saturate + relabel O(V^2√E) with gap+global relabel heuristics, O(V^3) plain fastest on dense graphs and on huge sparse ones with good heuristics cpp dinic.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct Edge { int to , rev , cap ; }; vector < vector < Edge >> g ( n ); void add ( int u , int v , int cap ) { // residual pair: forward cap, backward 0 g [ u ]. push_back ({ v , ( int ) g [ v ]. size (), cap }); g [ v ]. push_back ({ u , ( int ) g [ u ]. size () - 1 , 0 }); } int lvl [ n ], it [ n ]; bool bfs () { fill ( lvl , lvl + n , - 1 ); queue < int > q { }; lvl [ s ] = 0 ; q . push ( s ); while ( q . size ()) { int v = q . front (); q . pop (); for ( auto & e : g [ v ]) if ( e . cap && lvl [ e . to ] < 0 ) { lvl [ e . to ] = lvl [ v ] + 1 ; q . push ( e . to ); } } return lvl [ t ] >= 0 ; } int dfs ( int v , int pushed ) { // blocking flow, one DFS \"cursor\" per vertex if ( v == t ) return pushed ; for ( int & cid = it [ v ]; cid < ( int ) g [ v ]. size (); cid ++) { Edge & e = g [ v ][ cid ]; if ( e . cap && lvl [ e . to ] == lvl [ v ] + 1 ) { int tr = dfs ( e . to , min ( pushed , e . cap )); if (! tr ) continue ; e . cap -= tr ; g [ e . to ][ e . rev ]. cap += tr ; return tr ; } } return 0 ; } long long flow = 0 ; while ( bfs ()) { fill ( it , it + n , 0 ); while ( int pushed = dfs ( s , INF )) flow += pushed ; } Theorem Dinic: O(V^2 E) Each phase (one BFS + one blocking flow) costs O(VE) , and there are at most V-1 phases. Proof The blocking flow computation is O(VE) : every DFS either saturates an edge (at most E saturations) or advances a cursor it[v] permanently (at most V+E cursor steps per unit... formally, the total number of dfs calls that return 0 is bounded by the number of cursor advances, O(VE) including the path lengths). For the phase count: after a phase, the shortest augmenting-path length strictly increases (a standard argument: any new residual s – t path must use a backward edge of the blocking flow, which skips at least one level, so its length grows), and lengths are at most V-1 . ∎ How the min cut is read off run to completion, then BFS from s in the residual graph → S ; the cut edges are the original edges from S to bar S , \"which vertices can be on the source side of some min cut?\" — the family of min cuts forms a lattice; contract strongly-connected components of the final residual graph, and the min cuts correspond to closed sets of the condensation that contain scc(s) and exclude scc(t) : a vertex is in all min cuts' source sides iff scc(s) reaches it, in none iff it reaches scc(t) , minimum number of edges to delete to separate s,t = m", "w": 1119, "h": [["Three ways to pick the path", "three-ways-to-pick-the-path"]]}, {"u": "/flow/mincut-models/", "t": "Min-Cut Models", "s": "Seven reductions where the answer of a combinatorial problem is exactly a minimum cut — maximum closure, bipartite covering, project selection, and the submodular energy.", "c": "Cuts and Flows", "k": "flow modelling duality hard", "b": "Definition In a digraph, a set S is a closed set (down-set) if u ∈ S and u → v imply v ∈ S . The maximum weight closure problem: maximise ∑_v ∈ S w_v over closed S , with weights of both signs. Theorem Closure = min cut Build s → v with capacity w_v for w_v > 0 ; v → t with capacity -w_v for w_v < 0 ; every original edge u → v with capacity ∞ . Then max_closed S ∑_v∈ S w_v = ∑_w v > 0 w_v - min-cut, and the optimal S is the source side of the min cut. Proof For any cut (A, bar A) with s ∈ A , t ∉ A , put S = A ∖ {s} . If S is not closed, some edge u → v leaves S (u in, v out), contributing ∞ — so finite cuts are exactly the closed sets. For a closed S , the cut pays: (i) each positive w_v with v ∉ S (the edge s → v is cut), and (ii) each |w_v| with v ∈ S (the edge v → t is cut). So c(S) = ∑_w v>0, v∉ S w_v + ∑_w v<0, v∈ S (-w_v) = ∑_w v > 0 w_v - ∑_v ∈ S w_v, which is minimised exactly when the closure's weight is maximised. ∎ The catalogue (each is the same three lines) Project selection : profit p_i , cost c_j of prerequisites ⇒ closure with weights p_i - c_j . Add \" ∞ \" edges from each project to its prerequisites. Maximum weight independent set in a bipartite graph = total weight − min vertex cover: for each left vertex a weight, for each right a weight, edges l → r with ∞ , then s→ l (weight), r→ t (weight) — the ∞ edges forbid \"both endpoints kept\" exactly as needed; complements turn a cover into an independent set ( Kőnig's Theorem and Minimum Covers ). Minimum vertex cover in bipartite graphs (unweighted): source → left (cap 1), matching edges left→right ( ∞ ), right → sink (cap 1); the min cut has size = max matching (Kőnig's proof is this construction). Minimum number of edges/vertices to delete to separate s,t ( Connectivity, Bridges, Articulation Points ): unit capacities on edges, or vertex-splitting v_in→ v_out with capacity 1 for vertices. Maximum density subgraph / fractional covering : parametric min cut — binary search λ , solve \"is there S with ∑_v∈ S(w_v - λ) > 0 ?\" as a closure, O(log) max-flow runs. Binary energy minimisation : variables x_i ∈ {0,1} , unary terms go on s -/ t -edges, a pairwise term V_ij(0,1)+V_ij(1,0) ≥ V_ij(0,0)+V_ij(1,1) (submodularity) becomes one edge of capacity frac{V_ij(0,1)+V_ij(1,0)-V_ij(0,0)-V_ij(1,1)}{2} ; the min cut is the global optimum — this is the \"graph cut\" of vision, and it fails the instant submodularity is violated. Chain/antichain coverings in posets (Dilworth) — a matching problem in disguise ( Bipartite Matching (Kuhn's Algorithm) ). Note Recognising the pattern in a statement Three signals that a min cut is hiding: (a) a choice of \"keep / drop\" per object with a penalty for a one-directional implication; (b) \"minimum deletions to break all paths / all pairs\"; (c) a bipartite structure where constraints are \"not both\" or \"at least one of two\". In each case, write the objective as ∑ (kept profits) - ∑ (penalties) and ask: which constraint becomes an ∞ edge? Watch out Capacity of ∞ must be big, not maximal Use ∞ = 1 + ∑ |w| (or 10^15 with long long ), never INT_MAX : a relaxation flow += min(pushed, cap) over a saturated path plus INF - x arithmetic overflows, and INT_MAX capacities in a 1000-edge graph sum to 2×10^12 in the wrong type. Also: if the min cut you get has value ≥ ∞ , your model is wrong — a finite cut must always exist (e.g. take all positive vertices, if that is closed). Example Reading the specific answer out of the cut After a max flow, \"which projects are in the optimal set\" = the source side of the minimum cut; \"which items must be in every optimum\" = vertices that reach t in the residual graph too... precisely: v is in all min cuts' source sides iff s ⇝ v in the residual graph; v is in no min cut iff v ⇝ t . To enumerate all min cuts, condense the residual graph — the min cuts are the closed sets of the condensation separating scc(s) from scc(t) ( Max Flow: Ford–Fulkerson, Dinic, Push–Relabel 's lattice remark). Problems for this page all core h", "w": 823, "h": []}, {"u": "/flow/mcmf/", "t": "Min-Cost Max-Flow", "s": "Successive shortest augmenting paths with potentials, why negative cycles cannot appear, and the assignment/transportation problems it solves.", "c": "Cuts and Flows", "k": "flow costs matching hard", "b": "# The problem, and augmenting along cheapest paths Definition Capacities c(e) and per-unit costs a(e) . Among all flows of maximum value (or of a given value F ), minimise ∑_e a(e) f(e) . Theorem Successive shortest paths Start with f = 0 . Repeatedly find a shortest (cheapest) s ⇝ t path in the residual graph w.r.t. reduced costs, and augment along it. If costs are non-negative, the flow of value k produced after k augmentations (unit capacities) — and in general after each augmentation — is a minimum-cost flow of that value. Proof The residual graph of an optimal flow of value v contains no negative cycle: a negative cycle C could be augmented (it preserves conservation at every vertex) to strictly improve the cost — contradiction. Conversely, if f is min-cost for its value and P is a cheapest s ⇝ t residual path, then f + δ P is min-cost for value |f| + δ : any other flow g of that value differs from f by a decomposition into residual s ⇝ t paths and cycles, whose costs are ≥ c(P) (paths) and ≥ 0 (cycles, by optimality of f ), so cost(g) - cost(f) ≥ (amount) · c(P) = cost(f + δ P) - cost(f) . ∎ # Implementation with potentials cpp mcmf.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct Edge { int to , rev , cap ; long long cost ; }; vector < vector < Edge >> g ( n ); void add ( int u , int v , int cap , long long cost ) { g [ u ]. push_back ({ v , ( int ) g [ v ]. size (), cap , cost }); g [ v ]. push_back ({ u , ( int ) g [ u ]. size () - 1 , 0 , - cost }); } const long long INF = 4e18 ; long long pot [ n ]; // potentials = shortest distances so far priority_queue < pair < long long , int >, vector <...>, greater <...>> pq ; while ( need ) { // Dijkstra on reduced costs a(u,v) + pot[u] - pot[v] >= 0 fill ( dist , dist + n , INF ); dist [ s ] = 0 ; pq . push ({ 0 , s }); while ( pq . size ()) { auto [ d , v ] = pq . top (); pq . pop (); if ( d > dist [ v ]) continue ; for ( auto & e : g [ v ]) if ( e . cap && dist [ e . to ] > dist [ v ] + e . cost + pot [ v ] - pot [ e . to ]) { dist [ e . to ] = dist [ v ] + e . cost + pot [ v ] - pot [ e . to ]; pv [ e . to ] = v ; pe [ e . to ] = & e ; pq . push ({ dist [ e . to ], e . to }); } } if ( dist [ t ] == INF ) break ; // no more augmenting paths -> flow is maximum for ( int v = 0 ; v < n ; v ++) if ( dist [ v ] < INF ) pot [ v ] += dist [ v ]; int add = need ; // bottleneck along the path for ( int v = t ; v != s ; v = pv [ v ]) add = min ( add , g [ pv [ v ]][ pe [ v ]]. cap ); for ( int v = t ; v != s ; v = pv [ v ]) { Edge & e = g [ pv [ v ]][ pe [ v ]]; e . cap -= add ; g [ v ][ e . rev ]. cap += add ; } flow += add ; cost += add * pot [ t ]; // pot[t] is the true shortest distance s->t need -= add ; } # Why potentials work Note Why potentials, and why they stay valid Reduced cost a'(u,v) = a(u,v) + p(u) - p(v) preserves the cost of every s ⇝ t path up to the constant p(s) - p(t) (internal vertices cancel), and every cycle cost exactly. Choosing p = the shortest-distance vector from the previous round makes all residual edges non-negative: for an edge with residual capacity, d(v) ≤ d(u) + a(u,v) , i.e. a'(u,v) ≥ 0 . So Dijkstra is applicable, and after updating p += d the invariant is maintained. This is the same re-weighting trick as Johnson's algorithm ( Bellman–Ford and Negative Weights 's re-weighting remark), and it is the reason the O(VE) Bellman–Ford per round becomes O(E log V) . # Negative cycles and overflow Watch out The negative-cycle trap in a residual graph Residual edges carry negative costs ( -a(e) ), so the graph always has negative edges; potentials are what remove them. Three consequences: the first round needs Bellman–Ford (or SPFA) to get valid potentials if any original cost is negative — with non-negative costs, p = 0 works; vertices unreachable in a round must keep their old potential (never \"reset to 0\"), or reduced costs go negative and Dijkstra silently returns a wrong path; cost overflow : with |a| ≤ 10^6 , ", "w": 1145, "h": [["The problem, and augmenting along cheapest paths", "the-problem-and-augmenting-along-cheapest-paths"], ["Implementation with potentials", "implementation-with-potentials"], ["Why potentials work", "why-potentials-work"], ["Negative cycles and overflow", "negative-cycles-and-overflow"], ["Complexity, honestly stated", "complexity-honestly-stated"], ["Modelling: transportation and assignment", "modelling-transportation-and-assignment"], ["When not to use MCMF", "when-not-to-use-mcmf"]]}, {"u": "/matching/intro/", "t": "Matching: Definitions and Duality", "s": "Matchings, covers, and independent sets on one page — plus the two theorems that turn searching into proving.", "c": "Matching", "k": "matching duality core", "b": "Definition A matching M ⊆ E is a set of edges no two of which share an endpoint. Its size is |M| ; it is perfect if every vertex is matched, maximal if no edge can be added, maximum if no larger matching exists. A vertex cover is a set C of vertices touching every edge. An independent set is a set of vertices with no edge inside it. Three trivialities you will use constantly maximal ≠ maximum: a greedy maximal matching is a 2 -approximation of the maximum (every edge of a maximum matching touches a distinct chosen edge), and that factor is tight for the greedy; in any graph, the complement of a vertex cover is an independent set, so α(G) + τ(G) = n ; every matching edge needs its own cover vertex: |M| ≤ |C| for every matching M and every cover C . In particular a perfect matching certifies τ ≥ n/2 . Theorem Kőnig's theorem (bipartite) In a bipartite graph, the size of a maximum matching equals the size of a minimum vertex cover. (Proof, and the algorithm: Kőnig's Theorem and Minimum Covers .) Note Why the bipartite restriction is doing the work For a triangle K_3 : max matching =1 , min cover =2 . The odd cycle is exactly the obstruction, and the general-graph answer is Berge's: the defect is measured by Tutte's condition ( General Matching and Blossoms ). Duality with no gap is a bipartite phenomenon — the same reason LP duality is clean for network matrices. Theorem Berge's characterisation (every graph) M is a maximum matching iff there is no M -augmenting path : a path whose endpoints are unmatched and whose edges alternate ∉ M, ∈ M, ∉ M, … Proof If an augmenting path exists, symmetric-differencing M with it (flip matched/unmatched along the path) gives a matching of size |M|+1 . Conversely, if a larger matching M' exists, look at the subgraph M △ M' : every vertex has degree ≤ 2 and components alternate edges of M and M' , so the components are even cycles and paths. Since |M'| > |M| , some path component has one more M' -edge than M -edge — its endpoints are unmatched in M (an endpoint matched in M would need its M -edge present, giving the component an extra M edge or degree 3). That path is an augmenting path. ∎ Greedy vs. augmenting: find an augmenting path on the left picture and watch the matching grow by one; the right picture is what a maximal-but-not-maximum matching looks like. The map of the chapter Bipartite Matching (Kuhn's Algorithm) — DFS augmenting paths (Kuhn's algorithm), O(VE) , 10 lines, Hopcroft–Karp — BFS-layered multi-augmentation, O(E√V) , Kőnig's Theorem and Minimum Covers — minimum cover, maximum independent set, and minimum path cover from the same run, The Hungarian Algorithm — weighted assignment, O(n^3) , Matching Applications — tilings, domino problems, posets, \"place rooks\", grid-cut problems, General Matching and Blossoms — blossoms: why odd cycles need a contraction, and the O(V^3) implementation. Problems for this page all core warm-up CSES 1696 School Dance core the bipartite matching statement with output pairs CSES 1130 Tree Matching warm-up maximum matching on a tree is DP, not flow — know both", "w": 549, "h": []}, {"u": "/matching/bipartite/", "t": "Bipartite Matching (Kuhn's Algorithm)", "s": "Augmenting paths by DFS, ten lines long, O(VE) — with the tie-breaking trick that makes it pass in practice.", "c": "Matching", "k": "matching dfs bipartite core", "b": "Definition Bipartition L ∪ R ; only edges L – R exist. mt[x] = the partner of x , or -1 . A try(v) call attempts to make v matched, possibly stealing a partner and re-housing its previous match. cpp kuhn.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 vector < vector < int >> g ( n ); // only from the left side vector < int > mt ( m , - 1 ), used ( n ), timer_ ; // m = |R| bool try_kuhn ( int v ) { if ( used [ v ] == timer_ ) return false ; used [ v ] = timer_ ; for ( int to : g [ v ]) { if ( mt [ to ] == - 1 || try_kuhn ( mt [ to ])) { mt [ to ] = v ; return true ; } } return false ; } int matching = 0 ; for ( int v = 0 ; v < n ; v ++) { timer_ ++; if ( try_kuhn ( v )) matching ++; } Theorem Correctness After every vertex of L has been offered, mt is a maximum matching. Proof Each try_kuhn(v) either finds an augmenting path from v (the recursion stack records it: v → unmatched to , or v → to → the previous partner's re-housing) and flips it, increasing |M| by 1, or proves that no augmenting path from v exists in the current graph restricted to the visited set. Since each successful call strictly increases the matching and each call's recursion alternates unmatched/matched edges, every flip yields a valid matching. When all v ∈ L have been processed, suppose an augmenting path P existed; take its left endpoint u — the first u on P processed... Standard: Berge's lemma ( Matching: Definitions and Duality ) requires no augmenting path; the classical invariant proof shows the DFS explores all reachable alternating vertices from u , so if a shorter augmenting path from any vertex existed at the end, the last successful augmentation would have used it. Formally, the algorithm maintains \"no augmenting path starts at an already-processed vertex\", and processing all vertices then leaves none, since the left endpoints of any augmenting path are all in L . ∎ Note Read that proof as: greedy + steal-back = augmenting paths The insight is that a plain greedy \"take a free neighbour\" becomes optimal exactly when you allow the displaced vertex to search again — one level of recursion is enough to implement Berge's lemma because the recursion is the alternating path. The used marker prevents revisiting a left vertex within one search (else the DFS loops inside a cycle of the alternating graph). Watch out The four practical fixes that turn O(VE) into a passing submission Greedy pre-pass : first match every v to any free neighbour, then run try_kuhn only on unmatched vertices. On random graphs this cuts the running time by an order of magnitude, because the DFS recursion starts shallow. Order the left side by increasing degree when the graph is sparse-but-irregular — deep searches then happen on low-degree vertices (cheap). Do not clear used with a fill inside the loop ; the timer_ epoch trick above keeps it O(1) per start vertex and avoids an extra O(nm) . n = m = 5·10^4 , |E| = 10^5 : worst case 5×10^9 — use Hopcroft–Karp or max flow instead; if you keep Kuhn, you are betting on the average case, which is fine only when the statement's generator is random. What one run gives you the maximum matching itself ( mt ), the minimum vertex cover and maximum independent set (via the alternating reachability from unmatched left vertices — Kőnig's Theorem and Minimum Covers ), the forced/optional edges : an edge is in some maximum matching iff it is not a \"bridge-like\" failure in the directed graph G_M (orient unmatched R→ L , matched L→ R ); an edge is in every maximum matching iff it is matched and its removal drops the size, i.e. it is a bridge of that directed structure — a 5-line addition on top of the same DFS, perfect matching existence on a tree/convex graph, where the DFS is O(n) after ordering, maximum bipartite independent set = n − min cover, which is \"largest set with no conflict edge\" (e.g. \"choose the most items, no two from the same pair\"). Example Matching as a flow — the same algorithm in disguise Add s → L and R → t with capacity 1, the edges with", "w": 798, "h": []}, {"u": "/matching/hopcroft-karp/", "t": "Hopcroft–Karp", "s": "Augment along a maximal set of shortest disjoint paths per phase, and the O(E sqrt V) bound falls out of two counting arguments.", "c": "Matching", "k": "matching bfs complexity hard", "b": "# Phases Definition In one phase: BFS from all free left vertices through alternating edges to compute distances; then DFS-augment along all vertex-disjoint shortest augmenting paths (a maximal set), each of length d = the distance to the nearest free right vertex. Repeat. # Implementation cpp hopcroft-karp.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 int n , m ; // |L| = n, |R| = m vector < vector < int >> g ; // g[u] for u in L vector < int > mt ( m , - 1 ), dist ( n ), ptr ( n ); // mt[v] = matched left vertex of v in R bool bfs () { queue < int > q ; for ( int u = 0 ; u < n ; u ++) { dist [ u ] = - 1 ; if ( free_left ( u )) { dist [ u ] = 0 ; q . push ( u ); } } bool found = false ; while ( q . size ()) { int u = q . front (); q . pop (); for ( int v : g [ u ]) { int u2 = mt [ v ]; if ( u2 == - 1 ) found = true ; // free right vertex at this layer else if ( dist [ u2 ] == - 1 ) { dist [ u2 ] = dist [ u ] + 1 ; q . push ( u2 ); } } } return found ; } bool dfs ( int u ) { for ( int & i = ptr [ u ]; i < ( int ) g [ u ]. size (); i ++) { int v = g [ u ][ i ], u2 = mt [ v ]; if ( u2 == - 1 || ( dist [ u2 ] == dist [ u ] + 1 && dfs ( u2 ))) { mt [ v ] = u ; return true ; } } dist [ u ] = - 1 ; // dead end: prune for this phase return false ; } int matching = 0 ; while ( bfs ()) { fill ( ptr . begin (), ptr . end (), 0 ); for ( int u = 0 ; u < n ; u ++) if ( free_left ( u ) && dfs ( u )) matching ++; } # The O(E√V) bound Theorem Hopcroft–Karp runs in O(E√V) Let M^* be a maximum matching and d_i the length of the shortest augmenting path after phase i . Then (a) the d_i are strictly increasing, and (b) after O(√V) phases d_i > √V , and (c) once d > √V , at most O(√V) further phases are needed. Each phase costs O(E) . Proof (a) After augmenting along a maximal set of shortest paths of length d , no augmenting path of length d remains (else maximality is violated), and no shorter one can appear: the symmetric difference of M and M △ P (for the augmentations performed) shows a new shorter path would have yielded an old one. So distances strictly increase. (b) Let M_i be the current matching, M^* maximum, d = d_i > √V . Consider G' = M_i △ M^* : components are even cycles and alternating paths, and there are |M^*| - |M_i| paths that start and end with an M^* -edge — these are augmenting paths for M_i , each of length ≥ d . The paths are vertex-disjoint in M^* -edges… at least they are edge-disjoint, so (|M^*|-|M_i|) · d ≤ |M^*| + |M_i| ≤ 2V , giving |M^*| - |M_i| ≤ 2V/d < 2√V . Each phase increases |M_i| by at least 1, so at most 2√V phases remain. (c) The same inequality with d ≤ √V bounds the number of early phases by √V since each is a distinct length. Total: O(√V) phases × O(E) per phase. ∎ # What the proof buys the code Note What to copy from the proof into your code The two structural facts the proof needs are exactly the two lines people omit: (i) BFS layers give shortest augmenting paths, so the strict-increase argument holds — a DFS without layers degenerates to Kuhn and loses the bound; (ii) the per-phase ptr cursors plus dist[u] = -1 pruning make the phase O(E) once , not O(VE) ; without the pruning, dead left vertices get re-searched by every start vertex. # The bound in numbers The bound in numbers n = m = 10^5 , |E| = 3·10^5 : √V ≈ 450 , so ≈ 1.4 × 10^8 edge visits — a second, fine; dense bipartite n = m = 2000 : Kuhn's O(VE) = 8×10^9 fails, HK's 4×10^6 × 63 ≈ 2.5×10^8 is borderline, and Hungarian/Dinic may do better in practice; random graphs: HK's phases are few (usually 3–6), and greedy-Kuhn is often faster than HK because the BFS/DFS overhead per phase dominates — the classic case where theory and the clock disagree; unit networks in general (not just matching): the same O(E√V) argument works for Dinic on any unit-capacity network ( Max Flow: Ford–Fulkerson, Dinic, Push–Relabel ), which is why \"just run Dinic\" is asymptotically the same answer here. # Three bugs specifi", "w": 979, "h": [["Phases", "phases"], ["Implementation", "implementation"], ["The bound", "the-oesqrt-v-bound"], ["What the proof buys the code", "what-the-proof-buys-the-code"], ["The bound in numbers", "the-bound-in-numbers"], ["Three bugs specific to this code", "three-bugs-specific-to-this-code"], ["Where shows up elsewhere", "where-sqrt-v-shows-up-elsewhere"]]}, {"u": "/matching/konig/", "t": "Kőnig's Theorem and Minimum Covers", "s": "One alternating DFS gives the minimum vertex cover, the maximum independent set, and — with a small twist — the minimum path cover of a DAG.", "c": "Matching", "k": "matching duality cover hard", "b": "# Kőnig's theorem Theorem Kőnig (1931) In a bipartite graph the maximum matching size equals the minimum vertex cover size. Proof Let M be a maximum matching. Run the alternating DFS from all free left vertices along edges ∉ M and back along edges ∈ M ; let Z_L, Z_R be the left/right vertices reached. Claim C = (L ∖ Z_L) ∪ (R ∩ Z_R) is a vertex cover of size |M| . Covers every edge. An edge uv with u ∈ L ∖ C means u ∈ Z_L ; if also v ∉ C then v ∈ R ∖ Z_R . If uv ∉ M , then v would have been reached from u (the DFS follows all non-matching edges out of a reached left vertex) — contradiction. If uv ∈ M , then u is matched, and a matched left vertex is reached only through its matching edge from the right, so v ∈ Z_R — contradiction. Hence every edge is covered. Size. Each v ∈ R ∩ Z_R is matched (a free right vertex would end an augmenting path, impossible for maximum M ), so |R ∩ Z_R| ≤ |M| , and each u ∈ L ∖ Z_L that matters is matched to a right vertex in Z_R ... precisely: no two vertices of C are matched to each other (if uv ∈ M , u ∈ Z_L or v ∈ Z_R , never both directions), so |C| ≤ |M| ; combined with |C| ≥ |M| (any cover exceeds any matching, Matching: Definitions and Duality ) gives equality. ∎ # Extracting the cover cpp konig.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // after any bipartite matching (mt[] on the right side), build the cover: vector < int > zl ( n ), zr ( m ); // reached sets queue < int > q ; for ( int u = 0 ; u < n ; u ++) if ( free_left ( u )) { zl [ u ] = 1 ; q . push ( u ); } while ( q . size ()) { int u = q . front (); q . pop (); for ( int v : g [ u ]) if ( mt [ v ] != u && ! zr [ v ]) { // non-matching edge L -> R zr [ v ] = 1 ; if ( mt [ v ] != - 1 && ! zl [ mt [ v ]]) { zl [ mt [ v ]] = 1 ; q . push ( mt [ v ]); } // matching edge R -> L } } vector < int > cover ; for ( int u = 0 ; u < n ; u ++) if (! zl [ u ]) cover . push_back ( u ); // L \\ Z_L for ( int v = 0 ; v < m ; v ++) if ( zr [ v ]) cover . push_back ( n + v ); // R ∩ Z_R # Consequences Consequences, each a one-line reduction maximum independent set = V ∖ minimum cover, so its size is n - |M| and it is computable , not just bounded — \"choose the most cells with no two attacking\" ( Matching Applications ), minimum edge cover (uncovered-vertex-free set of edges): n - |M| for graphs without isolated vertices — take the matching plus one edge per unmatched vertex, Dilworth's theorem : in a poset, the maximum antichain size = minimum number of chains covering the set. Build the bipartite graph with an edge x → y when x < y ; a matching of size k gives a chain cover of size n - k , and Kőnig's cover gives an antichain of the same size — this is the algorithm for \"longest non-decreasing subsequence\"-family problems, minimum path cover in a DAG (vertex-disjoint directed paths covering all vertices): split each v into v_out, v_in , add u_out → v_in for each DAG edge, and the answer is n - |matching| ( Matching Applications ), maximum bipartite induced matching / \"2D domino placement\" : several of these reduce to matching on a derived graph, where the same cover argument certifies optimality. # The min-cut view Note The min-cut view (why the same formula appears in Min-Cut Models ) With unit capacities on s → L and R → t and ∞ on the middle edges, every finite cut is: drop a left vertex (pay 1) or drop a right vertex (pay 1) so that no ∞ edge survives — i.e. a vertex cover. So the min cut is the min cover, and \"max matching = min cover\" is a special case of max-flow min-cut. The alternating DFS above is what a max flow's final residual graph is : Z_L = left vertices still reachable from s . # Output-size traps Watch out Output-size traps If the problem asks for the cover, you must run the reachability on the final matching — using the matching mid-loop gives a set that is not a cover, Isolated vertices: they are never in Z_R but they are in L ∖ Z_L only if free... an isolated left vertex is free, hence in Z_L , hence not in the cover — correct; ", "w": 881, "h": [["Kőnig's theorem", "kőnigs-theorem"], ["Extracting the cover", "extracting-the-cover"], ["Consequences", "consequences"], ["The min-cut view", "the-min-cut-view"], ["Output-size traps", "output-size-traps"]]}, {"u": "/matching/hungarian/", "t": "The Hungarian Algorithm", "s": "Weighted assignment in O(n^3) with labels and equality subgraphs — the min-cost flow specialisation that is shorter, faster, and needs no graph.", "c": "Matching", "k": "matching weights dp hard", "b": "# The assignment problem Definition Given an n × n cost matrix A , choose a permutation π minimising ∑_i A[i][π(i)] . (Maximise a benefit? Negate, or use labels with the opposite sign.) # Labels, slack and the equality subgraph The duality that makes it work Keep labels u_i (left) and v_j (right) with the invariant u_i + v_j ≤ A[i][j] (feasible). The equality subgraph has the edges where equality holds. Then: any matching M in the equality subgraph with |M| = n is optimal, because ∑_i A[i][π(i)] = ∑_i (u_i + v_π(i)) = ∑ u + ∑ v , which is a lower bound for every permutation; so the algorithm alternates two moves: augment inside the equality subgraph, and when it cannot, adjust labels to add exactly one new edge while keeping feasibility. # Implementation cpp hungarian.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // A is 1-indexed, n x n. Returns the min cost and p[] = assignment (p[j] = row matched to column j). vector < long long > u ( n + 1 ), v ( n + 1 ); vector < int > p ( n + 1 ), way ( n + 1 ); for ( int i = 1 ; i <= n ; i ++) { p [ 0 ] = i ; int j0 = 0 ; vector < long long > minv ( n + 1 , INF ); vector < char > used ( n + 1 , false ); do { used [ j0 ] = true ; int i0 = p [ j0 ], j1 = - 1 ; long long delta = INF ; for ( int j = 1 ; j <= n ; j ++) if (! used [ j ]) { long long cur = A [ i0 ][ j ] - u [ i0 ] - v [ j ]; if ( cur < minv [ j ]) { minv [ j ] = cur ; way [ j ] = j0 ; } if ( minv [ j ] < delta ) { delta = minv [ j ]; j1 = j ; } } for ( int j = 0 ; j <= n ; j ++) { if ( used [ j ]) { u [ p [ j ]] += delta ; v [ j ] -= delta ; } else minv [ j ] -= delta ; } j0 = j1 ; } while ( p [ j0 ]); do { // augment along `way` int j1 = way [ j0 ]; p [ j0 ] = p [ j1 ]; j0 = j1 ; } while ( j0 ); } // cost = -v[0] (or sum A[p[j]][j]); assignment: column j gets row p[j] Note Read the code as the duality, not as magic cur = the slack A[i_0][j] - u_i 0 - v_j ≥ 0 ; minv[j] is the minimum slack to reach column j from the alternating tree built so far; delta is the minimum of minv over the unvisited columns, exactly the largest label shift that keeps every reduced cost ≥ 0 while making one new edge tight. After the shift, the columns with minv[j] == delta join the equality subgraph, i.e. the tree grows by at least one vertex each iteration — hence ≤ n iterations per row and O(n^2) work per row: O(n^3) . # Correctness Theorem Correctness At the end, p is a perfect matching in the equality subgraph of feasible labels, therefore optimal; and the labels are always feasible because every shift subtracts the minimum slack. Proof Feasibility: for j not in the tree, u is unchanged for its row's... formally, minv[j] -= delta is exactly A[i][j] - u_i - v_j ≥ slack - δ ≥ 0 for the tree row i that achieved minv[j] , and other rows only become \"more slack\" since their u increases by δ at most as much; for j in the tree, v_j decreases by δ while u_p[j] increases by δ , so the sum u+v is unchanged and equality edges stay tight. Optimality is the bound above: any permutation costs ≥ ∑ u + ∑ v , and the produced one attains it (all its edges are tight). ∎ # Hungarian vs min-cost max-flow Hungarian vs. min-cost max-flow on the same instance Hungarian MCMF (potentials + Dijkstra) setup O(n^2) matrix, no graph build 2n + 2 nodes, n^2 edges time O(n^3) always O(n^2 · n log n) = O(n^3 log n) , similar constant rectangular k × n , k ≤ n pad with zero rows, same code free extra constraints (forbidden pairs, capacity on a side) awkward natural memory O(n^2) for A O(n^2) for the edges — both die at n ≈ 5000 maximise with large weights trivial same # Four details that decide WA vs AC Watch out Four details that decide WA vs AC u is indexed by rows , v by columns , and the answer is -v[0] — using ∑ A after the fact is safer than any identity, for a max problem with non-negative weights, negate A ; labels may then be negative, which is fine — but do not initialise u , v to 0 when A is huge and you use int (overflow in cur )", "w": 972, "h": [["The assignment problem", "the-assignment-problem"], ["Labels, slack and the equality subgraph", "labels-slack-and-the-equality-subgraph"], ["Implementation", "implementation"], ["Correctness", "correctness"], ["Hungarian vs min-cost max-flow", "hungarian-vs-min-cost-max-flow"], ["Four details that decide WA vs AC", "four-details-that-decide-wa-vs-ac"], ["Variants", "variants"]]}, {"u": "/matching/applications/", "t": "Matching Applications", "s": "Domino tilings, rook placements, DAG path covers, poset width, and \"maximum non-attacking set\" — five reductions, one algorithm.", "c": "Matching", "k": "matching reductions tilings hard", "b": "The five shapes to memorise Domino tiling of a board with holes : colour the board like a chessboard; each domino covers one black and one white cell ⇒ build the bipartite graph (black cell → adjacent white cell). A tiling covering the maximum number of cells = a maximum matching ; a tiling of the whole board exists iff the matching is perfect and the colour classes are equal (the colour-count check is the classic quick rejection). Non-attacking rooks / queens on marked cells : maximum number of rooks with no two in a row-and-column conflict = matching on (row, column) pairs. For \"minimum number of rooks covering all marked cells\" = minimum vertex cover = the same matching, by Kőnig ( Kőnig's Theorem and Minimum Covers ). Minimum path cover of a DAG (vertex-disjoint directed paths covering every vertex): split v → v_out, v_in ; answer = n - |M| . If paths may share vertices, it becomes a flow with vertex capacities ( Max Flow: Ford–Fulkerson, Dinic, Push–Relabel ). Poset width / Dilworth : maximum antichain = minimum chain cover; build x → y for x < y and use the transitive closure — so the reduction is \"matching on the closure\", O(n^3) with Floyd–Warshall ( Floyd–Warshall ) then HK. Maximum independent set in a bipartite graph = n − min cover: \"largest set of objects with no conflict pair\" — scheduling with mutual exclusions, \"keep the most shows\". Do not confuse this with \"delete the fewest vertices to make the graph bipartite\", which is NP-hard (odd-cycle transversal); independence in a graph that is already bipartite is the free case. Example Minimum path cover, worked Given a DAG with n vertices, cover all vertices with the fewest directed paths (each vertex in exactly one path). Each matching edge u_out → v_in means \"the path continues from u to v \", i.e. it saves one path: start with n single-vertex paths, and each such link merges two into one. So the count is n - |M| , and the paths themselves are read off the successors. Adding \"at most K paths\" or \"each path has length ≤ L\" is no longer matching — those need flow with an extra layer or become NP-hard; know that the clean version is exactly the unconstrained one. Theorem Tiling with dominoes: when the answer is 'no' for a reason you can print If a complete domino tiling of a board with holes does not exist, the certificate is either unequal colour-class sizes, or a vertex cover of the cell-adjacency graph smaller than the number of cells/2 — i.e. the matching itself is the proof. For the classic \"remove two opposite corners of a chessboard\" argument, the cover is one colour class: the obstruction is exactly what Kőnig names. Note Tilings that are NOT matching Tromino/L-tile tilings : not bipartite-2-to-1; usually DP over rows/columns ( Linear Recurrences from Graphs and Matrices ) or a checker invariant, domino tilings counted (not \"does one exist\"): that is the permanent/Pfaffian world — for a planar bipartite graph, Kasteleyn: # perfect matchings = √(|det K|) with a signed adjacency. CSES \"Counting Tilings\" (2181) is a transfer-matrix DP, not a determinant — do not confuse existence (matching), counting (Pfaffian/DP), and optimisation (min-cost matching), tilings of a region by rectangles of size ≥ 2 with minimum cost : that is a flow with submodular costs, and generally NP-hard once the pieces are not \"1×2 or 2×1\". Other places matching is the hidden half Stable marriage / hospital-residents : Gale–Shapley produces a matching with an optimality property, not a maximum; the \"minimum regret\" variants become bipartite matching + binary search on a threshold, Assignment of tasks to machines with a capacity k : replace each machine by k clones (matching) or use flow with capacity k (same thing, less memory), Graph bipartiteness is a precondition, not a step : check it with 2-colouring first ( Bipartite Graphs and 2-Colouring ); if the graph is not bipartite, most of these reductions are dead, and the same question (e.g. maximum independent set) becomes NP-hard ( The NP", "w": 844, "h": []}, {"u": "/matching/general-matching/", "t": "General Matching and Blossoms", "s": "Why odd cycles break the bipartite algorithm, what a blossom is, Edmonds' contraction, and the Tutte/Berge formulas.", "c": "Matching", "k": "matching blossoms hard olympiad", "b": "# Blossoms: why bipartite logic dies Definition Given a matching M in an arbitrary graph, a blossom is an odd cycle C with |C| = 2k+1 together with a vertex b ∈ C (its base ) such that the path from b along C in either direction is alternating starting with a matched edge... equivalently: C has k matched edges, and the two edges of C incident to b are both unmatched. Contracting C into a single vertex preserves matchings up to one extra matched edge. Note Why bipartite logic dies on a triangle Take K_3 with M = {uv} and a free vertex w . Searching for an augmenting path from w : w → u (unmatched), then from u the only way onward is the matched edge uv , landing on v , and from v the unmatched edge vw returns to the start. The alternating walk is a cycle of odd length — so \"reachable\" sets become inconsistent: the same vertex is reached at both even and odd distance, and the DFS's used marker either loops forever or refuses a legitimate augmentation. Blossoms are exactly these odd cycles; the fix is to contract each one, keep searching in the smaller graph, and expand the answer afterwards. # Edmonds' matching theorem Theorem Edmonds' matching theorem A graph has a perfect matching iff for every U ⊆ V , the number of odd components of G - U is at most |U| . (Tutte's 1-factor theorem; equivalently, the maximum matching has size tfrac12 min_U (n - o(G-U) + |U|) — Berge–Edmonds formula.) Proof Necessity: in a perfect matching, each odd component of G-U must send at least one edge into U , and those edges are distinct per component, so |U| ≥ o(G-U) . Sufficiency: contrapositive via the algorithm — if the search for an augmenting path fails in the maximal-augmentation state, the set U of bases of the blossoms/outer vertices reached by the alternating forest violates the condition: every odd component of G - U is a contracted blossom with all its vertices matched internally, and each outer vertex of U accounts for at most one such component. The construction of U from the failed search is the certificate, which is why the theorem is a proof of existence and the algorithm's correctness argument at once. ∎ # What to implement, realistically What to implement, realistically O(V^3) Edmonds with union-find + explicit contraction : ~70 lines; correct, slow constant; use when V ≤ 500 , O(V E) blossom growth without global contraction (the \"labb, maxlab\" Gabow style): faster, but 3× the code, randomised algebraic approach : the Tutte matrix over a large field, rank = 2 · (max matching size); O(n^ω) with Gaussian elimination, ~25 lines, and it answers \"is there a perfect matching\", \"which edges are allowed\", and \"the size\" with a one-sided error of 2^-30 per trial. This is often the shortest correct solution in a contest, at the price of a probabilistic argument you must be able to state, for the special cases : a tree (DP, Matching: Definitions and Duality problems), a cactus, a planar graph with small faces (Kasteleyn/Pfaffian for counting), or general graphs where maximum matching is only needed for \"does a near-perfect matching exist\" (then the greedy maximal matching plus a few augmentations usually passes, but has no guarantee — do not rely on it). cpp blossom-sketch.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // find-augmenting-path with blossom contraction, O(V*E) per search: // base[v], p[v], q = BFS queue of \"outer\" vertices // lca(a, b): walk up via p[] marking used[] to find the blossom base // mark_path(v, b, children): walk v up to b, contracting each vertex on the way // when an edge (a,b) joins two outer vertices of different trees: // if lca(a,b) is undefined -> augment along a + path to root + b // else -> blossoms: mark_path(a,b,a), mark_path(b,a,b) and enqueue contracted vertices int lca ( int a , int b ) { static vector < bool > used ( n ); fill ( used . begin (), used . end (), false ); for (;;) { a = base [ a ]; used [ a ] = true ; if (! match [ a ]) break ; a = p [ match [ a ]]; } for (;;) { b = base [ ", "w": 1025, "h": [["Blossoms: why bipartite logic dies", "blossoms-why-bipartite-logic-dies"], ["Edmonds' matching theorem", "edmonds-matching-theorem"], ["What to implement, realistically", "what-to-implement-realistically"], ["The three implementation cliffs", "the-three-implementation-cliffs"], ["Weighted general matching", "weighted-general-matching"]]}, {"u": "/special/planar/", "t": "Planarity and Euler's Formula", "s": "m <= 3n - 6 and what it buys you, the Kuratowski obstruction, the linear-time tests you can actually run, and why planarity keeps appearing in olympiad graph problems.", "c": "Special Topics", "k": "planar counting extremal hard", "b": "# Euler's formula Theorem Euler's formula A connected planar graph drawn without crossings, with n vertices, m edges and f faces (the outer face counted), satisfies n - m + f = 2 . Proof Induction on m . A spanning tree has m = n-1 , f = 1 : equality. Adding an edge between two existing vertices of a connected drawing splits one face into two, so both m and f increase by 1 and n - m + f is unchanged. (Formally: any connected graph is a tree plus m - n + 1 chords, each chord increasing f by exactly 1 because the unique tree path between its endpoints cuts a face.) ∎ # The two edge bounds Corollary The two inequalities For a simple planar graph with n ≥ 3 : m ≤ 3n - 6 . If additionally the graph has no triangle (bipartite, e.g.): m ≤ 2n - 4 . Proof Every face has boundary length ≥ 3 (simplicity), so 2m = ∑_faces |∂| ≥ 3f , i.e. f ≤ 2m/3 . Euler: 2 = n - m + f ≤ n - m + tfrac{2m}{3} = n - tfrac m3 , giving m ≤ 3n - 6 . With girth ≥ 4 replace 3 by 4: f ≤ m/2 and m ≤ 2n - 4 . ∎ # A counterexample detector Note Use it as a counterexample detector , not an algorithm \" K_5 is not planar\": m = 10 > 3· 5 - 6 = 9 . ✓ \" K_3,3 is not planar\": m = 9 > 2· 6 - 4 = 8 (bipartite ⇒ girth 4). ✓ \"the dodecahedral graph is planar and has 20 vertices\": then f = 2 - n + m = 12 , and all faces are pentagons — 2m = 30 = 5f . ✓ These one-line computations settle a large fraction of olympiad planarity questions, and the same counting with 1 ≤ face size gives every planar-family bound (e.g. \"at most 3n-6 pairs of touching regions\", \"some face has ≤ 6 sides\" ⇒ min degree ≤ 5 ⇒ five-colour theorem). # What planarity buys algorithmically What planarity costs you algorithmically Testing planarity: Hopcroft–Tarjan linear time, but nobody writes it; practical options are (a) Boyer–Myrvold via a library (LEMON/NetworkX/OGDF), (b) Kuratowski-subgraph search on small n , (c) for contest problems, planarity is given by the story (a map, a grid, regions) and you only need Euler's formula or a dual construction; The dual graph G^* : a vertex per face, an edge per primal edge. G^** = G for 2-connected plane graphs; a face of G is a cycle iff the corresponding G^* edges form a bond; spanning trees of G correspond bijectively to spanning trees of G^* (the complement of a tree's edges is a tree in the dual) — which is the cleanest proof that the number of spanning trees is the same for G and G^* ( The Matrix–Tree Theorem ); Planar separators : a planar graph has a set of O(√n) vertices whose removal splits it into parts of size ≤ 2n/3 (Lipton–Tarjan). This is why planar problems admit O(n^3/2) or subexponential divide-and-conquer, and why \"small treewidth\" ( Independent Sets, Cliques, and Treewidth 's remark) is the generalisation that makes DP possible; Four colour theorem : every planar graph is 4-colourable. Its proof is a computer check of 1936 reducible configurations; the algorithmic content is that greedy/DP colouring works because of the \"min degree ≤ 5\" structure (five-colouring is elementary and linear, Graph Colouring ), and that 4-colouring a given planar graph is O(n^2) (Robertson–Sanders–Seymour–Thomas), not something you implement. # Three statements people get wrong Watch out Three statements people get wrong m ≤ 3n-6 is necessary, not sufficient : the Petersen graph has n=10, m=15 ≤ 24 and is non-planar. Only K_5 and K_3,3 subdivisions/minors are the real obstruction (Kuratowski: non-planar ⟺ contains a subdivision of K_5 or K_3,3 ; Wagner: ⟺ has one as a minor , which is the stronger and cleaner form). Planarity depends on the abstract graph, not the drawing: a drawing with crossings proves nothing (a graph can be planar yet look tangled in your sketch). \"It has a crossing\" ≠ \"non-planar\"; only the absence of any crossing-free drawing does. Euler's formula needs connected : for c components, n - m + f = 1 + c . Forgetting c is the standard off-by-1 in \"count the regions\" problems (and the region count includes the outer one). # A contest-shaped applicat", "w": 902, "h": [["Euler's formula", "eulers-formula"], ["The two edge bounds", "the-two-edge-bounds"], ["A counterexample detector", "a-counterexample-detector"], ["What planarity buys algorithmically", "what-planarity-buys-algorithmically"], ["Three statements people get wrong", "three-statements-people-get-wrong"], ["A contest-shaped application", "a-contest-shaped-application"]]}, {"u": "/special/degree-sequence/", "t": "Degree Sequences and Graphical Sequences", "s": "The handshaking lemma's consequences, Erdős–Gallai and Havel–Hakimi — which one to use when you must build the graph.", "c": "Special Topics", "k": "degree sequence greedy existence core", "b": "# Realisability Definition A sequence d_1 ≥ d_2 ≥ … ≥ d_n ≥ 0 is graphical if some simple graph has exactly these degrees. deg(v) = degree; ∑_v deg(v) = 2m (handshaking). Immediate necessary conditions (all of them cheap to check) ∑ d_i is even (handshaking); d_1 ≤ n - 1 , and more: ∑_i=1^k d_i ≤ k(k-1) + ∑_i>k min(d_i, k) for every k — this is the full characterisation (below); a vertex of degree n-1 and a vertex of degree 0 cannot coexist (and more generally d_1 = n - 1 - t bounds the number of zeros by t ); if exactly one vertex has odd degree, the sequence is not graphical (the count of odd-degree vertices is always even); tree version: a sequence of n positive integers is the degree sequence of a tree iff ∑ d_i = 2n - 2 (construct with Prüfer codes, Counting Trees: Cayley and Prüfer ). # Erdős–Gallai Theorem Erdős–Gallai (1960) ∑ d_i is even and for every 1 ≤ k ≤ n , ∑_i=1^k d_i ≤ k(k-1) + ∑_i=k+1^n min(d_i, k). Key idea The left side is the number of edges inside the k highest-degree vertices plus those leaving them. The right side is the maximum possible: at most k(k-1) inside (complete graph), and each remaining vertex i can send at most min(d_i, k) edges into the set. Equality conditions aside, the point is that the \"top k \" is the worst case — which is why sorting is part of the theorem. # Havel–Hakimi, the constructive test Theorem Havel–Hakimi (1955), constructive d is graphical iff d_1 = 0 (all zero) or, with d_1 = k > 0 , the sequence obtained by deleting d_1 and subtracting 1 from the next k entries (then re-sorting) is graphical. Proof Necessity: in any realisation, the vertex of max degree k has k neighbours; those are at least as \"hungry\" as the others, so if a realisation exists one exists where it attaches to the k largest — the standard swap argument : if u (degree k ) is adjacent to w but not to v , and deg(v) ≥ deg(w) with v adjacent to some neighbour x of... concretely, if uw ∈ E , uv ∉ E and there is x with xv ∈ E , xu ∉ E , then replacing uw, xv by uv, xw keeps every degree and moves an edge toward v . Repeating puts u 's neighbours among the k largest. Sufficiency is immediate: attach the vertex to those k entries and realise the rest. ∎ # Implementation cpp havel-hakimi.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 // builds an adjacency matrix, or reports \"not graphical\" bool realise ( vector < pair < int , int >> d , vector < vector < int >> & A ) { // d = (deg, vertex) priority_queue < pair < int , int >> q ; for ( auto [ deg , v ] : d ) if ( deg ) q . push ({ deg , v }); while ( q . size ()) { vector < pair < int , int >> need ; int k = q . top (). first , u = q . top (). second ; q . pop (); if ( k > ( int ) q . size ()) return false ; for ( int i = 0 ; i < k ; i ++) { need . push_back ( q . top ()); q . pop (); } for ( auto [ deg , v ] : need ) { A [ u ][ v ] = A [ v ][ u ] = 1 ; if ( deg - 1 > 0 ) q . push ({ deg - 1 , v }); } } return true ; } # Which test, when Note Which test, when only need yes/no , n ≤ 5000 : Erdős–Gallai with a two-pointer/sorted-prefix computation, O(n) after sorting (using the min split point found by binary search) — or just O(n^2) , which is 2.5×10^7 and fine, need the graph : Havel–Hakimi with a heap/insertion into a sorted array, O(n^2) or O(nm) , and it produces the edges, need it to be connected : the clean sufficient condition is 2 ≤ d_n ≤ d_1 ≤ n-1 and ∑ d_i even — then Erdős–Gallai applied to the shifted sequence yields a connected realisation (Anstee's characterisation adds the prefix inequalities ∑_i=1^k d_i ≥ k(k+1) replaced by \" > for k<n \", which is what you actually verify), or, easier to code: run Havel–Hakimi on a forced spanning path first and check the remainder is graphical; need a tree : sum = 2n-2 with all d_i ≥ 1 , construct from the Prüfer sequence, need a directed realisation of (in,out) pairs: the Ford–Fulkerson bipartite test (source→out-vertices→in-vertices→sink, capacity 1, no self-loops and no parallel arcs ⇒ a matching/flow question, Max Flow: ", "w": 1017, "h": [["Realisability", "realisability"], ["Erdős–Gallai", "erdősgallai"], ["Havel–Hakimi, the constructive test", "havelhakimi-the-constructive-test"], ["Implementation", "implementation"], ["Which test, when", "which-test-when"], ["Regularity and the two classic traps", "regularity-and-the-two-classic-traps"], ["Realisation in a contest statement", "realisation-in-a-contest-statement"]]}, {"u": "/special/coloring/", "t": "Graph Colouring", "s": "Greedy bounds, Brooks' and Reed's theorems, chromatic number versus clique number, and the three colouring problems that are actually solvable.", "c": "Special Topics", "k": "coloring greedy np-complete core", "b": "# Four parameters, one paragraph Definition A proper k -colouring assigns colours to vertices so that adjacent vertices differ. χ(G) = the minimum k ; ω(G) = clique number; Δ(G) = maximum degree; g(G) = girth. Four inequalities, all proved by one paragraph χ(G) ≥ ω(G) (a clique needs all-different colours), χ(G) ≤ Δ + 1 (greedy: when you colour a vertex, at most Δ colours are forbidden), χ(G) ≤ ⌊ 1/2 + √(2m + 1/4) ⌋ (greedy in degeneracy order: χ ≤ degeneracy + 1 ≤ √(2m) + 1 ), χ(G) ≤ degeneracy(G) + 1 , where degeneracy = max_H ⊆ G δ(H) is found by repeatedly deleting a minimum-degree vertex. Since the degeneracy is at most ⌊ √(2m) ⌋ , this subsumes the previous bound on sparse graphs — and it is the only one of the four that is useful in code . # Brooks’ theorem Theorem Brooks (1941) If G is connected with maximum degree Δ , then χ(G) ≤ Δ unless G is a complete graph or (for Δ = 3 ) an odd cycle. Proof Both exceptions need Δ + 1 : K_Δ+1 has Δ+1 mutually adjacent vertices, and C_2k+1 needs 3 colours with Δ = 2 . Otherwise take a vertex v with two non-adjacent neighbours x,y (exists because G is not a clique; for Δ=2 the path/cycle case is immediate). Order the vertices so that x, y come last and every other vertex has a later neighbour — a reverse BFS/leaf-elimination order works. Colour greedily in the reverse of that order. Every vertex other than v has a later neighbour, so when it is coloured at most Δ - 1 colours are forbidden — one of Δ remains. The clean finish: give x and y the same colour first (they are non-adjacent), then colour the remaining vertices in reverse BFS order rooted at {x,y} , with v last. Every vertex coloured before v has a neighbour coloured earlier, so at most Δ - 1 colours are forbidden at its turn; and when v is coloured, its Δ neighbours use at most Δ - 1 colours because x and y share one. So Δ colours suffice. ∎ # What a solver actually does Note What a solver actually does 2-colouring = bipartiteness, O(n+m) by BFS ( Bipartite Graphs and 2-Colouring ). Exact and the only case that is polynomial for general graphs, 3-colouring is NP-complete even for max-degree-4 graphs, but polynomial for: bipartite (trivial), interval and chordal graphs (perfect: χ = ω , greedy on a perfect elimination order — Independent Sets, Cliques, and Treewidth ), planar with girth ≥ 5 (Grötzsch: 3-colourable), and fixed graphs (4 colours suffice for every planar graph, Planarity and Euler's Formula , but finding a 4-colouring is O(n^2) with the deep algorithm; the elementary route is 5-colouring by the min-degree-≤-5 argument), list colouring / DP colouring for bounded treewidth, and edge colouring by Vizing ( χ' ≤ Δ + 1 ; bipartite edge colouring = Δ exactly — König's line-colouring theorem, which is matching again, Bipartite Matching (Kuhn's Algorithm) ). # Greedy colouring in degeneracy order cpp greedy-colour.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // degeneracy ordering + greedy: always optimal on chordal/interval, within 1 of Brooks elsewhere vector < int > order , col ( n , - 1 ); { vector < int > deg ( n ); for ( int v = 0 ; v < n ; v ++) deg [ v ] = g [ v ]. size (); vector < vector < int >> buckets ( max_deg + 1 ); for ( int v = 0 ; v < n ; v ++) buckets [ deg [ v ]]. push_back ( v ); vector < char > removed ( n ); for ( int b = 0 ; b <= max_deg ; b ++) while ( buckets [ b ]. size ()) { int v = buckets [ b ]. back (); buckets [ b ]. pop_back (); if ( removed [ v ]) continue ; removed [ v ] = 1 ; order . push_back ( v ); for ( int to : g [ v ]) if (! removed [ to ] && deg [ to ] > b ) { deg [ to ]--; buckets [ deg [ to ]]. push_back ( to ); } } } for ( int i = n - 1 ; i >= 0 ; i --) { // reverse order = colour the \"hard\" ones first vector < char > used ( k + 1 ); for ( int to : g [ order [ i ]]) if ( col [ to ] != - 1 ) used [ col [ to ]] = 1 ; for ( int c = 0 ; c <= k ; c ++) if (! used [ c ]) { col [ order [ i ]] = c ; break ; } } # Where colouring statements bite Watch out Where colourin", "w": 1038, "h": [["Four parameters, one paragraph", "four-parameters-one-paragraph"], ["Brooks’ theorem", "brooks-theorem"], ["What a solver actually does", "what-a-solver-actually-does"], ["Greedy colouring in degeneracy order", "greedy-colouring-in-degeneracy-order"], ["Where colouring statements bite", "where-colouring-statements-bite"], ["Exercises", "exercises"]]}, {"u": "/special/twosat/", "t": "2-SAT", "s": "The implication graph, the \"x and not x in one SCC\" criterion, and the assignment read off the condensation order — with the linear-time proof.", "c": "Special Topics", "k": "sat scc reduction core", "b": "# The problem Definition Variables x_1..x_n , clauses (a ∨ b) where each literal is x_i or ¬ x_i . Decide whether a satisfying assignment exists (and produce one). **3**-SAT with the same shape is NP-complete ( The NP-complete Graph Problems Worth Knowing ); the difference is exactly what makes 2-SAT linear. # The implication-graph criterion Theorem The implication-graph criterion Rewrite each clause (a ∨ b) as the two implications (¬ a → b) and (¬ b → a) . The formula is satisfiable iff no variable x_i has x_i and ¬ x_i in the same strongly connected component of that digraph. Proof Necessity. If x_i ⇝ ¬ x_i ⇝ x_i , then in any assignment x_i ⇒ ¬ x_i and ¬ x_i ⇒ x_i must both hold, which is impossible — every implication on the cycle must be satisfied, and a cycle through both polarities forces a contradiction. Sufficiency. Contract to the condensation DAG and number the components in topological order. Define x_i := [ scc(¬ x_i) comes strictly before scc(x_i) in that order ]. This is well defined: no component contains both literals, so exactly one of the two orders holds. The key structural fact is that the implication graph is skew-symmetric : negating every literal is an anti-automorphism, u → v iff bar v → bar u . Hence on the condensation, C ≤ D iff bar D ≤ bar C (where bar C is the component of the negated literals). Now suppose a clause (a ∨ b) is falsified by this assignment: a and b are both false, i.e. bar a, bar b both true. Truth of bar a means scc(a) < scc(bar a) ... precisely, \" bar a true\" is the rule applied to the literal bar a , so scc(bar ā) = scc(a) comes before scc(bar a) : scc(a) < scc(bar a), scc(b) < scc(bar b). The clause's own implications give bar a → b , so scc(bar a) ≤ scc(b) , and bar b → a gives scc(bar b) ≤ scc(a) . Chaining: scc(a) < scc(bar a) ≤ scc(b) < scc(bar b) ≤ scc(a), a strict cycle in a DAG — impossible. So no clause is falsified. ∎ # Reading the assignment off the components Note Practical reading of the rule (one line of code) After Tarjan/Kosaraju, sat = (comp[2i] != comp[2i+1]) for all i ; the assignment is cpp twosat-assign.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 // literal encoding: 2*i = \"x_i is true\", 2*i+1 = \"x_i is false\" int neg ( int l ) { return l ^ 1 ; } void add_or ( int a , int b ) { // clause (a or b) as two implications adj [ neg ( a )]. push_back ( b ); adj [ neg ( b )]. push_back ( a ); } // comp[] = component number in REVERSE topological order (Kosaraju's second pass gives it free) vector < char > sat_ok ( 2 * n ); // ... compute comps first, then: for ( int i = 0 ; i < n ; i ++) if ( comp [ 2 * i ] == comp [ 2 * i + 1 ]) return false ; // x_i and ¬x_i together for ( int i = 0 ; i < n ; i ++) ans [ i ] = comp [ 2 * i ] > comp [ 2 * i + 1 ]; // true iff its SCC is later i.e. set x_i true when its own SCC is later than the SCC of ¬ x_i . The comparison uses component numbers from Kosaraju's second pass, which are already in reverse topological order, so no extra sorting is needed. # Modelling: the clause types you need Modelling: the three clause types you will actually need (¬ a ∨ ¬ b) = \"not both\": add a → bar b and b → bar a — a conflict edge; this is the \"choose an independent set of size...\" pattern restricted to pairs, a → b (an implication \"if x then y \") is (¬ a ∨ b) : both directions of an equivalence a rightarrow b are two clauses ⇒ a, b in the same SCC, so equality of variables is just SCC merging , and \"exactly one of a,b \" is (a∨ b)∧(¬ a∨¬ b) , \"at most one of x_1..x_k is true\" needs O(k^2) pairwise clauses — quadratic blowup is normally avoided with a chain of auxiliary variables (prefix counters): O(k) clauses, which is the trick used in \"sudoku-like grid constraints\" and \"at most one rectangle per row\". # Four ways 2-SAT solutions die Watch out The four ways 2-SAT solutions die Encoding literals as ± i and then mixing \"index by |i| \" with \"index by literal\" — use the 2i/2i{+}1 encoding above everywhere, including in add_or , forgetting the second impl", "w": 1040, "h": [["The problem", "the-problem"], ["The implication-graph criterion", "the-implication-graph-criterion"], ["Reading the assignment off the components", "reading-the-assignment-off-the-components"], ["Modelling: the clause types you need", "modelling-the-clause-types-you-need"], ["Four ways 2-SAT solutions die", "four-ways-2-sat-solutions-die"], ["A worked instance", "a-worked-instance"]]}, {"u": "/special/random-walk/", "t": "Random Walks and Cover Times", "s": "Hitting times as linear equations, commute-time bounds via effective resistance, and the walk-based algorithms that actually get used.", "c": "Special Topics", "k": "probability markov trees hard", "b": "# Hitting and commute times Definition A walk on G that at each step chooses a uniformly random neighbour of the current vertex. H_uv = hitting time = expected steps to reach v from u ; C_uv = H_uv + H_vu = commute time ; the cover time is the expectation of the first time all vertices have been visited. # Hitting times solve a linear system Theorem Hitting times solve a linear system For fixed target v : H_vv = 0 and H_uv = 1 + (1)/(deg(u)) ∑_w ∼ u H_wv (u ≠ v). On a connected graph this system has a unique solution. Proof First-step analysis: one step is taken (the +1 ), then the walk is at a uniformly random neighbour, and the expectation from there is the average — the Markov property makes the future independent of the past. Uniqueness: the coefficient matrix is I - P' where P' is the transition matrix with row v removed; because the walk reaches v with probability 1 (finite irreducible chain), the spectral radius of P' is < 1 , so I - P' is invertible. ∎ # Commute time is effective resistance Theorem Commute time = effective resistance C_uv = 2m · R_eff(u,v), where R_eff is the resistance between u and v when every edge is a 1 Ω resistor. Proof Inject one unit of current at u and extract it at v ; the potential φ then satisfies Lφ = mathbf 1_u - mathbf 1_v (Kirchhoff) and φ(u)-φ(v) = R_eff . The proof identifies the hitting-time difference with this potential. Define g(x) = H_xu - H_xv . Subtracting the two hitting-time equations cancels the +1 terms, so g is harmonic at every x ∉ {u,v} : g(x) is the average of g over its neighbours. The unit-current potential φ is harmonic at the same vertices, and both are determined by their values at u,v (uniqueness of harmonic functions with given boundary values, by the maximum principle). Comparing the edge differences: the walk crosses edge xy (in the direction away from v ) with rate proportional to (1)/(deg) times the stationary mass (deg(x))/(2m) , so the \"flow\" 2m ∇ g is a unit flow from u to v ; energy minimality then gives g(u)-g(v) = 2m (φ(u)-φ(v)) = 2m R_eff . Since C_uv = H_uv+H_vu = g(u) - g(v) with this g , the identity follows. ∎ # Values you can quote Consequences you can quote (with the resistance computed by hand) path on n vertices: R_eff = n-1 ohms in series ⇒ C = Θ(n^2) ⇒ hitting time from end to end is Θ(n^2) (the classic gambler's-ruin computation), complete graph K_n : R_eff = 2/n (direct edge 1 Ω in parallel with the n-2 two-edge paths, each of 2 Ω , giving (1 + tfrac{n-2}{2})^-1 = tfrac{2}{n} ) ⇒ C_uv = 2m R = n(n-1)·tfrac2n = 2(n-1) — and directly, while the walk is away from v each step lands on v with probability frac1{n-1} , so H_uv = n-1 and symmetry gives C_uv = 2(n-1) ✓; cycle C_n : R = ((n/2)(n/2))/(n) = n/4 ⇒ C = Θ(n^2) , lollipop / \"barbell\" graphs : cover time Θ(n^3) — the worst case for random walk covering, and the reason \"let the walk run and hope\" is not an algorithm, general bounds: cover time is at least n log n (coupon collector; tight on K_n ) and at most (4)/(27) n^3 (1+o(1)) (Feige); the elementary bound on the maximum hitting time is H_max ≤ 2m(n-1) = O(n^3) , from C_uv ≤ 2m · (n-1) using R_eff ≤ distance in edges ≤ n-1 , bipartite graphs : the walk is periodic, so stationary distribution arguments fail unless you add laziness (with probability ½ stay put) — a real trap, since π_u = (deg u)/(2m) only holds for the aperiodic (lazy) chain. # Two algorithms built on walks Note The two algorithms built on walks Monte-Carlo s – t connectivity / undirected ST = L (Reingold): a walk of length poly(n) finds a path in a connected graph with constant probability using O(log n) random bits per step, via expander graph products — the reason L is closed under complement, and the reason \"random walk on a graph\" is a complexity topic, not just a puzzle; Karp–Luby style walk-based counting/estimation , and loopy random walks for sampling from a distribution defined on states (MCMC): the mixing time bound ≤ a cover-type bound is the practical version. ", "w": 1195, "h": [["Hitting and commute times", "hitting-and-commute-times"], ["Hitting times solve a linear system", "hitting-times-solve-a-linear-system"], ["Commute time is effective resistance", "commute-time-is-effective-resistance"], ["Values you can quote", "values-you-can-quote"], ["Two algorithms built on walks", "two-algorithms-built-on-walks"], ["Worked: a walk on a tree", "worked-a-walk-on-a-tree"], ["Modelling traps", "modelling-traps"]]}, {"u": "/special/ramsey/", "t": "Ramsey Theory on Graphs", "s": "R(3,3)=6 and R(4,4)=18 with complete elementary proofs, the recursion behind every bound, and the counting argument that explains why the numbers explode.", "c": "Special Topics", "k": "ramsey extremal counting olympiad", "b": "# Ramsey numbers Definition R(s,t) is the least n such that every red/blue colouring of the edges of K_n contains a red K_s or a blue K_t . Equivalently: every graph on n ≥ R(s,t) vertices has a clique of size s or an independent set of size t (red = \"edge\"). Equality has two halves — every colouring of K_n forces the monochromatic clique, and some colouring of K_n-1 avoids both. # The one case you can do by hand Theorem R(3,3) = 6 Every 2-colouring of K_6 has a monochromatic triangle, and there is a colouring of K_5 with none. Proof Upper bound. Fix a vertex v of K_6 ; its 5 incident edges have 3 of one colour by the pigeonhole principle, say va, vb, vc red. If an edge of the triangle abc is red, it completes a red triangle with v ; otherwise a,b,c span a blue triangle. Lower bound. Colour K_5 with the red edges forming a 5-cycle: both colour classes are triangle-free, so no monochromatic triangle exists. ∎ # The recursion behind every upper bound Note The only recursion needed for every upper bound R(s,t) ≤ R(s-1,t) + R(s,t-1), R(s,t) = R(t,s), R(2,t) = t . Proof. Colour K_n with n = R(s-1,t) + R(s,t-1) , and take a vertex v . Split its neighbours into the red set A and the blue set B ; then |A| + |B| = n - 1 , so either |A| ≥ R(s-1,t) or |B| ≥ R(s,t-1) . In the first case, A contains a red K_s-1 (which together with v is a red K_s ) or a blue K_t ; the second case is symmetric. ∎ Telescoping from R(2,t)=t gives R(s,t) ≤ C(s+t-2, s-1) , and hence R(k,k) ≤ 4^k+o(k) . One sharpening is worth knowing: if R(s-1,t) and R(s,t-1) are both even , the inequality is strict, R(s,t) ≤ R(s-1,t) + R(s,t-1) - 1 (otherwise a colouring of K_n-1 would force each vertex to have exactly R(s-1,t)-1 red neighbours, and the parity of the red degrees contradicts ∑ odd = even). # Small exact values Theorem Small exact values R(3,3)=6 , R(3,4)=9 , R(3,5)=14 , R(4,4)=18 , R(3,6)=18 , R(3,7)=23 , R(3,8)=28 , R(3,9)=36 . R(5,5) is unknown; the best published range is 43 ≤ R(5,5) ≤ 48 , and lim_k R(k,k)^1/k is known only to lie in [√2, 4] . # Two hand-computed values: R(3,4) and R(4,4) Example R(3,4) = 9, both halves Upper bound: R(3,4) ≤ R(2,4) + R(3,3) = 4 + 6 = 10 , and the parity strengthening (both terms even) gives R(3,4) ≤ 9 . Lower bound: on 8 vertices take the circulant graph on ℤ_8 joining i to i ± 1 and i+4 (the Möbius–Kantor cubic graph). It has 8 vertices, is triangle-free, and its independence number is 3 — so it has no red triangle and no independent set of size 4, i.e. a colouring of K_8 with no red K_3 and no blue K_4 . Hence R(3,4) > 8 . ∎ Example R(4,4) = 18, both halves Upper bound: R(4,4) ≤ R(3,4) + R(4,3) = 9 + 9 = 18 (the parity trick does not apply: 9 is odd). Lower bound: on 17 vertices, colour ij red iff i - j (mod 17) ∈ {±3, ±5, ±6, ±7} . Every vertex has red degree 8; checking all C(17, 4) = 2380 four-sets shows that no four vertices are pairwise red-adjacent and none are pairwise blue-adjacent. So R(4,4) ≥ 18 , and with the upper bound, equality. (The check is 10 lines of code — worth running yourself once, as a reminder that \"Ramsey lower bounds\" are finite verifications, not mysticism.) # The probabilistic lower bound Theorem Exponential lower bound (Erdős, 1947) If C(n, k) 2^1-C(k, 2) < 1 then R(k,k) > n . In particular R(k,k) ≥ (2^1/2 - o(1))^k . Proof Colour each of the C(n, 2) edges red/blue independently with probability ½. For a fixed k -set S , \"S is monochromatic\" has probability 2 · 2^-C(k, 2) , so by the union bound Pr[∃ monochromatic K_k] ≤ C(n, k) 2^1-C(k, 2) < 1. With positive probability no monochromatic K_k exists, which proves such a colouring exists. Substituting C(n, k) ≤ (en/k)^k and n = ⌊ 2^k/2⌋ , the exponent is klog_2(en/k) + 1 - C(k, 2) , negative for large k because k^2/4 dominates k log k . ∎ # Reading the bounds correctly Reading the bounds correctly the argument is non-constructive : it shows existence by a probability estimate; derandomising it (conditional expectations) gives a polynomial-time a", "w": 1695, "h": [["Ramsey numbers", "ramsey-numbers"], ["The one case you can do by hand", "the-one-case-you-can-do-by-hand"], ["The recursion behind every upper bound", "the-recursion-behind-every-upper-bound"], ["Small exact values", "small-exact-values"], ["Two hand-computed values: R(3,4) and R(4,4)", "two-hand-computed-values-r34-and-r44"], ["The probabilistic lower bound", "the-probabilistic-lower-bound"], ["Reading the bounds correctly", "reading-the-bounds-correctly"], ["How the arguments appear in contests", "how-the-arguments-appear-in-contests"], ["Erdős–Szekeres", "erdősszekeres"], ["Four precision points", "four-precision-points"]]}, {"u": "/special/independent/", "t": "Independent Sets, Cliques, and Treewidth", "s": "Why maximum independent set is hopeless in general, what makes it easy on trees, chordal graphs and bounded-treewidth graphs, and the two approximations that are worth coding.", "c": "Special Topics", "k": "independent set clique treewidth np-hard hard", "b": "# The two problems, and how they differ Definition Independent set : α(G) = max |S| with no edge inside S . Clique : ω(G) = α(bar G) . Vertex cover : τ(G) = min |C| touching every edge, with α + τ = n always. All three are NP-hard in general ( The NP-complete Graph Problems Worth Knowing ), and all three become polynomial on the four graph classes below. # Where it becomes easy Where it becomes easy — and the exact reason trees / forests : DP with two states per vertex, dp[v][0/1] = best with v excluded/included, combined over children — Distance, Radius, Eccentricity verbatim, O(n) ; also maximum matching by König gives α on bipartite graphs, and on a tree the two approaches agree (Kőnig + matching DP), bipartite graphs : α = n - ν where ν is the maximum matching (König's theorem, Kőnig's Theorem and Minimum Covers ) — the cover side is polynomial, so the independent set is too, chordal graphs (every cycle of length ≥ 4 has a chord): χ = ω and both are found by a perfect elimination ordering (PEO) — repeatedly delete a vertex whose later neighbours form a clique; maximum clique = max_v (deg_later(v) + 1) , and α = the minimum number of cliques covering V ; all in O(n+m) via maximum cardinality search (lexBFS choosing the vertex with the most already-numbered neighbours), bounded treewidth k : DP over a tree decomposition with 2^k+1 states per bag, O(2^k n) — the single most powerful \"easy\" case, because it also handles dominating set, colouring, Steiner tree, Hamiltonian cycle (for fixed k ) with the same skeleton, interval graphs (a chordal special case): greedy by right endpoint gives maximum independent set in O(n log n) , and it is the \"select the most non-overlapping intervals\" problem — not a graph algorithm at all, once you see the structure. # Greedy guarantees and their limits Theorem Greedy guarantees, and their limits Repeatedly take the vertex of minimum degree, delete it and its neighbours: the result is an independent set of size ≥ ∑_v (1)/(deg(v)+1) ≥ (n)/(bar d + 1) ( Caro–Wei / Turán ). No polynomial-time algorithm achieves a better than O(n loglog n / log n) approximation ratio unless P = NP, and on general graphs the greedy bound is within a logarithmic factor of the best possible — so greedy is \"the right answer\" up to logs. Proof Order the vertices uniformly at random and select each vertex that comes before all of its neighbours: the probability is exactly (1)/(deg(v)+1) (of the vertices in N[v] , each is equally likely to be first), so the expected selected set has that size and is independent by construction. ∎ # Trees: the two-state DP cpp max-independent-tree.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 // O(n) on a tree; the same two-state skeleton works for vertex cover and dominating set // (dominating set needs three states: in / covered-by-child / must-be-covered-by-parent) long long dp [ n ][ 2 ]; void dfs ( int v , int p ) { dp [ v ][ 0 ] = 0 ; dp [ v ][ 1 ] = 1 ; for ( int to : g [ v ]) if ( to != p ) { dfs ( to , v ); dp [ v ][ 0 ] += max ( dp [ to ][ 0 ], dp [ to ][ 1 ]); // v out: child free dp [ v ][ 1 ] += dp [ to ][ 0 ]; // v in: children must be out } } # Treewidth, in one paragraph Note Treewidth in one paragraph, since it is the useful generalisation A tree decomposition of G is a tree of bags B_1..B_t ⊆ V with (i) every vertex in some bag, (ii) every edge inside some bag, (iii) for each vertex, the bags containing it induce a connected subtree. The width is max |B_i| - 1 , and tw(G) is the minimum. Trees have tw 1, series-parallel 2, outerplanar 2, planar graphs up to Θ(√n) , cliques n-1 . Then: any \"MSO-definable\" or \"state-per-vertex\" problem is O(f(k) · n) on graphs of tw k , by the same DP over the decomposition — which is precisely why Escape Routes: What To Do When It Is NP-Hard 's \"parameterise by structure instead of by n \" works. Computing treewidth is NP-hard, but 4-approximation is easy and exact O(2^k n) algorithms exist for fixed k (minimal triangulations / elimination orderings: t", "w": 1150, "h": [["The two problems, and how they differ", "the-two-problems-and-how-they-differ"], ["Where it becomes easy", "where-it-becomes-easy"], ["Greedy guarantees and their limits", "greedy-guarantees-and-their-limits"], ["Trees: the two-state DP", "trees-the-two-state-dp"], ["Treewidth, in one paragraph", "treewidth-in-one-paragraph"], ["Four missteps", "four-missteps"], ["Two variants, two worlds", "two-variants-two-worlds"]]}, {"u": "/appendix/template/", "t": "Contest Template", "s": "One file of includes, typedefs and helpers that covers every algorithm in this book — with the settings you should not change.", "c": "Appendix", "k": "template reference core", "b": "Note How to use this page Copy it into your editor's snippet, then delete what you do not need. A template that grows past ~200 lines stops being a template: you begin to carry bugs you never read. The sections below are ordered by how often they are used in this book. # The template cpp template.cpp Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 # include < bits / stdc ++. h > using namespace std ; using ll = long long ; using pii = pair < int , int >; const int INF = 0x3f3f3f3f ; const ll LINF = ( ll ) 4e18 ; // ---------- modular arithmetic (prime modulus arithmetic, @matrices/recurrences) ---------- struct Mod { static const ll P = 998244353 ; static ll powm ( ll a , ll e = P - 2 ) { ll r = 1 ; for (; e ; e >>= 1 , a = a * a % P ) if ( e & 1 ) r = r * a % P ; return r ; } }; inline void add ( ll & a , ll b ) { a += b ; if ( a >= Mod :: P ) a -= Mod :: P ; } // ---------- DSU with size, no path compression when rollback is needed (@structures/dsu) ---------- struct DSU { vector < int > p , sz ; DSU ( int n = 0 ) { reset ( n ); } void reset ( int n ) { p . resize ( n ); sz . assign ( n , 1 ); iota ( p . begin (), p . end (), 0 ); } int find ( int v ) { return p [ v ] == v ? v : p [ v ] = find ( p [ v ]); } bool unite ( int a , int b ) { a = find ( a ); b = find ( b ); if ( a == b ) return false ; if ( sz [ a ] < sz [ b ]) swap ( a , b ); p [ b ] = a ; sz [ a ] += sz [ b ]; return true ; } int size ( int v ) { return sz [ find ( v )]; } }; // ---------- Fenwick over int or ll (1-indexed) (@structures/fenwick) ---------- struct BIT { vector < ll > t ; int n ; BIT ( int n = 0 ) : t ( n + 1 , 0 ), n ( n ) {} void add ( int i , ll v ) { for (; i <= n ; i += i & - i ) t [ i ] += v ; } ll sum ( int i ) { ll r = 0 ; for (; i > 0 ; i -= i & - i ) r += t [ i ]; return r ; } ll range ( int l , int r ) { return sum ( r ) - sum ( l - 1 ); } int kth ( ll k ) { int i = 0 ; for ( int b = 31 - __builtin_clz ( n ); b >= 0 ; b --) if ( i + ( 1 << b ) <= n && t [ i + ( 1 << b )] < k ) { i += 1 << b ; k -= t [ i ]; } return i + 1 ; } }; // ---------- Segment tree, iterative, any associative op (@structures/segment-tree) ---------- template < class T , T (* F )( T , T )> struct Seg { int n ; vector < T > t ; Seg ( const vector < T > & a ) : n ( a . size ()), t ( 2 * n ) { copy ( a . begin (), a . end (), t . begin () + n ); for ( int i = n - 1 ; i > 0 ; i --) t [ i ] = F ( t [ 2 * i ], t [ 2 * i + 1 ]); } void setp ( int i , T v ) { for ( t [ i += n ] = v ; i > 1 ; i >>= 1 ) t [ i >> 1 ] = F ( t [ i ], t [ i ^ 1 ]); } T query ( int l , int r ) { T L {}, R {}; for ( l += n , r += n ; l < r ; l >>= 1 , r >>= 1 ) { if ( l & 1 ) L = F ( L , t [ l ++]); if ( r & 1 ) R = F ( t [-- r ], R ); } return F ( L , R ); } }; // ---------- binary lifting + LCA (@lca/binary-lifting) ---------- // ---------- Dinic (@flow/maxflow) ---------- // ---------- Kuhn / Hopcroft-Karp (@matching/bipartite) ---------- // ... deliberately not repeated here: copy the version from the page you are using. int main () { ios :: sync_with_stdio ( false ); cin . tie ( nullptr ); int tc = 1 ; // cin >> tc; // uncomment for multi-test cases while ( tc --) { } return 0 ; } # Settings that are not style preferences Settings that are not style preferences #include <bits/stdc++.h> + using namespace std : fine on Codeforces/CSES/AtCoder (GCC); for a judge with a strict compiler, list the ~10 real headers instead, ios::sync_with_stdio(false); cin.tie(nullptr); — without it, cin on 10⁶ integers can TLE by itself, INF = 0x3f3f3f3f (≈ 1.06·10⁹): the only 32-bit-ish \"infinity\" where INF + INF doesn't overflow and memset(a, 0x3f, sizeof a) fills it, const ll LINF = 4e18 : use for distances/flows that can sum to 10¹⁸; LLONG_MAX breaks d + w comparisons silently, recursion depth: if your DFS may reach 10⁶ frames, either write it iteratively or start the proc", "w": 1039, "h": [["The template", "the-template"], ["Settings that are not style preferences", "settings-that-are-not-style-preferences"], ["Three anti-patterns", "three-anti-patterns"], ["Debug block", "debug-block"], ["Stress testing", "stress-testing"]]}, {"u": "/appendix/notation/", "t": "Notation and Symbols", "s": "Every symbol this book uses, the standard complexity table, and the terminology that differs between communities.", "c": "Appendix", "k": "reference notation core", "b": "Graphs symbol meaning first used G=(V,E) , n=|V| , m=|E| the graph and its sizes A Zoo of Graphs N(v) , N[v] open / closed neighbourhood of v A Zoo of Graphs deg(v) , δ(G) , Δ(G) degree, minimum and maximum degree A Zoo of Graphs bar G complement Subgraphs, Minors and Operations G[S] , G - S induced subgraph, deletion of a vertex set Subgraphs, Minors and Operations G/e , G - e edge contraction, edge deletion Contraction, Induction and Lifting H ≼_m G H is a minor of G (deletions + contractions) Planarity and Euler's Formula ω(G) , α(G) , χ(G) , χ'(G) clique, independent set, chromatic, chromatic index Graph Colouring κ(G) , λ(G) vertex- and edge-connectivity Connectivity, Bridges, Articulation Points G^* planar dual Planarity and Euler's Formula ecc(v) , rad(G) , diam(G) eccentricity, radius, diameter Tree Diameter in Two Passes cen(G) centre (one vertex or an edge) Tree Diameter in Two Passes dist(u,v) shortest-path distance (number of edges, or ∑ weights) Distance, Radius, Eccentricity A^k_uv number of walks of length k from u to v Counting Walks with Matrix Powers L = D - A , τ(G) Laplacian, number of spanning trees The Matrix–Tree Theorem tin[v] , tout[v] DFS entry/exit times Entry/Exit Times and the Euler Tour depth[v] , par[v] , sub[v] rooted-tree depth, parent, subtree size Depth-First Search lca(u,v) lowest common ancestor The LCA Problem M , ν(G) , μ(G) a matching, its maximum size (two conventions) Matching: Definitions and Duality τ_v(G) minimum vertex cover size (some books write β ) Kőnig's Theorem and Minimum Covers R(s,t) , R_k(3) Ramsey numbers Ramsey Theory on Graphs Δ , ∇ max degree; also the Laplacian operator in Random Walks and Cover Times — Complexity and asymptotics notation meaning O(f) , Ω(f) , Θ(f) upper, lower, both — and in this book O(·) is worst-case unless \"expected\" is written tilde O(f) O(f log^c f) for some constant c [n] the set {1,…,n} (or {0,…,n-1} in code) C(n, k) binomial coefficient; logC(n, k) ≤ klog(en/k) , used in Ramsey Theory on Graphs x mod p , x^-1 residue in [0,p) ; multiplicative inverse mod prime p α ≤ β + ε n the \" +o(n) \"-style sloppiness in approximation statements P, NP, NP-hard, NP-complete P, NP, and Reductions — What You Actually Need FPT, XP fixed-parameter tractable: f(k) n^O(1) versus n^f(k) — Escape Routes: What To Do When It Is NP-Hard 3-SAT, CLIQUE, 3-DIM MATCHING the standard sources of reductions ( The NP-complete Graph Problems Worth Knowing ) The complexity table you should be able to recite bound at n = 10^5 verdict O(n) , O(nlog n) fine — up to 10^7 – 10^8 operations O(n√n) ≈ 3·10^7 fine O(nlog^2 n) , O(nlog n) with big constants fine, watch the memory O(n^2) = 10^10 too slow; O(n^2) is only OK up to n ≈ 5000 O(n^2/64) bitset 1.5·10^8 word-ops — passes; the kind of trick Independent Sets, Cliques, and Treewidth is about O(2^n n) n ≤ 22 ( Escape Routes: What To Do When It Is NP-Hard 's Held–Karp bound) O(3^n/3) , O(1.2^n) n ≤ 40 – 60 — measure and conquer territory O(n!) n ≤ 10 Note Where this book's terminology differs from other sources \"walk\" vs \"path\" vs \"trail\" : a walk repeats freely, a trail repeats no edge, a path repeats no vertex. Upstream and several Russian texts use \"path\" for walks; Walks, Trails, Paths, Cycles fixes the convention used here, connected : this book says \"connected component\" for maximal connected subgraphs and never calls a disconnected graph \"connected with k parts\", DFS order : tin / tout here mean entry/exit timer values (not \"the i -th visited vertex\"); interval containment is the whole point ( Entry/Exit Times and the Euler Tour ), tree DP states are written (v, 0/1) for \"not taken/taken\" — the same skeleton as Independent Sets, Cliques, and Treewidth , matching duality : ν for matching size and τ for cover size, following Vizing/Kőnig usage; some books use α' and β , 0-indexed vs 1-indexed : the code uses 0-based arrays, the math uses [n] . When a proof says \"the parent of the root is itself\", that is the code convention ( ", "w": 788, "h": []}, {"u": "/appendix/how-to-read/", "t": "How to Read This Book", "s": "The two-pass method, what the badges mean, how to use the demos, and what to do when a proof does not land.", "c": "Appendix", "k": "meta study easy", "b": "# The skeleton of every page Every page has the same skeleton Front matter — difficulty, complexity badges, prerequisites, and the problems this page makes solvable, a framing paragraph : what question the page answers and what breaks without it, definitions → theorem → proof : the proof is written at the level of \"the step that is not obvious\", so fill in the algebra yourself — that is where the learning is, a comparison table when several methods exist (this is the part people skip and then regret in a contest), code — complete enough to type, not complete enough to paste blindly: variable names carry the invariants, warnings/traps — the bugs that come from knowing 80% of the algorithm, an interactive demo where one helps, and problems with the reason each was chosen. you should know this to call yourself competent at graphs warm-up, usually one idea the idea is simple, the case analysis is not proof-heavy; the code is the easy part # Two passes, not one Note Two passes, not one Pass 1 (a day): read the chapter leads and the tables only — every page's first screen. You are building an index, not understanding. When a table says \"X vs Y\", stop and guess which one the last problem you failed needed. Pass 2 (a week, interleaved with problems): read one chapter per sitting, implement its main algorithm from memory into a blank file, then compare against the page's snippet and note the difference. The difference is what you will forget in a contest; write it as a comment at the top of your template. Reading without implementing has a documented half-life of about three days. # Using the demos How to use the demos Each demo is a stepper, not an animation. Three habits make them worth the minute they cost: predict the next frame before pressing Next (if you cannot, you do not yet know the invariant, which is the thing being tested), drive it to a worst case : a path, a star, a graph where the algorithm does no work at all — every demo has controls for this, and the failure mode you find there is the one in the problemset, then reimplement the demo's data structures , not the algorithm: lvl / ptr arrays, the used -with-timestamp trick, the \"emit then reverse\" order — those are the parts that get subtly mangled under time pressure. # When a proof does not land When a proof does not land, do this State it for n = 1, 2, 3 by hand. If the base case is where the proof starts, most \"understood\" claims were never checked, find the invariant sentence (usually one paragraph, sometimes only in the code comment) and ask which step would fail without it — that step is what the proof is actually about, break the hypothesis and see where the proof dies: e.g. remove \"non-negative weights\" from Dijkstra and construct the counterexample ( Dijkstra's Algorithm ), which is the fastest way to know what a condition is for , implement the algorithm on a random instance against a brute force (the stress-test loop in Contest Template ), and read the mismatching case — a 4-vertex counterexample explains a proof gap better than another hour of reading. # A 10-day plan Example A 10-day plan, if you want one day pages you can then solve 1 A Zoo of Graphs , Four Ways to Store a Graph , Walks, Trails, Paths, Cycles CSES 1666, 1192 2 Depth-First Search , Breadth-First Search , Connectivity, Bridges, Articulation Points CSES 1193, 1669 3 Tree Diameter in Two Passes , Distance, Radius, Eccentricity , Entry/Exit Times and the Euler Tour CSES 1131, 1132, 1674 4 DAGs and Topological Order , Strongly Connected Components , Cycles: Detection, Extraction, Feedback Sets CSES 1682, 1683, 1680 5 Hierholzer's Linear Algorithm , Euler Tours: When They Exist CSES 1691, 1693, 1692 6 Dijkstra's Algorithm , Bellman–Ford and Negative Weights , Floyd–Warshall CSES 1671, 1195, 1197 7 Disjoint Set Union (Union–Find) , Fenwick Tree (Binary Indexed Tree) , Segment Tree CSES 2138, 1143, 1734 8 LCA by Binary Lifting , Centroid Decomposition , Heavy-Light Decomposition CSES 1687, 1688, 11", "w": 924, "h": [["The skeleton of every page", "the-skeleton-of-every-page"], ["Two passes, not one", "two-passes-not-one"], ["Using the demos", "using-the-demos"], ["When a proof does not land", "when-a-proof-does-not-land"], ["A 10-day plan", "a-10-day-plan"], ["What this book is not", "what-this-book-is-not"]]}]}