⚙AlgorithmIntermediate

Bipartite Matching - Hopcroft-Karp

Key Points

  • ‱
    Hopcroft–Karp computes maximum matching in a bipartite graph in O(E ) time, which is asymptotically faster than repeated DFS (Kuhn's algorithm).
  • ‱
    It alternates between a BFS that builds distance layers and a DFS that finds a maximal set of vertex-disjoint shortest augmenting paths.
  • ‱
    By augmenting many shortest paths in one phase, it guarantees that the number of phases is O().
  • ‱
    Use it when you need fast unweighted bipartite matching for large graphs, such as task assignment, grid pairing, or DAG path cover.
  • ‱
    The BFS only follows unmatched edges from L to R and matched edges from R back to L to respect alternating paths.
  • ‱
    A correct implementation must keep left/right partitions consistent and reset distance labels every phase.
  • ‱
    From a maximum matching, you can recover a minimum vertex cover in O(V+E) using König’s theorem.
  • ‱
    In competitive programming, Hopcroft–Karp is a standard tool for 2000–2400 CF problems involving pairings or reductions to matching.

Prerequisites

  • →Graphs and adjacency lists — You need to represent bipartite graphs efficiently and traverse neighbors.
  • →Breadth-First Search (BFS) — BFS layering is essential to find shortest augmenting paths.
  • →Depth-First Search (DFS) — DFS finds vertex-disjoint augmenting paths within the layered graph.
  • →Matchings and augmenting paths — Understanding how augmentations increase matching size is fundamental.
  • →Big-O notation — To analyze and compare algorithmic complexity such as O(E \sqrt{V}).
  • →König’s theorem (bipartite) — Lets you derive minimum vertex cover and related structures from the matching.
  • →Hall’s marriage theorem — Characterizes when a perfect matching exists and informs feasibility checks.

Detailed Explanation

Tap terms for definitions

01Overview

Hopcroft–Karp is a classic algorithm for finding a maximum cardinality matching in a bipartite graph G = (L, R, E), where each edge links a vertex from the left set L to the right set R. Unlike the simpler Kuhn’s algorithm (repeated DFS), which augments one path at a time and can be slow on large or adversarial graphs, Hopcroft–Karp speeds things up by carefully organizing the search for augmenting paths. It proceeds in phases. Each phase begins with a BFS that builds a layered (level) graph of alternating paths from all currently unmatched vertices in L to the nearest unmatched vertices in R. Then a DFS pass finds a maximal set of vertex-disjoint augmenting paths that are shortest with respect to these layers, and augments all of them at once by flipping matched/unmatched status along those paths. This strategy ensures that after a small number of phases, no more augmenting paths exist, meaning the matching is maximum. The power of the algorithm comes from two observations: shortest augmenting paths are easy to find using BFS layering, and augmenting along many vertex-disjoint shortest paths at once significantly reduces the number of future phases. The resulting complexity is O(E \sqrt{V}), which is optimal up to polylog factors for this problem in the unit-weight setting. The algorithm is simple to implement with adjacency lists and works efficiently on graphs with hundreds of thousands of edges in typical competitive programming settings.

02Intuition & Analogies

Imagine you’re organizing a school dance with two groups: L are students who signed up to lead, and R are students who prefer to follow. Some pairs can dance together (an edge). A matching is a set of pairs so that each student dances with at most one partner. If you discover that a certain unmatched leader could become matched by rearranging who pairs with whom (i.e., swapping partners along a line of compatible pairs), you’ve found an augmenting path: it begins at an unmatched leader in L and ends at an unmatched follower in R, switching matched/unmatched status along the path to add one more pair overall. Kuhn’s algorithm looks for such a path one leader at a time, which can be like trying to rearrange the dance by handling students individually—often leading to many small, incremental changes. Hopcroft–Karp, in contrast, first sends out a wave (BFS) to find the minimal number of swaps needed to match any currently unmatched leader. Then, within those tight constraints, it simultaneously locks in as many disjoint rearrangements (DFS) as possible. Think of it as organizing multiple quick partner swaps at once, all with the least number of moves, before moving on to any longer or more complex rearrangements. Because you always prefer the shortest fixes and do many at a time, the process converges quickly. Moreover, by insisting on shortest augmenting paths per phase, you prevent easy opportunities from lingering and cluttering later efforts, keeping the total number of such mass rearrangement phases small—on the order of the square root of the number of students. This “BFS to find closest opportunities, DFS to execute many at once” rhythm is the heart of Hopcroft–Karp.

03Formal Definition

Given a bipartite graph G = (L, R, E), a matching M E is a set of pairwise vertex-disjoint edges. An augmenting path with respect to M is a simple path that starts at an unmatched vertex in L, ends at an unmatched vertex in R, and alternates between edges not in M and edges in M. Augmenting along such a path P replaces M with M P (symmetric difference), increasing by 1. Hopcroft–Karp maintains a current matching M and repeats phases until no augmenting path exists. In each phase, it performs: (1) BFS layering on the directed alternating graph that orients unmatched edges from L to R and matched edges from R to L, starting from the set of free vertices in L. This BFS computes the distance d(u) for u in L, and identifies the minimum distance D to any free vertex in R. (2) DFS searches for a maximal set of vertex-disjoint augmenting paths that are shortest, i.e., have length D in this metric, and augments along all of them simultaneously. The BFS ensures that DFS only explores edges consistent with increasing distance labels (from L to R along unmatched edges) and uses matched edges to return from R to L, preserving alternation and shortest-path structure. The algorithm terminates when BFS cannot reach any free vertex in R, implying no augmenting paths remain; by Berge’s theorem, M is then maximum. The total running time is O(E ).

04When to Use

Use Hopcroft–Karp whenever you need maximum matching in a large unweighted bipartite graph and performance matters. Typical examples include: (1) Assignment without weights: pairing workers to tasks when you only care about feasibility and maximizing count (not cost). (2) Scheduling with conflicts: matching classes to rooms or time slots subject to compatibility. (3) Grid or chessboard problems: pairing white and black cells under adjacency constraints (domino tiling checks). (4) Network pairing: connecting clients to servers under capacity 1. (5) Reductions: compute a minimum vertex cover or maximum independent set in bipartite graphs via König’s theorem once you have a maximum matching. (6) DAG minimum path cover: reduce to bipartite matching by splitting each vertex into left/right copies and adding edges for original DAG edges; the minimum path cover size equals n minus the maximum matching. Avoid Hopcroft–Karp if edges have weights and you need minimum-cost maximum matching—in that case use the Hungarian algorithm (assignment) or min-cost max-flow. Also, if the graph is not bipartite, you must either transform it to a bipartite setting (e.g., via problem structure) or use general matching algorithms such as Edmonds’ blossom for non-bipartite graphs.

⚠Common Mistakes

Common pitfalls include: (1) Mixing partitions: adding edges within L or within R will break correctness—ensure every edge goes from L to R. (2) Wrong BFS directions: in the alternating graph, only traverse unmatched edges L→R and matched edges R→L; reversing these silently blocks augmenting paths. (3) Not resetting distances per phase: you must recompute level labels from scratch in each BFS, otherwise DFS may follow stale layers and miss or invent augmenting paths. (4) Using 1-indexed inputs but storing 0-indexed without subtracting 1 leads to out-of-bounds or missed edges—be consistent. (5) Forgetting to stop DFS at free right vertices at the first layer depth D; exploring beyond D violates the “shortest augmenting path” guarantee and hurts complexity. (6) Recursion depth: a deeply recursive DFS can overflow the stack on huge instances; consider iterative DFS or increasing stack size in environments that allow it. (7) Reusing adjacency without clearing between test cases in CP templates causes spurious edges. (8) Assuming Hopcroft–Karp solves weighted assignment—it does not; use Hungarian or min-cost flow for costs. (9) Post-processing sets for König’s theorem: the minimum vertex cover is (L \ visited) âˆȘ (R ∩ visited) after alternating BFS from free L; mixing up the set complement is a frequent error. Carefully implement the visited/side logic.

Key Formulas

Augmentation Effect

Explanation: If P is an augmenting path with respect to matching M, flipping matched status along P increases the matching size by 1. This is the fundamental operation used to grow the matching.

Hopcroft–Karp Time Complexity

Explanation: The total running time on a graph with n = and m = is O(E ). It comes from O() phases, each doing O(E) BFS/DFS work.

Phase Bound

Explanation: The algorithm performs at most O() BFS–DFS phases. Intuitively, many short augmenting paths are exhausted quickly, and then the shortest augmenting path length strictly increases.

Augmentations Count

Explanation: Each augmenting path increases matching size by exactly 1, so the matching size after k augmentations is + k. This helps reason about convergence.

Hall's Condition

Explanation: A bipartite graph has a matching that saturates L if and only if every subset S of L has at least |S| neighbors in R. While Hopcroft–Karp does not use it directly, it characterizes feasibility.

König's Theorem

Explanation: In bipartite graphs, the maximum matching size equals the minimum vertex cover size. This enables computing MVC from a maximum matching in linear time.

DAG Path Cover Reduction

Explanation: In a DAG with n vertices, the minimum number of vertex-disjoint paths that cover all vertices equals n minus the maximum matching size in a constructed bipartite graph.

Degree Sum

Explanation: Total edge endpoints count twice the number of edges, implying adjacency-list scans across the graph in BFS/DFS are O(E).

Layer Consistency

Explanation: DFS must only traverse edges that advance from a lower layer to the next (for unmatched edges ) or follow matched edges back from , ensuring shortest augmenting paths.

Space Complexity

Explanation: Storing adjacency lists, match arrays for L and R, and distance/visited labels requires memory linear in vertices and edges.

Complexity Analysis

Hopcroft–Karp alternates between BFS and DFS phases. In each phase, the BFS builds levels in the alternating graph starting from all free vertices in L. Building this layering requires scanning each adjacency list at most once, leading to O(E) time. The following DFS step attempts to find a maximal set of vertex-disjoint augmenting paths that are shortest with respect to these layers. By restricting DFS to edges that respect the layering (unmatched edges that go from level d to d+1, and matched edges that go from level d+1 back to d), each edge is considered only a constant number of times during the DFS of a single phase. Thus the DFS work per phase is also O(E). The key to the overall O(E ) bound is the number of phases. There are two regimes: while shortest augmenting paths have length at most 2−1, each phase increases the matching size by at least () because the shortest paths are vertex-disjoint; hence there can be at most O() such phases. After that point, the shortest augmenting path length increases by at least 1 every phase, and since no path can exceed length O(V), the number of remaining phases is also O(). Summing both regimes yields O() phases total. Therefore total time is O(E) per phase times O() phases, i.e., O(E ). Space usage is dominated by adjacency lists, match arrays for both partitions, and auxiliary arrays for distances and visitation flags, which together require O(V+E) memory. In practice, the algorithm is fast and typically outperforms naive DFS-based augmenting path methods by large margins on medium to large graphs.

Code Examples

Canonical Hopcroft–Karp implementation (maximum matching)
1#include <bits/stdc++.h>
2using namespace std;
3
4struct HopcroftKarp {
5 // Left side size nL, Right side size nR
6 int nL, nR;
7 vector<vector<int>> adj; // adj[u] = list of v in R that u in L connects to
8 vector<int> dist; // distance (level) for BFS on L side
9 vector<int> matchL; // matchL[u] = matched v in R or -1
10 vector<int> matchR; // matchR[v] = matched u in L or -1
11
12 HopcroftKarp(int nL, int nR): nL(nL), nR(nR) {
13 adj.assign(nL, {});
14 matchL.assign(nL, -1);
15 matchR.assign(nR, -1);
16 dist.resize(nL);
17 }
18
19 void add_edge(int u, int v) {
20 // 0-based: u in [0,nL), v in [0,nR)
21 adj[u].push_back(v);
22 }
23
24 bool bfs() {
25 queue<int> q;
26 // Initialize distances for free L-vertices
27 for (int u = 0; u < nL; ++u) {
28 if (matchL[u] == -1) { dist[u] = 0; q.push(u); }
29 else dist[u] = -1; // -1 means unvisited in this BFS
30 }
31 bool reachableFreeR = false;
32 while (!q.empty()) {
33 int u = q.front(); q.pop();
34 for (int v : adj[u]) {
35 int mu = matchR[v]; // matched partner of v on L side (or -1)
36 // We can go L(u)->R(v) if edge is unmatched (always true in L->R direction)
37 // and then R(v)->L(mu) via matched edge if exists.
38 if (mu != -1) {
39 if (dist[mu] == -1) { // not yet layered
40 dist[mu] = dist[u] + 1;
41 q.push(mu);
42 }
43 } else {
44 // Found an unmatched R at some level; indicates there is a shortest augmenting path
45 reachableFreeR = true;
46 }
47 }
48 }
49 return reachableFreeR;
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 mu = matchR[v];
56 if (mu == -1) {
57 // We reached a free R-vertex along a shortest path layer; augment here
58 matchL[u] = v;
59 matchR[v] = u;
60 return true;
61 } else if (dist[mu] == dist[u] + 1 && dfs(mu, it)) {
62 // Follow layer-respecting alternating edge R(v)->L(mu)
63 matchL[u] = v;
64 matchR[v] = u;
65 return true;
66 }
67 }
68 // Dead end; do not revisit u in this phase
69 dist[u] = -1;
70 return false;
71 }
72
73 int max_matching() {
74 int matching = 0;
75 // While there exists some shortest augmenting path
76 while (bfs()) {
77 // Iteration pointers to avoid re-scanning edges from L
78 vector<int> it(nL, 0);
79 for (int u = 0; u < nL; ++u) {
80 if (matchL[u] == -1) {
81 if (dfs(u, it)) matching++;
82 }
83 }
84 }
85 return matching;
86 }
87};
88
89int main() {
90 ios::sync_with_stdio(false);
91 cin.tie(nullptr);
92
93 // Example: 4 workers (L) and 4 jobs (R)
94 int nL = 4, nR = 4;
95 HopcroftKarp hk(nL, nR);
96 // Edges (0-based): compatible worker->job
97 hk.add_edge(0, 0);
98 hk.add_edge(0, 1);
99 hk.add_edge(1, 1);
100 hk.add_edge(1, 2);
101 hk.add_edge(2, 2);
102 hk.add_edge(3, 2);
103 hk.add_edge(3, 3);
104
105 int m = hk.max_matching();
106 cout << "Maximum matching size = " << m \<< "\n";
107
108 // Print pairs u-L matched with v-R
109 for (int u = 0; u < nL; ++u) if (hk.matchL[u] != -1) {
110 cout << "L(" << u << ") - R(" << hk.matchL[u] << ")\n";
111 }
112
113 return 0;
114}
115

