⚙️AlgorithmIntermediate

König's Theorem

Key Points

  • König's Theorem states that in any bipartite graph, the size of a maximum matching equals the size of a minimum vertex cover.
  • After computing a maximum matching, you can build a minimum vertex cover in O(|E|) time using alternating paths from unmatched left vertices.
  • Use Hopcroft–Karp to find a maximum matching in O( ), which is fast enough for most competitive programming tasks.
  • The construction rule is: start BFS from all unmatched left vertices along alternating edges; the min cover is (left unreachable) ∪ (right reachable).
  • This theorem is a special case of strong LP duality for bipartite graphs, where the LP relaxations of matching and vertex cover are integral.
  • The complement of a minimum vertex cover is a maximum independent set in bipartite graphs.
  • Do not run the cover-construction on a non-maximum matching; it will not produce a minimum cover.
  • Index your sides clearly (L and R) and pay attention to edge directions when doing alternating BFS.

Prerequisites

  • Graph terminology (vertices, edges, paths)You must understand the basic objects to interpret matchings and covers.
  • Bipartite graphs and bipartitionsKönig's Theorem holds specifically for bipartite graphs; the algorithm relies on the two-side structure.
  • BFS and DFSBoth Hopcroft–Karp and the cover-construction use BFS/DFS traversals.
  • Augmenting pathsMaximum matching algorithms hinge on finding and flipping augmenting paths.
  • Big-O notationTo analyze time and space complexity of matching and cover extraction.
  • Linear programming (optional)Gives a dual perspective explaining why equality holds via integrality and duality.

Detailed Explanation

Tap terms for definitions

01Overview

Hook: Imagine pairing students (left side) to projects (right side). You want to assign as many students as possible so that no project takes more than one student. At the same time, your teacher wants the smallest set of people and projects that collectively "touch" every possible student–project proposal, so that every proposal is reviewed by at least one member of that set. Surprisingly, these two quantities always match in bipartite settings. Concept: König's Theorem connects two problems in bipartite graphs: maximum matching (most disjoint pairs) and minimum vertex cover (fewest vertices touching all edges). It guarantees that their optimal values are equal. Example: Suppose we have three students and three projects with edges S1–P1, S2–P1, S2–P2, S3–P3. The maximum matching pairs S1–P1, S2–P2, S3–P3 (size 3). The minimum vertex cover must have at least three vertices, and indeed {P1, P2, P3} touches every edge. The theorem tells us both optima equal 3.

02Intuition & Analogies

Think of the left set as workers and the right set as jobs. A matching assigns each worker to at most one job and vice versa, maximizing how many pairs we form. A vertex cover is a safety net: a set of workers and jobs such that every potential worker–job compatibility (edge) is supervised by at least one member of the net. Intuitively, the more pairs you can form independently, the fewer vertices you need to block all edges, and vice versa. Alternating paths are the gears that connect these two views. Starting from an unmatched worker, you walk along edges that alternate between not-in-matching and in-matching. If you find an augmenting path (ending at an unmatched job), you can improve the matching. When you can no longer improve, the frontier of these alternating explorations encodes which vertices are "critically engaged" with the matching. Precisely, the left vertices you cannot reach and the right vertices you can reach form a minimum set that covers all edges. This happens because every uncovered edge would enable one more augmentation, contradicting maximality. So the structure of failed attempts to augment actually reveals the optimal cover.

03Formal Definition

Let G = (L R, E) be a bipartite graph with bipartition L and R. A matching M E is a set of edges with no shared vertices. A vertex cover C L R is a set of vertices such that every edge e E has at least one endpoint in C. Define the matching number (G) as the maximum cardinality of a matching, and the vertex cover number (G) as the minimum cardinality of a vertex cover. König's Theorem states: for any bipartite graph G, (G) = (G). Furthermore, given a maximum matching M, let Z be the set of vertices reachable from all unmatched vertices in L via M-alternating paths that start with an unmatched edge. Then the set = (L Z) (R Z) is a minimum vertex cover with = . An M-alternating path is a path whose edges alternate between E M and M; an augmenting path starts and ends at unmatched vertices and allows increasing the matching size by one when its edges are flipped.