This is a standard Hopcroft–Karp implementation. BFS builds levels from all free left vertices and detects if some free right vertex is reachable at the current shortest distance. DFS then finds disjoint augmenting paths that respect these layers and augments them simultaneously. The example constructs a small compatibility graph and outputs the maximum matching and pairs.

Time: O(E sqrt(V))Space: O(V + E)
Recover minimum vertex cover from maximum matching (König’s theorem)
1#include <bits/stdc++.h>
2using namespace std;
3
4struct HopcroftKarp {
5 int nL, nR; vector<vector<int>> adj; vector<int> dist, matchL, matchR;
6 HopcroftKarp(int nL, int nR): nL(nL), nR(nR) {
7 adj.assign(nL, {}); matchL.assign(nL, -1); matchR.assign(nR, -1); dist.resize(nL);
8 }
9 void add_edge(int u, int v){ adj[u].push_back(v);}
10 bool bfs(){
11 queue<int> q; bool reachable=false;
12 for(int u=0;u<nL;++u){ if(matchL[u]==-1){dist[u]=0;q.push(u);} else dist[u]=-1; }
13 while(!q.empty()){
14 int u=q.front(); q.pop();
15 for(int v: adj[u]){
16 int mu=matchR[v];
17 if(mu!=-1){ if(dist[mu]==-1){ dist[mu]=dist[u]+1; q.push(mu);} }
18 else reachable=true;
19 }
20 }
21 return reachable;
22 }
23 bool dfs(int u, vector<int>& it){
24 for(int &i=it[u]; i<(int)adj[u].size(); ++i){
25 int v=adj[u][i]; int mu=matchR[v];
26 if(mu==-1 || (dist[mu]==dist[u]+1 && dfs(mu,it))){ matchL[u]=v; matchR[v]=u; return true; }
27 }
28 dist[u]=-1; return false;
29 }
30 int max_matching(){ int ans=0; while(bfs()){ vector<int> it(nL); for(int u=0;u<nL;++u) if(matchL[u]==-1 && dfs(u,it)) ++ans; } return ans; }
31};
32
33// Compute a minimum vertex cover from maximum matching in a bipartite graph.
34// Returns pair<coverL, coverR> as boolean arrays marking which vertices are in the MVC.
35// Procedure: Run alternating BFS from all free L-vertices using unmatched edges L->R and matched edges R->L.
36// Let VisL, VisR be visited sets. Then MVC = (L \ VisL) union (R ∩ VisR).
37
38pair<vector<char>, vector<char>> min_vertex_cover(const HopcroftKarp& hk){
39 int nL=hk.nL, nR=hk.nR;
40 vector<char> visL(nL,0), visR(nR,0);
41 queue<int> q;
42 // Start from all free vertices in L
43 for(int u=0;u<nL;++u) if(hk.matchL[u]==-1){ visL[u]=1; q.push(u);}
44 while(!q.empty()){
45 int u=q.front(); q.pop();
46 // Traverse unmatched edges L->R
47 for(int v: hk.adj[u]){
48 if(hk.matchL[u]==v) continue; // skip matched edge u-v here (we only want unmatched L->R)
49 if(!visR[v]){
50 visR[v]=1; // visit R via unmatched edge
51 int mu = hk.matchR[v];
52 if(mu!=-1 && !visL[mu]){ visL[mu]=1; q.push(mu);} // follow matched edge R->L
53 }
54 }
55 }
56 // Build MVC sets
57 vector<char> coverL(nL,0), coverR(nR,0);
58 for(int u=0;u<nL;++u) if(!visL[u]) coverL[u]=1; // L \ VisL
59 for(int v=0;v<nR;++v) if(visR[v]) coverR[v]=1; // R ∩ VisR
60 return {coverL, coverR};
61}
62
63int main(){
64 ios::sync_with_stdio(false); cin.tie(nullptr);
65
66 // Example graph
67 int nL=3, nR=3; HopcroftKarp hk(nL,nR);
68 hk.add_edge(0,0); hk.add_edge(0,1);
69 hk.add_edge(1,1);
70 hk.add_edge(2,1); hk.add_edge(2,2);
71
72 int mm = hk.max_matching();
73 cout << "Max matching = " << mm << "\n";
74
75 auto [covL, covR] = min_vertex_cover(hk);
76 cout << "Min Vertex Cover (L side indices): ";
77 for(int u=0;u<nL;++u) if(covL[u]) cout << u << ' '; cout << "\n";
78 cout << "Min Vertex Cover (R side indices): ";
79 for(int v=0;v<nR;++v) if(covR[v]) cout << v << ' '; cout << "\n";
80
81 // You can also compute maximum independent set in bipartite graphs as V - MVC
82 int mvc = accumulate(covL.begin(), covL.end(), 0) + accumulate(covR.begin(), covR.end(), 0);
83 cout << "|MVC| = " << mvc << ", |Max Independent Set| = " << (nL+nR - mvc) << "\n";
84 return 0;
85}
86

After computing a maximum matching, we derive a minimum vertex cover using an alternating BFS from all free left vertices. Visited sets determine the cover as (L \ VisL) âˆȘ (R ∩ VisR) by König’s theorem. The example prints both maximum matching size and the resulting minimum vertex cover.

Time: O(E + V) after matching (overall O(E sqrt(V)) for matching)Space: O(V + E)
Check bipartiteness and run Hopcroft–Karp on general graph
1#include <bits/stdc++.h>
2using namespace std;
3
4// Color a general undirected graph to test bipartiteness and build L/R partitions.
5// Then connect edges from L to R accordingly and run Hopcroft–Karp.
6
7struct HopcroftKarp {
8 int nL, nR; vector<vector<int>> adj; vector<int> dist, matchL, matchR;
9 HopcroftKarp(int nL, int nR): nL(nL), nR(nR) {
10 adj.assign(nL, {}); matchL.assign(nL, -1); matchR.assign(nR, -1); dist.resize(nL);
11 }
12 void add_edge(int u, int v){ adj[u].push_back(v);}
13 bool bfs(){ queue<int> q; bool ok=false; for(int u=0;u<nL;++u){ if(matchL[u]==-1){dist[u]=0;q.push(u);} else dist[u]=-1; }
14 while(!q.empty()){ int u=q.front(); q.pop(); for(int v: adj[u]){ int mu=matchR[v]; if(mu!=-1){ if(dist[mu]==-1){ dist[mu]=dist[u]+1; q.push(mu);} } else ok=true; } } return ok; }
15 bool dfs(int u, vector<int>& it){ for(int &i=it[u]; i<(int)adj[u].size(); ++i){ int v=adj[u][i]; int mu=matchR[v]; if(mu==-1 || (dist[mu]==dist[u]+1 && dfs(mu,it))){ matchL[u]=v; matchR[v]=u; return true; } } dist[u]=-1; return false; }
16 int max_matching(){ int ans=0; while(bfs()){ vector<int> it(nL); for(int u=0;u<nL;++u) if(matchL[u]==-1 && dfs(u,it)) ++ans; } return ans; }
17};
18
19int main(){
20 ios::sync_with_stdio(false); cin.tie(nullptr);
21
22 int n, m; // general undirected graph
23 // Example input: n m then m edges 0-based
24 // 4 4
25 // 0 1
26 // 1 2
27 // 2 3
28 // 0 3
29 cin >> n >> m;
30 vector<vector<int>> g(n);
31 for(int i=0;i<m;++i){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u);}
32
33 // BFS color to test bipartiteness
34 const int UNK=-1; vector<int> color(n, UNK);
35 queue<int> q;
36 bool bip=true; vector<int> Lmap(n,-1), Rmap(n,-1); int nL=0, nR=0;
37 for(int s=0;s<n;++s){ if(color[s]!=UNK) continue; color[s]=0; q.push(s);
38 while(!q.empty() && bip){ int u=q.front(); q.pop(); for(int v: g[u]){ if(color[v]==UNK){ color[v]=color[u]^1; q.push(v);} else if(color[v]==color[u]){ bip=false; break; } } }
39 }
40 if(!bip){ cout << "Graph is not bipartite.\n"; return 0; }
41
42 // Build indices for L and R partitions
43 for(int i=0;i<n;++i){ if(color[i]==0) Lmap[i]=nL++; else Rmap[i]=nR++; }
44 HopcroftKarp hk(nL, nR);
45 for(int u=0;u<n;++u){ if(color[u]!=0) continue; // only add L->R edges once
46 for(int v: g[u]) if(color[v]==1){ hk.add_edge(Lmap[u], Rmap[v]); }
47 }
48
49 cout << "Maximum matching = " << hk.max_matching() << "\n";
50 for(int u=0;u<nL;++u) if(hk.matchL[u]!=-1){ cout << "L("<<u<<") - R("<<hk.matchL[u]<<")\n"; }
51
52 return 0;
53}
54