04When to Use

Use König's Theorem whenever you have a bipartite pairing problem and you also need a small set of critical entities that "touch" all relationships. Typical cases include: task assignment (max match) plus picking minimal monitors (min cover), scheduling conflicts (pairing slots to tasks) with minimal oversight set, or network design where you want either as many disjoint connections as possible or a minimal set of nodes intercepting all possible links. In competitive programming, combine it with Hopcroft–Karp to compute a maximum matching, then extract a minimum vertex cover in linear time. You can also obtain a maximum independent set in bipartite graphs as the complement of a minimum vertex cover, which solves problems asking for the largest subset of vertices with no internal edges. When dealing with constraints that are not bipartite (odd cycles), König's Theorem does not apply; consider other techniques like general matching (Edmonds' blossom algorithm) or vertex cover approximations. If weights are involved, look up the Kőnig–Egerváry generalization or the Hungarian algorithm for assignment problems.

⚠️Common Mistakes

• Using a maximal (cannot add more) instead of a maximum (truly optimal) matching when constructing the cover. The alternating-reachability recipe is correct only for a maximum matching; otherwise you may get a non-minimum cover. Fix: always run a correct maximum matching algorithm first. • Mixing up which side contributes reachable vs unreachable vertices. The correct minimum cover is (L \ Z) union (R ∩ Z) where Z is the alternating-reachable set from free L-vertices. Swapping sides produces a generally different set. • Traversing edges in the wrong direction during the alternating BFS/DFS. From L, follow only non-matching edges; from R, follow only matching edges. Reversing this breaks the invariant and may falsely mark vertices. • Not initializing BFS with all unmatched L vertices, which can miss components and produce an incorrect cover. • Forgetting that vertex indices are separate for L and R. Many implementations flatten indices; off-by-one or side confusion leads to wrong matches or segmentation faults. • Expecting König's Theorem to hold in non-bipartite graphs. It does not; the equality fails because LP relaxations are not integral there. • Assuming uniqueness. Maximum matchings and minimum covers need not be unique; your code should not rely on uniqueness.

Key Formulas

König's Theorem (cardinality form)

Explanation: For any bipartite graph G, the maximum size of a matching equals the minimum size of a vertex cover. This is the central equality you exploit algorithmically.

Minimum cover from alternating reachability

Explanation: Given a maximum matching and Z as the set of vertices reachable from free L-vertices via alternating paths, this expression produces a minimum vertex cover. Compute Z by alternating BFS/DFS, then assemble accordingly.

Size equality of constructed cover

Explanation: Every matched edge contributes exactly one endpoint to the set (its L-end if R-end is reachable, otherwise its R-end), and no edge can be uncovered. This shows the constructed cover has size equal to the matching.

Hopcroft–Karp time

Explanation: The time to compute a maximum matching using Hopcroft–Karp is proportional to the number of edges times the square root of the number of vertices. It is efficient for sparse and dense bipartite graphs alike.

Cover construction time

Explanation: Building Z with an alternating BFS/DFS touches each edge at most a constant number of times, so the minimum vertex cover can be extracted in linear time after the matching.

Integer program for matching

Explanation: This maximizes the number of chosen edges so that no vertex is used more than once. In bipartite graphs, its LP relaxation is integral.

Integer program for vertex cover

Explanation: This minimizes the number of selected vertices so that every edge has at least one endpoint selected. It is the LP dual of the matching LP in the bipartite case.

Independent set–vertex cover complement

Explanation: In any graph, a maximum independent set size equals total vertices minus the size of a minimum vertex cover. With König's Theorem, in bipartite graphs this also equals - (G).

Complexity Analysis

Let n = = + and m = . Using Hopcroft–Karp, the maximum matching in a bipartite graph can be found in O(m ) time. The algorithm repeats phases composed of a BFS that builds distance layers from all free left vertices to the set of free right vertices via alternating edges, followed by multiple DFS searches that find a maximal set of vertex-disjoint shortest augmenting paths. Each BFS runs in O(m), each DFS phase runs in O(m), and there are O() phases because the shortest augmenting path length increases at least every other phase. Space usage is O(n + m) to store the graph, match arrays, and BFS/DFS metadata. After obtaining a maximum matching M, constructing the minimum vertex cover via alternating reachability takes O(n + m): perform a single alternating BFS/DFS starting from all unmatched left vertices, following only non-matching edges from L to R and only matching edges from R to L. Mark the reachable set Z; then output (L \ Z) ∪ (R ∩ Z). This pass touches each edge a constant number of times and uses O(n) extra space for visited flags. If, instead, you use the simpler DFS-based Kuhn algorithm for matching, the worst-case time is O(n m), which may TLE on dense graphs with n ≈ 2e5 and m ≈ 5e5, but is acceptable for smaller constraints. Regardless of the matching algorithm, the cover extraction remains linear. The end-to-end pipeline thus runs in O(m ) time and O(n + m) space with Hopcroft–Karp, which fits the 1900–2300 CF range.

Code Examples

Hopcroft–Karp + Construct Minimum Vertex Cover (Standard Pipeline)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Bipartite graph: left side [1..nL], right side [1..nR]
5// Edges are from L to R.
6struct HopcroftKarp {
7 int nL, nR;
8 vector<vector<int>> adj; // adj[u] = list of v in R
9 vector<int> dist; // BFS distances for L side
10 vector<int> matchL, matchR; // matchL[u] in R or 0 if free; matchR[v] in L or 0
11
12 HopcroftKarp(int nL, int nR) : nL(nL), nR(nR) {
13 adj.assign(nL + 1, {});
14 matchL.assign(nL + 1, 0);
15 matchR.assign(nR + 1, 0);
16 dist.resize(nL + 1);
17 }
18
19 void add_edge(int u, int v) {
20 // 1 <= u <= nL, 1 <= v <= nR
21 adj[u].push_back(v);
22 }
23
24 bool bfs() {
25 queue<int> q;
26 // Initialize layer distances; 0 denotes NIL's level will be set via dist[0]
27 for (int u = 1; u <= nL; ++u) {
28 if (matchL[u] == 0) {
29 dist[u] = 0;
30 q.push(u);
31 } else {
32 dist[u] = -1;
33 }
34 }
35 bool reachableFreeRight = false; // whether we can reach a free vertex on R via alternating path
36 while (!q.empty()) {
37 int u = q.front(); q.pop();
38 for (int v : adj[u]) {
39 int uu = matchR[v]; // 0 if v is free; otherwise go to its matched left neighbor
40 if (uu == 0) {
41 // Found a shortest path to a free R-vertex
42 reachableFreeRight = true;
43 } else if (dist[uu] == -1) {
44 dist[uu] = dist[u] + 1;
45 q.push(uu);
46 }
47 }
48 }
49 return reachableFreeRight;
50 }
51
52 bool dfs(int u, vector<int>& it) {
53 for (int &i = it[u]; i < (int)adj[u].size(); ++i) {
54 int v = adj[u][i];
55 int uu = matchR[v];
56 // If v is free, try to match; otherwise, try to advance along layer graph
57 if (uu == 0 || (dist[uu] == dist[u] + 1 && dfs(uu, it))) {
58 matchL[u] = v;
59 matchR[v] = u;
60 return true;
61 }
62 }
63 dist[u] = -1; // dead end for this layer
64 return false;
65 }
66
67 int max_matching() {
68 int matching = 0;
69 while (bfs()) {
70 vector<int> it(nL + 1, 0);
71 for (int u = 1; u <= nL; ++u) {
72 if (matchL[u] == 0) {
73 if (dfs(u, it)) matching++;
74 }
75 }
76 }
77 return matching;
78 }
79
80 // After calling max_matching(), build a minimum vertex cover using alternating BFS
81 // Rule: Start from all free L vertices, traverse non-matching edges (L->R) and matching edges (R->L)
82 // Then cover = (L \ Z) U (R ∩ Z)
83 pair<vector<int>, vector<int>> min_vertex_cover() {
84 vector<char> visL(nL + 1, false), visR(nR + 1, false);
85 queue<pair<int,bool>> q; // (id, isLeft)
86 for (int u = 1; u <= nL; ++u) if (matchL[u] == 0) {
87 visL[u] = true;
88 q.push({u, true});
89 }
90 while (!q.empty()) {
91 auto [id, isLeft] = q.front(); q.pop();
92 if (isLeft) {
93 int u = id;
94 for (int v : adj[u]) {
95 if (matchL[u] != v && !visR[v]) { // non-matching edge
96 visR[v] = true;
97 q.push({v, false});
98 }
99 }
100 } else {
101 int v = id;
102 int u = matchR[v];
103 if (u != 0 && !visL[u]) { // follow matching edge back to L
104 visL[u] = true;
105 q.push({u, true});
106 }
107 }
108 }
109 vector<int> coverL, coverR;
110 for (int u = 1; u <= nL; ++u) if (!visL[u]) coverL.push_back(u); // L \ Z
111 for (int v = 1; v <= nR; ++v) if (visR[v]) coverR.push_back(v); // R ∩ Z
112 return {coverL, coverR};
113 }
114};
115
116int main() {
117 ios::sync_with_stdio(false);
118 cin.tie(nullptr);
119
120 int nL, nR, m; // read bipartite sizes and edges
121 // Example input format:
122 // nL nR m
123 // m lines: u v (1-based u in [1..nL], v in [1..nR])
124 if (!(cin >> nL >> nR >> m)) {
125 // Demo graph if no input
126 nL = 3; nR = 3; m = 4;
127 HopcroftKarp hk(nL, nR);
128 hk.add_edge(1,1);
129 hk.add_edge(2,1);
130 hk.add_edge(2,2);
131 hk.add_edge(3,3);
132 int mm = hk.max_matching();
133 auto [cL, cR] = hk.min_vertex_cover();
134 cout << "max_matching = " << mm << "\n";
135 cout << "min_vertex_cover_size = " << (cL.size() + cR.size()) << "\n";
136 cout << "L part:"; for (int u: cL) cout << ' ' << u; cout << "\n";
137 cout << "R part:"; for (int v: cR) cout << ' ' << v; cout << "\n";
138 return 0;
139 }
140
141 HopcroftKarp hk(nL, nR);
142 for (int i = 0; i < m; ++i) {
143 int u, v; cin >> u >> v;
144 hk.add_edge(u, v);
145 }
146 int mm = hk.max_matching();
147 auto [cL, cR] = hk.min_vertex_cover();
148
149 cout << mm << "\n"; // size of maximum matching == size of min vertex cover
150 cout << (cL.size() + cR.size()) << "\n";
151 // Optionally print the cover itself
152 // Left side
153 cout << cL.size(); for (int u : cL) cout << ' ' << u; cout << "\n";
154 // Right side
155 cout << cR.size(); for (int v : cR) cout << ' ' << v; cout << "\n";
156 return 0;
157}
158

This program computes a maximum matching using Hopcroft–Karp and then constructs a minimum vertex cover using the alternating-reachability rule: start from all unmatched left vertices, traverse only non-matching edges L→R and only matching edges R→L, then output (L \ Z) ∪ (R ∩ Z). It prints the matching size and the cover size (which are equal by König's Theorem) and the cover sets.

Time: O(|E| sqrt(|V|)) for matching + O(|E|) for cover constructionSpace: O(|V| + |E|)
Kuhn's Algorithm (DFS augmenting paths) + Minimum Vertex Cover Builder
1#include <bits/stdc++.h>
2using namespace std;
3
4// Simpler but slower matching to emphasize the cover construction
5struct Kuhn {
6 int nL, nR;
7 vector<vector<int>> g; // adj from L to R
8 vector<int> matchR; // which left is matched to right v (0 if free)
9 vector<int> seen; // DFS visited R nodes timestamp
10 int visToken = 1;
11
12 Kuhn(int nL, int nR): nL(nL), nR(nR) {
13 g.assign(nL + 1, {});
14 matchR.assign(nR + 1, 0);
15 seen.assign(nR + 1, 0);
16 }
17
18 void add_edge(int u, int v) { g[u].push_back(v); }
19
20 bool dfs(int u) {
21 for (int v : g[u]) {
22 if (seen[v] == visToken) continue;
23 seen[v] = visToken;
24 if (matchR[v] == 0 || dfs(matchR[v])) {
25 matchR[v] = u;
26 return true;
27 }
28 }
29 return false;
30 }
31
32 // Returns matchL for completeness
33 vector<int> max_matching() {
34 vector<int> matchL(nL + 1, 0);
35 int flow = 0;
36 bool progress = true;
37 while (progress) {
38 progress = false;
39 ++visToken;
40 for (int u = 1; u <= nL; ++u) if (matchL[u] == 0) {
41 if (dfs(u)) {
42 progress = true;
43 // Recover the edge to which u was matched in matchR
44 // Find v with matchR[v] == u (could optimize by returning it)
45 for (int v : g[u]) if (matchR[v] == u) { matchL[u] = v; break; }
46 ++flow;
47 }
48 }
49 }
50 return matchL;
51 }
52};
53
54// Build min vertex cover from L->R adjacency and a maximum matching matchL/matchR
55pair<vector<int>, vector<int>> build_min_vertex_cover(const vector<vector<int>>& adj, const vector<int>& matchL, const vector<int>& matchR) {
56 int nL = (int)matchL.size() - 1;
57 int nR = (int)matchR.size() - 1;
58 vector<char> visL(nL + 1, false), visR(nR + 1, false);
59 queue<pair<int,bool>> q;
60 for (int u = 1; u <= nL; ++u) if (matchL[u] == 0) { visL[u] = true; q.push({u, true}); }
61 while (!q.empty()) {
62 auto [id, isLeft] = q.front(); q.pop();
63 if (isLeft) {
64 int u = id;
65 for (int v: adj[u]) if (matchL[u] != v && !visR[v]) { visR[v] = true; q.push({v, false}); }
66 } else {
67 int v = id;
68 int u = matchR[v];
69 if (u != 0 && !visL[u]) { visL[u] = true; q.push({u, true}); }
70 }
71 }
72 vector<int> cL, cR;
73 for (int u = 1; u <= nL; ++u) if (!visL[u]) cL.push_back(u);
74 for (int v = 1; v <= nR; ++v) if (visR[v]) cR.push_back(v);
75 return {cL, cR};
76}
77
78int main() {
79 ios::sync_with_stdio(false); cin.tie(nullptr);
80
81 int nL = 4, nR = 4; // small demo
82 Kuhn solver(nL, nR);
83 vector<vector<int>> adj(nL + 1);
84
85 auto add = [&](int u, int v){ solver.add_edge(u,v); adj[u].push_back(v); };
86 add(1,1); add(1,2); add(2,2); add(3,3); add(3,4); add(4,4);
87
88 vector<int> matchL = solver.max_matching();
89 vector<int> matchR(nR + 1, 0);
90 for (int v = 1; v <= nR; ++v) if (solver.matchR[v] != 0) matchR[v] = solver.matchR[v];
91
92 auto [cL, cR] = build_min_vertex_cover(adj, matchL, matchR);
93
94 cout << "matching_size = ";
95 int mm = 0; for (int u = 1; u <= nL; ++u) if (matchL[u]) ++mm; cout << mm << "\n";
96 cout << "min_vertex_cover_size = " << (int)(cL.size() + cR.size()) << "\n";
97 cout << "Cover L:"; for (int u : cL) cout << ' ' << u; cout << "\n";
98 cout << "Cover R:"; for (int v : cR) cout << ' ' << v; cout << "\n";
99}
100

This example shows a straightforward DFS-based maximum matching (Kuhn's algorithm). After computing matchL/matchR, it builds the minimum vertex cover via the same alternating BFS logic. It is easier to implement but has worse asymptotic time than Hopcroft–Karp.

Time: O(|V||E|) for matching + O(|E|) for cover constructionSpace: O(|V| + |E|)
From Minimum Vertex Cover to Maximum Independent Set in a Bipartite Graph
1#include <bits/stdc++.h>
2using namespace std;
3
4// Assume we already computed a min vertex cover (coverL, coverR) on bipartite graph (L,R)
5// Then a maximum independent set is simply (L \ coverL) U (R \ coverR).
6int main(){
7 ios::sync_with_stdio(false);
8 cin.tie(nullptr);
9
10 int nL = 3, nR = 4;
11 vector<int> coverL = {2}; // example: L-cover has vertex 2
12 vector<int> coverR = {1, 4}; // example: R-cover has vertices 1 and 4
13
14 vector<char> inCovL(nL + 1, false), inCovR(nR + 1, false);
15 for (int u : coverL) inCovL[u] = true;
16 for (int v : coverR) inCovR[v] = true;
17
18 vector<int> indepL, indepR; // maximum independent set
19 for (int u = 1; u <= nL; ++u) if (!inCovL[u]) indepL.push_back(u);
20 for (int v = 1; v <= nR; ++v) if (!inCovR[v]) indepR.push_back(v);
21
22 cout << "max_independent_set_size = " << (int)(indepL.size() + indepR.size()) << "\n";
23 cout << "Independent L:"; for (int u : indepL) cout << ' ' << u; cout << "\n";
24 cout << "Independent R:"; for (int v : indepR) cout << ' ' << v; cout << "\n";
25
26 return 0;
27}
28

In bipartite graphs, by König's Theorem and the complement relation, the complement of any minimum vertex cover is a maximum independent set. This program demonstrates how to derive that set once the cover is known.

Time: O(|V|)Space: O(|V|)
Brute-force Minimum Vertex Cover (for tiny graphs) to Validate König's Theorem
1#include <bits/stdc++.h>
2using namespace std;
3
4// Validate on tiny graphs (nL, nR <= 10) by brute forcing vertex covers
5int main(){
6 ios::sync_with_stdio(false);
7 cin.tie(nullptr);
8
9 int nL, nR, m; cin >> nL >> nR >> m;
10 if (nL > 10 || nR > 10) { cerr << "Use tiny graphs only.\n"; return 0; }
11 vector<vector<int>> adj(nL, vector<int>(nR, 0));
12 vector<pair<int,int>> edges;
13 for (int i = 0; i < m; ++i) {
14 int u, v; cin >> u >> v; --u; --v; adj[u][v] = 1; edges.push_back({u,v});
15 }
16 int best = nL + nR + 1;
17 vector<int> bestL, bestR;
18 // Enumerate subsets of L and R (2^(nL+nR))
19 for (int maskL = 0; maskL < (1<<nL); ++maskL) {
20 for (int maskR = 0; maskR < (1<<nR); ++maskR) {
21 bool cover = true;
22 for (auto [u,v] : edges) {
23 if (((maskL>>u)&1) == 0 && ((maskR>>v)&1) == 0) { cover = false; break; }
24 }
25 if (cover) {
26 int sz = __builtin_popcount((unsigned)maskL) + __builtin_popcount((unsigned)maskR);
27 if (sz < best) {
28 best = sz; bestL.clear(); bestR.clear();
29 for (int u = 0; u < nL; ++u) if ((maskL>>u)&1) bestL.push_back(u+1);
30 for (int v = 0; v < nR; ++v) if ((maskR>>v)&1) bestR.push_back(v+1);
31 }
32 }
33 }
34 }
35 cout << "bruteforce_min_vertex_cover_size = " << best << "\n";
36 cout << "L:"; for (int u: bestL) cout << ' ' << u; cout << "\n";
37 cout << "R:"; for (int v: bestR) cout << ' ' << v; cout << "\n";
38 return 0;
39}
40

This brute-force search confirms the minimum vertex cover size for tiny graphs by checking all subsets of L and R. It's useful for testing and validating your matching/cover code on small cases. For larger graphs, it explodes exponentially and should not be used.

Time: O(2^{|L|+|R|} * |E|)Space: O(|E|)