This example starts from a general undirected graph, checks bipartiteness by BFS 2-coloring, maps original vertices to L/R indices, constructs the bipartite graph, and runs Hopcroft–Karp. If the graph is not bipartite, it reports and exits; otherwise it prints the maximum matching.

Time: O(V + E) for bipartite check, plus O(E sqrt(V)) for matchingSpace: O(V + E)
Minimum path cover in a DAG via Hopcroft–Karp reduction
1#include <bits/stdc++.h>
2using namespace std;
3
4// Reduce DAG minimum path cover (vertex-disjoint) to bipartite matching.
5// Build bipartite graph with left and right copies of each vertex.
6// For each edge u->v in DAG, add L(u) -> R(v). Then MinPathCover = n - MaxMatching.
7
8struct HopcroftKarp {
9 int nL, nR; vector<vector<int>> adj; vector<int> dist, matchL, matchR;
10 HopcroftKarp(int nL, int nR): nL(nL), nR(nR){ adj.assign(nL,{}); matchL.assign(nL,-1); matchR.assign(nR,-1); dist.resize(nL);}
11 void add_edge(int u,int v){ adj[u].push_back(v);}
12 bool bfs(){ queue<int> q; bool ok=false; for(int u=0;u<nL;++u){ if(matchL[u]==-1){dist[u]=0;q.push(u);} else dist[u]=-1; }
13 while(!q.empty()){ int u=q.front(); q.pop(); for(int v: adj[u]){ int mu=matchR[v]; if(mu!=-1){ if(dist[mu]==-1){ dist[mu]=dist[u]+1; q.push(mu);} } else ok=true; } } return ok; }
14 bool dfs(int u, vector<int>& it){ for(int &i=it[u]; i<(int)adj[u].size(); ++i){ int v=adj[u][i]; int mu=matchR[v]; if(mu==-1 || (dist[mu]==dist[u]+1 && dfs(mu,it))){ matchL[u]=v; matchR[v]=u; return true; } } dist[u]=-1; return false; }
15 int max_matching(){ int ans=0; while(bfs()){ vector<int> it(nL); for(int u=0;u<nL;++u) if(matchL[u]==-1 && dfs(u,it)) ++ans; } return ans; }
16};
17
18int main(){
19 ios::sync_with_stdio(false); cin.tie(nullptr);
20
21 int n, m; // DAG with vertices 0..n-1 and m directed edges
22 // Example:
23 // 5 5
24 // 0 1
25 // 0 2
26 // 1 3
27 // 2 3
28 // 3 4
29 cin >> n >> m;
30 vector<vector<int>> dag(n);
31 for(int i=0;i<m;++i){ int u,v; cin>>u>>v; dag[u].push_back(v);}
32
33 // Build bipartite graph L(0..n-1) -> R(0..n-1)
34 HopcroftKarp hk(n, n);
35 for(int u=0; u<n; ++u) for(int v: dag[u]) hk.add_edge(u, v);
36
37 int mm = hk.max_matching();
38 int minPathCover = n - mm;
39 cout << "Maximum matching = " << mm << "\n";
40 cout << "Minimum path cover in DAG = " << minPathCover << "\n";
41
42 // Optionally reconstruct the paths:
43 // For each vertex u, out-match to v if matchL[u]==v; build chains starting at vertices with no in-match (matchR[u]==-1).
44 vector<int> outMatch(n, -1), inMatch(n, -1);
45 for(int u=0; u<n; ++u) if(hk.matchL[u]!=-1) outMatch[u]=hk.matchL[u];
46 for(int v=0; v<n; ++v) if(hk.matchR[v]!=-1) inMatch[v]=hk.matchR[v];
47
48 vector<char> used(n, 0);
49 for(int u=0; u<n; ++u){
50 if(inMatch[u]==-1){ // start of a path
51 int x=u; vector<int> path;
52 while(x!=-1 && !used[x]){ used[x]=1; path.push_back(x); x=outMatch[x]; }
53 if(!path.empty()){
54 cout << "Path: ";
55 for(size_t i=0;i<path.size();++i){ cout<<path[i]<<(i+1==path.size()? '\n':' ');}
56 }
57 }
58 }
59
60 return 0;
61}
62

This program reads a DAG and reduces its minimum path cover to bipartite matching by duplicating vertices into L and R and adding edges for each u→v. Hopcroft–Karp finds the maximum matching, yielding MinPathCover = n − |M|. The code also sketches how to reconstruct the path cover chains from the matching.

Time: O(E sqrt(V)) for matching (E is DAG edges, V = n+n)Space: O(V + E)