Prerequisites: Bipartite Graphs, Graph Matching
Introduction
This article studies the maximum matching problem in a bipartite graph .
A familiar example of bipartite matching is pairing men and women. Suppose there are several men () and women (); each person may be paired at most once, and the allowed pairings are specified by a list (). The task of maximum bipartite matching is to find as many pairs as possible under these restrictions.
Note
We assume that a partition (or coloring) of the bipartite graph's vertex set is known: . If the partition of is not known in advance, it can be found using the bipartite-graph coloring algorithm in time.
Kuhn Algorithm
The Kuhn algorithm is a direct application of Berge's lemma. It is also part of the Hungarian algorithm.
Process
To find a maximum matching, the algorithm enumerates the vertices one by one, finds an augmenting path starting from each vertex, and augments along it. An augmenting path always has odd length, so in a bipartite graph its endpoints must lie in different parts. Therefore, it suffices to consider augmenting paths starting from the left part.
To find an augmenting path, orient the bipartite graph according to the current matching . Along an augmenting path (or any alternating path) starting from an unmatched left vertex, non-matching edges go from the left part to the right part, while matching edges go from the right part to the left part. Thus, orient every non-matching edge toward the right part and every matching edge toward the left part. Finding an augmenting path becomes finding a simple directed path from an unmatched left vertex to an unmatched vertex, which can be done with DFS or BFS in time.
(In the figure, dark vertices are matched, light vertices are unmatched, red edges are matching edges, black edges are non-matching edges, and arrows show the orientation induced by the current matching. The path is an augmenting path relative to the current matching.)
Initially, all edges point toward the right part. Whenever an augmenting path is found, reverse every edge on the path to reflect the reversal of its matching status. At the end, exactly the edges pointing toward the left part are matching edges.
Since at most left vertices need to be enumerated once each, the total time complexity is .
Optimizations
Some simple techniques can improve the constants of the Kuhn algorithm:
- Kuhn's algorithm is based on Berge's lemma, which does not require the two parts of a bipartite graph to be specified in advance. Thus, Kuhn's algorithm remains correct even when the two parts are not explicitly identified, as long as the graph is bipartite. In practice, however, coloring the graph first and determining its left and right parts is often more efficient.
- The time complexity of the Kuhn algorithm described above is actually , so the smaller of the two parts can be chosen as the left part .
- When searching for augmenting paths, the visited marks used to avoid repeated searches do not need to be cleared before every DFS. Before clearing them, try to find augmenting paths from all unmatched left vertices. In one such round every edge is visited at most once, so the complexity remains ; however, several augmenting paths may be found in one round. Therefore, the total number of rounds is at most , where is a maximum matching, and the overall complexity becomes .
- When searching for an augmenting path, consider unmatched right vertices first, since this gives a shorter augmenting path.
- Berge's lemma does not require the initial matching to be empty. Kuhn's algorithm can therefore begin with a random set of pairwise disjoint edges as an initial matching, reducing later searches. If optimization 3 is used, this optimization can be omitted.
The worst-case complexity is still , but a fully optimized Kuhn algorithm performs well in practice. To prevent particular inputs from forcing the worst case, randomly shuffle the order of edges or vertices before matching.
Reference Implementation
The implementation does not need to maintain the orientations explicitly; it only needs to maintain the vertex matched with each vertex.
Template Problem: Library Checker - Matching on Bipartite Graph
#include <algorithm>
#include <ctime>
#include <iostream>
#include <random>
#include <tuple>
#include <vector>
std::mt19937_64 rng(
static_cast<std::mt19937_64::result_type>(std::time(nullptr)));
struct BipartiteGraph {
int n1, n2; // number of vertices in X and Y, resp.
std::vector<std::vector<int>> g; // edges from X to Y
std::vector<int> ma, mb; // matches from X to Y and from Y to X, resp.
std::vector<bool> vis; // visiting marks for DFS.
BipartiteGraph(int n1, int n2)
: n1(n1), n2(n2), g(n1), ma(n1, -1), mb(n2, -1) {}
// Add an edge from u in X to v in Y.
void add_edge(int u, int v) { g[u].emplace_back(v); }
// Find an augmenting path starting at u.
bool dfs(int u) {
vis[u] = true;
// Heuristic: find unsaturated vertices whenever possible.
for (int v : g[u]) {
if (mb[v] == -1) {
ma[u] = v;
mb[v] = u;
return true;
}
}
for (int v : g[u]) {
if (!vis[mb[v]] && dfs(mb[v])) {
ma[u] = v;
mb[v] = u;
return true;
}
}
return false;
}
// Kuhn's maximum matching algorithm.
std::vector<std::pair<int, int>> kuhn_maximum_matching() {
// Randomly shuffle the edges.
for (int u = 0; u < n1; ++u) {
std::shuffle(g[u].begin(), g[u].end(), rng);
}
// Find a maximal set of vertex-disjoint augmenting paths in each round.
while (true) {
bool succ = false;
vis.assign(n1, false);
for (int u = 0; u < n1; ++u) {
succ |= ma[u] == -1 && dfs(u);
}
if (!succ) break;
}
// Collect the matched pairs.
std::vector<std::pair<int, int>> matches;
matches.reserve(n1);
for (int u = 0; u < n1; ++u) {
if (ma[u] != -1) {
matches.emplace_back(u, ma[u]);
}
}
return matches;
}
};
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(nullptr);
int n1, n2, m;
std::cin >> n1 >> n2 >> m;
BipartiteGraph gr(n1, n2);
for (int i = 0; i < m; ++i) {
int u, v;
std::cin >> u >> v;
gr.add_edge(u, v);
}
auto res = gr.kuhn_maximum_matching();
std::cout << res.size() << '\n';
for (int i = 0; i < res.size(); ++i) {
std::cout << res[i].first << ' ' << res[i].second << '\n';
}
return 0;
}
Hopcroft–Karp Algorithm
The Hopcroft–Karp algorithm further optimizes the Kuhn algorithm's search for augmenting paths, reducing the total number of rounds to and achieving time complexity . It is in fact a special case of Dinic's algorithm.
Process
The algorithm still searches for augmenting paths, but to complete the matching in fewer rounds, it uses the following strategy in each round:
- Orient matching edges toward the left part and non-matching edges toward the right part.
- Starting from every unmatched left vertex, run BFS on the directed graph and record the level of each visited vertex, stopping when an unmatched right vertex first appears. If BFS finishes without finding an unmatched right vertex, the current matching is maximum.
- Starting from each unmatched left vertex in turn, run DFS to find and augment along an augmenting path. During DFS, follow edges whose levels are consecutive and strictly increasing (that is, ), and visit only vertices not yet visited during the current DFS round. In particular, DFS does not visit vertices that BFS in the previous step did not reach.
In network-flow terminology, step 2 constructs a level graph, while step 3 finds a blocking flow in the level graph. A level graph is one in which every edge goes from one level to the next; in this context, a blocking flow is a maximal collection of pairwise vertex-disjoint augmenting paths. The collection found in step 3 is necessarily maximal: otherwise, if a new augmenting path existed, the algorithm would already have found it when it enumerated its starting point.
Compared with the Kuhn algorithm above, the key change in Hopcroft–Karp is adding the construction of a level graph before finding a blocking flow. DFS on the level graph restricts the algorithm to shortest paths to each vertex. The benefit is that the lengths of the augmenting paths found in successive rounds are strictly increasing. Moreover, one can prove that, before a maximum matching is found, the length of an augmenting path increases at most times, where is the size of a maximum matching. Thus the total number of augmentation rounds is bounded by , giving time complexity . Since , the time complexity can also be written as the looser upper bound .
Proof
First, we show that the lengths of the augmenting paths found in successive rounds are strictly increasing.
Suppose the BFS in the current round reaches level . Since all unmatched right vertices found by BFS are on the same level, every augmenting path found by DFS in this round has length . We need to prove that after augmenting along the set of paths found in this round, the reoriented directed graph contains no augmenting path of length at most .
In fact, if is a shortest augmenting path relative to and is an augmenting path relative to , then . This is because augments relative to twice. Thus, by an argument analogous to the proof of Berge's lemma, the symmetric difference contains at least two vertex-disjoint augmenting paths relative to , namely and . By the minimality of ,
This shows that . Therefore, if after augmenting along a new augmenting path still had the same length, it would have to be disjoint from every one of them, contradicting the maximality of . This contradiction shows that after augmenting along the blocking flow, every new augmenting path is strictly longer.
Finally, we show that the length of an augmenting path increases at most times.
Let . After the first rounds, the remaining augmenting paths have length at least . Let the current matching be . As in the preceding case, we can show that the graph contains pairwise vertex-disjoint augmenting paths relative to . Each augmenting path uses at least matching edges from , so the total number of these paths is at most ; that is, . Thus, starting from , at most more augmentations are possible, which also means that the algorithm performs at most more augmentation rounds. Hence, the total increase in augmenting-path length is at most .
This is only a worst-case estimate for the Hopcroft–Karp algorithm. In a random graph, the time complexity of Hopcroft–Karp is in fact with high probability.1
Optimization
When building the level graph, Hopcroft–Karp, like the usual Dinic algorithm, stops upon reaching an unmatched right vertex. For bipartite matching alone, however, this is unnecessary. Moreover, premature termination of BFS restricts the scope of subsequent DFS and limits the number of augmenting paths found in each round, slowing the overall matching process. On some graphs, it is even less efficient than the optimized Kuhn algorithm. A simple improvement is therefore to let BFS continue instead of terminating early, and build the level graph for every reachable vertex.
Correctness Proof
In the optimized algorithm, the augmenting paths in a blocking flow no longer all have the same length, so the preceding complexity proof no longer applies directly. However, by constructing an auxiliary graph for each round, we can still show that the shortest augmenting-path length strictly increases, preserving the worst-case complexity bound.
Let the bipartite graph be with current matching . Let be the set of unmatched right vertices reachable by BFS, and let the shortest augmenting path reaching have length . Set . For each , create a chain starting at of length , marking its new vertices alternately as left and right vertices and its new edges alternately as matching and non-matching edges. Let the resulting graph be , with matching and shortest augmenting-path length . The augmenting paths found in the level graph of relative to —that is, the shortest augmenting paths reaching the corresponding vertices—are in bijection with the globally shortest augmenting paths in relative to . Therefore, finding a blocking flow and augmenting in the level graph of is equivalent to doing so in the level graph of . By the preceding proof, after augmentation, has no augmenting path of length . Hence, has no augmenting path of length either: such a path would extend along the newly added alternating chain and necessarily correspond to an augmenting path in of length . Thus, the shortest augmenting-path length is again strictly increasing from one round to the next, and the overall complexity remains .
Reference Implementation
Template Problem: Library Checker - Matching on Bipartite Graph
#include <algorithm>
#include <iostream>
#include <queue>
#include <tuple>
#include <vector>
struct BipartiteGraph {
int n1, n2; // number of vertices in X and Y, resp.
std::vector<std::vector<int>> g; // edges from X to Y
std::vector<int> ma, mb; // matches from X to Y and from Y to X, resp.
std::vector<int> dist; // distance from unsaturated vertices in X.
BipartiteGraph(int n1, int n2)
: n1(n1), n2(n2), g(n1), ma(n1, -1), mb(n2, -1) {}
// Add an edge from u in X to v in Y.
void add_edge(int u, int v) { g[u].emplace_back(v); }
// Build the level graph.
bool bfs() {
dist.assign(n1, -1);
std::queue<int> q;
for (int u = 0; u < n1; ++u) {
if (ma[u] == -1) {
dist[u] = 0;
q.emplace(u);
}
}
// Build the level graph for all reachable vertices.
bool succ = false;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (mb[v] == -1) {
succ = true;
} else if (dist[mb[v]] == -1) {
dist[mb[v]] = dist[u] + 1;
q.emplace(mb[v]);
}
}
}
return succ;
}
// Find an augmenting path starting at u.
bool dfs(int u) {
for (int v : g[u]) {
if (mb[v] == -1 || (dist[mb[v]] == dist[u] + 1 && dfs(mb[v]))) {
ma[u] = v;
mb[v] = u;
return true;
}
}
dist[u] = -1; // Mark this point as inreachable after one visit.
return false;
}
// Hopcroft-Karp maximum matching algorithm.
std::vector<std::pair<int, int>> hopcroft_karp_maximum_matching() {
// Build the level graph and then find a blocking flow.
while (bfs()) {
for (int u = 0; u < n1; ++u) {
if (ma[u] == -1) {
dfs(u);
}
}
}
// Collect the matched pairs.
std::vector<std::pair<int, int>> matches;
matches.reserve(n1);
for (int u = 0; u < n1; ++u) {
if (ma[u] != -1) {
matches.emplace_back(u, ma[u]);
}
}
return matches;
}
};
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(nullptr);
int n1, n2, m;
std::cin >> n1 >> n2 >> m;
BipartiteGraph gr(n1, n2);
for (int i = 0; i < m; ++i) {
int u, v;
std::cin >> u >> v;
gr.add_edge(u, v);
}
auto res = gr.hopcroft_karp_maximum_matching();
std::cout << res.size() << '\n';
for (int i = 0; i < res.size(); ++i) {
std::cout << res[i].first << ' ' << res[i].second << '\n';
}
return 0;
}
Reduction to the Maximum-Flow Problem
The maximum bipartite matching problem can be reduced to the maximum-flow problem.
As shown in the figure, add two vertices as the source and sink. From the source, connect an edge to every left vertex; from every right vertex, add an edge to the sink; and for every undirected edge in the bipartite graph, add an edge directed from the left vertex to the right vertex. Every edge has capacity . Each flow in the resulting directed graph corresponds bijectively to a matching in the bipartite graph, and the value of the flow is the size of the corresponding matching. Therefore, solving maximum bipartite matching is equivalent to solving the maximum flow in the corresponding directed graph.
Any algorithm that solves maximum flow can be used to solve maximum bipartite matching. It is easy to see that the Kuhn and Hopcroft–Karp algorithms are special cases of corresponding maximum-flow algorithms. Likewise, algorithms such as the push–relabel algorithm can also solve maximum bipartite matching. However, note that any maximum-flow algorithm needs to be optimized specifically when applied to bipartite matching to avoid excessive constant factors.
Linear Programming Form
Like other maximum-flow problems, maximum matching in a bipartite graph can be formulated as a linear program. If indicates whether edge belongs to the matching, we obtain the following linear program:
Here, denotes incidence, meaning that vertex is an endpoint of edge . In addition to nonnegativity constraints, the problem requires each vertex to be incident to at most one edge, which is exactly the definition of a matching. Thus, every matching corresponds to an integer point in the feasible region of this linear program.
The converse is not true. In a feasible solution, may be fractional, which does not represent an actual matching. Nevertheless, for a bipartite graph , every extreme-point solution of the linear program above is integral. This means that the optimum of the objective function is always attained at an integer point, so noninteger cases need not be considered. This property does not hold for general graphs, so the linear program above is not equivalent to the maximum matching problem on general graphs.
The dual of this linear program can be written as follows:
We will soon see that this is exactly the minimum vertex-cover problem for bipartite graphs.
Dulmage–Mendelsohn Decomposition
Using a maximum matching in a bipartite graph, we can partition the vertices into several disjoint subsets, fully characterizing the distribution and structural properties of all maximum matchings in the graph. This is the Dulmage–Mendelsohn decomposition. In algorithm competitions, this decomposition can identify critical vertices and critical edges in a maximum matching, and can then be used to determine whether the maximum matching is unique or to solve problems such as bipartite graph games.
Construction
For the bipartite graph , let be a maximum matching.
As shown in the figure, define the following three subsets of all vertices :
- Even-reachable vertices : the set of vertices reachable from an unmatched vertex by an alternating path of even length;
- Odd-reachable vertices : the set of vertices reachable from an unmatched vertex by an alternating path of odd length;
- Unreachable vertices : the set of vertices that cannot be reached from an unmatched vertex by an alternating path.
The three vertex sets obtained this way have the following properties:
Properties
- The sets form a partition of the vertex set, and this partition is independent of the choice of maximum matching .
- Every maximum matching of contains a perfect matching among the vertices of , and matches every vertex in to a vertex in . In other words, the size of a maximum matching of is .
- The graph contains no edge connecting a vertex in to a vertex in .
Proof
By definition, is disjoint from . It remains to prove that and are disjoint. Otherwise, for a vertex , there is an alternating path of even length from an unmatched vertex to , and an alternating path of odd length from an unmatched vertex to . Since is bipartite, , and the edges by which the two paths reach are respectively a matching edge and a non-matching edge. Connecting the two paths therefore gives an alternating path from through to . This is an augmenting path, contradicting the fact that is a maximum matching. Hence .
- Let be a maximum matching different from . Repeating the proof of Berge's lemma shows that consists only of even-length paths and even cycles. Starting from maximum matching , we can flip the edges in these connected components (swapping matching and non-matching edges) one component at a time to obtain maximum matching . When an even cycle is flipped, unmatched vertices remain unmatched, and the parity of the lengths of alternating paths starting from them does not change; when an even-length path is flipped, the matching status of its two endpoints is exchanged, but the parity of the path length from either endpoint to any vertex on the path is the same. Therefore, throughout the flipping process, the sets remain unchanged. This shows that the decomposition is independent of the choice of maximum matching .
- If a matching edge occurs on an alternating path starting from an unmatched vertex , its two endpoints necessarily have different parity of distance from and therefore belong to and , respectively; otherwise, both endpoints belong to . Thus, every matching edge in a maximum matching is either an edge or a edge. Conversely, an unmatched vertex can be reached by the length-zero alternating path starting from itself, so unmatched vertices occur only in ; hence every vertex in and is matched. Simple counting shows that the size of a maximum matching is .
- By definition, any vertex in , including , can be reached from an unmatched vertex by an alternating path of even length; that is, a vertex in is either unmatched or the alternating path reaching it ends with a matching edge. If the graph contains an edge connecting to a vertex in , namely , then by the preceding paragraph this edge is necessarily non-matching, and we can extend the alternating path along it. This puts in , contradicting the first property. Therefore, the graph contains no edge connecting a vertex in to a vertex in .
The decomposition of the vertex set obtained this way is called the Dulmage–Mendelsohn decomposition. After finding a maximum matching with the algorithms described above, the decomposition can be computed by BFS in time.
Critical Vertices of a Maximum Matching
If a vertex is matched in every maximum matching of the bipartite graph , it is called a critical vertex of the maximum matching. The following result says that a vertex is critical if and only if, in one maximum matching, there is no even-length alternating path from an unmatched vertex to that vertex.
Theorem
Let the Dulmage–Mendelsohn decomposition of the bipartite graph be . Then a vertex is critical if and only if .
Proof
By the properties of the Dulmage–Mendelsohn decomposition, in graph , the vertices in and are matched in every maximum matching. Therefore, every vertex in is critical. It remains to show that no vertex in is critical. If, in a maximum matching , a vertex is critical, there is an even-length alternating path connecting to an unmatched vertex . Flipping all edges on this path gives a maximum matching in which is unmatched. Therefore, no vertex in is critical.
Thus, to find the critical vertices of a maximum matching, it suffices to compute the Dulmage–Mendelsohn decomposition.
Critical Edges of a Maximum Matching
Similarly, if an edge is a matching edge in every maximum matching of the bipartite graph , it is called a critical edge of the maximum matching. The maximum matching is unique if and only if every matching edge in one of its maximum matchings is critical.
Theorem
Let the Dulmage–Mendelsohn decomposition of the bipartite graph be , and let be one of its maximum matchings. Then an edge is critical if and only if both endpoints of are in , is a matching edge in , and relative to there is no alternating cycle containing .
Proof
The endpoints of a critical edge must be critical vertices. By the properties of the Dulmage–Mendelsohn decomposition, matching edges can only be edges or edges. However, no vertex in is critical, so a critical edge must be an edge. Of course, a critical edge must also be a matching edge in . Let be an edge. It is not critical if and only if there is another maximum matching such that . Repeating the proof of Berge's lemma shows that consists only of even-length paths and even cycles. One endpoint of each such path is unmatched relative to , so no vertex on a path can be in , contradicting the choice of . Hence, can occur only on an even cycle. Therefore, an edge is not critical if and only if, relative to , there is an alternating cycle containing . This proves the claim.
Thus, to find the critical edges of a maximum matching, perform the following steps:
- Find graph 's maximum matching ;
- Orient according to the edges of graph , obtaining directed graph ;
- Use BFS to find the set in the Dulmage–Mendelsohn decomposition, that is, the set of vertices unreachable from unmatched vertices along alternating paths;
- Use Tarjan's algorithm to find all strongly connected components of directed graph ;
- Traverse the edges in matching . If both endpoints are in but are not in the same strongly connected component, it is a critical edge.
After a maximum matching has been obtained, the remaining steps take time.
Related Problems
The maximum bipartite matching algorithm can be used to solve other combinatorial optimization problems.
Minimum Vertex Cover in a Bipartite Graph
The minimum vertex cover problem asks us to choose as few vertices as possible in an undirected graph so that every edge has at least one selected endpoint.
The minimum vertex cover problem is NP-hard for general graphs, but for bipartite graphs, König's theorem reduces it to maximum matching, so it can be solved efficiently. The proof of the theorem also gives a construction of a minimum vertex cover.
König's Theorem
In a bipartite graph, the number of vertices in a minimum vertex cover equals the number of edges in a maximum matching.
Proof
Let the bipartite graph have maximum matching . Let 's unmatched left vertices be , and let be the set of vertices reachable from them along alternating paths. Then is the desired minimum vertex cover.
First, is a vertex cover. Suppose otherwise: there is an edge with and . Let be an alternating path reaching . If is a matching edge, the last edge of is , contradicting ; if is not a matching edge, we can extend along edge the path to obtain an alternating path reaching , again contradicting . These contradictions show that every edge has at least one endpoint in , so is a vertex cover.
It remains to show that is a minimum vertex cover. To cover all edges of the maximum matching , any vertex cover needs at least vertices. Therefore, it suffices to prove , which makes it a minimum vertex cover. This is equivalent to proving that, besides containing one endpoint of every matching edge, contains no other vertices; in other words, contains no unmatched vertex. Suppose otherwise that there is an unmatched vertex . If , then necessarily , contradicting the construction of ; if , an alternating path reaching is an augmenting path relative to , which contradicts the fact that is maximum by Berge's lemma. These contradictions show that no such unmatched vertex exists, and hence is a minimum vertex cover.
From the network-flow viewpoint, the minimum vertex cover problem is the minimum-cut problem: selecting a left vertex is equivalent to cutting its edge to the source, and selecting a right vertex is equivalent to cutting its edge to the sink. From the linear-programming viewpoint, the minimum vertex cover problem is the dual of the maximum matching problem. Thus, König's theorem can be viewed as a special case of the max-flow min-cut theorem, or, more generally, a special case of the strong duality theorem of linear programming.
Maximum Independent Set in a Bipartite Graph
The maximum independent set problem asks us to choose as many vertices as possible in an undirected graph so that no two are adjacent.
For general graphs, the following theorem holds:
Theorem
In a graph , a vertex set is a vertex cover if and only if its complement is an independent set.
Proof
A vertex set is a vertex cover if and only if at least one endpoint of every edge in , say edge , occurs in , if and only if there is no edge in whose two endpoints both occur in , if and only if is an independent set.
Corollary
In a graph , the sum of the sizes of a minimum vertex cover and a maximum independent set equals the number of vertices.
Therefore, just like the minimum vertex cover problem, the maximum independent set problem is NP-hard for general graphs, but can be reduced to maximum matching for bipartite graphs and solved efficiently.
Minimum Path Cover in a Directed Acyclic Graph
The minimum path cover problem asks us to choose the minimum number of simple paths in a directed graph such that every vertex occurs in exactly one path.
The minimum path cover problem is NP-hard on general directed graphs, but for directed acyclic graphs it can be reduced to maximum bipartite matching. For a DAG , construct a bipartite graph as follows:
- For each vertex , create an in-vertex and an out-vertex . Let the sets of all in-vertices and out-vertices be and , respectively. They form the left and right parts of the new graph, respectively.
- For each directed edge , create an undirected edge . The set of all undirected edges is .
Thus, we have the following conclusion:
Theorem
The sum of the size of a minimum path cover of the DAG and the size of a maximum matching of the corresponding bipartite graph equals the number of vertices.
Proof
In the bipartite graph , each matching corresponds to a subgraph of graph , namely , and every vertex in has indegree and outdegree at most one; that is, is a collection of pairwise vertex-disjoint paths or cycles in the directed graph . However, is assumed to be acyclic, so contains only disjoint paths. Conversely, each such subgraph yields a corresponding matching. Since the size of matching equals the number of vertices minus the number of paths in , the minimum path cover problem of corresponds to the maximum matching problem of .
The proof is constructive, so it is easy to construct a corresponding minimum path cover from the maximum matching obtained. This construction also shows why the reduction fails for general directed graphs: a matching in the bipartite graph may correspond to a cycle in the directed graph.
In particular, for a set with a partial order on it, construct the DAG . Then, by Dilworth's theorem, the size of a minimum path cover of equals the length of its longest antichain, that is, the width of the poset . Thus, this section in fact gives an efficient way to compute the width of any poset.
Example Problems
The difficulty in applying bipartite matching lies in constructing the graph; this section demonstrates graph-construction techniques through several examples.
Luogu P1129 Matrix Game
There is a 01 matrix. In each operation, we may swap two rows or two columns. Determine whether it is possible to make every entry on the main diagonal (from top left to bottom right) equal to 1.
Solution
Observe that if there are entries equal to , with no two of these s in the same row or column, a solution necessarily exists; otherwise, no solution exists. The problem is therefore reduced to finding these entries equal to .
For a particular , selecting this in the final arrangement means that the row and column of this are occupied. Thus, construct a bipartite graph with left vertices and right vertices. For every element equal to , add an edge between the left vertex corresponding to its row and the right vertex corresponding to its column. We can then solve a bipartite matching problem.
Code
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int ct, n, t[100010], x, r[100010], ans, vis[100010], dis[100010];
vector<int> to[100010];
queue<int> q;
int DFS(int x) {
if (vis[x]) {
return 0;
}
vis[x] = 1;
for (auto i : to[x]) {
if (!r[i]) {
r[i] = x;
t[x] = i;
return 1;
}
if (dis[r[i]] == dis[x] + 1 && DFS(r[i])) {
r[i] = x;
t[x] = i;
return 1;
}
}
return 0;
}
int BFS() {
fill(vis + 1, vis + n + 1, 0);
fill(dis + 1, dis + n + 1, 0);
for (int i = 1; i <= n; i++) {
if (!t[i]) {
q.push(i);
dis[i] = 1;
}
}
int f = 0;
for (; q.size(); q.pop()) {
int tmp = q.front();
for (auto i : to[tmp]) {
if (!r[i]) {
f = 1;
}
if (r[i]) {
if (!dis[r[i]]) {
dis[r[i]] = dis[tmp] + 1;
q.push(r[i]);
}
}
}
}
return f;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
for (cin >> ct; ct--;) {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> x;
if (x) {
to[i].push_back(j);
}
}
}
for (; BFS();) {
for (int i = 1; i <= n; i++) {
if (!t[i] && DFS(i)) {
ans++;
}
}
}
cout << (ans == n ? "Yes" : "No") << '\n';
for (int i = 1; i <= n; i++) {
t[i] = r[i] = 0;
to[i].clear();
}
ans = 0;
}
return 0;
}
Gym 104427B Lawyers
There are lawyers, all accused of fraud. They must defend one another so that every lawyer is acquitted. These lawyers have pairs of trust relations; a trust relation means that can defend . Any lawyer who is defended will be acquitted, with one exception: if and defend each other, both will be convicted.
Determine whether it is possible for every lawyer to be acquitted.
Solution
For every unordered pair , if can defend , add an edge from the unordered-pair vertex to that lawyer; conversely, add the corresponding edge for in the reverse direction.
Keeping only the pairs that are connected by an edge transforms the problem into maximum matching in a bipartite graph with left vertices and right vertices.
Code
#include <algorithm>
#include <iostream>
#include <map>
#include <queue>
#include <utility>
#include <vector>
using namespace std;
int n, k, u, v, t[200020], r[200020], ans, vis[200020], dis[200020], c;
map<pair<int, int>, int> mp;
vector<int> to[200020];
queue<int> q;
int DFS(int x) {
if (vis[x]) {
return 0;
}
vis[x] = 1;
for (auto i : to[x]) {
if (!r[i]) {
r[i] = x;
t[x] = i;
return 1;
}
if (dis[r[i]] == dis[x] + 1 && DFS(r[i])) {
r[i] = x;
t[x] = i;
return 1;
}
}
return 0;
}
int BFS() {
fill(vis + 1, vis + n + 1, 0);
fill(dis + 1, dis + n + 1, 0);
for (int i = 1; i <= n; i++) {
if (!t[i]) {
q.push(i);
dis[i] = 1;
}
}
int f = 0;
for (; q.size(); q.pop()) {
int tmp = q.front();
for (auto i : to[tmp]) {
if (!r[i]) {
f = 1;
}
if (r[i]) {
if (!dis[r[i]]) {
dis[r[i]] = dis[tmp] + 1;
q.push(r[i]);
}
}
}
}
return f;
}
void mxf() {
for (; BFS();) {
for (int i = 1; i <= n; i++) {
if (!t[i] && DFS(i)) {
ans++;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= k; i++) {
cin >> u >> v;
if (mp.find({u, v}) == mp.end()) {
mp[{u, v}] = mp[{v, u}] = ++c;
}
to[v].push_back({mp[{u, v}]});
}
mxf();
cout << (ans == n ? "YES" : "NO") << '\n';
ans = 0;
return 0;
}
Codeforces 1404E Bricks
Use bricks of size to exactly cover an grid; bricks may be rotated, and some cells cannot be covered.
Solution
Consider how the final arrangement is formed:
First cover every coverable cell with a brick. A brick can be formed by repeatedly merging consecutive bricks in the same row, a row merge. Similarly, an brick can be formed by repeatedly merging consecutive bricks in the same column, a column merge.
Clearly, one row merge and one column merge cannot involve the same brick, and the more merges we perform, the fewer bricks remain. Therefore, take row merges as the left vertices and column merges as the right vertices, and use the conflicts described above as edges to construct a bipartite graph. The original problem then becomes a maximum independent set problem in a bipartite graph.
Code
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int n, m, belr[220][220], beld[220][220], dcnt, rcnt, vis[100010], dis[100010],
t[100010], r[100010], cnt;
char c[220][220];
vector<int> to[100010];
queue<int> q;
int DFS(int x) {
if (vis[x]) {
return 0;
}
vis[x] = 1;
for (auto i : to[x]) {
if (!r[i]) {
r[i] = x;
t[x] = i;
return 1;
}
if (dis[r[i]] == dis[x] + 1 && DFS(r[i])) {
r[i] = x;
t[x] = i;
return 1;
}
}
return 0;
}
int BFS() {
fill(vis + 1, vis + rcnt + 1, 0);
fill(dis + 1, dis + rcnt + 1, 0);
for (int i = 1; i <= rcnt; i++) {
if (!t[i]) {
q.push(i);
dis[i] = 1;
}
}
int f = 0;
for (; q.size(); q.pop()) {
int tmp = q.front();
for (auto i : to[tmp]) {
if (!r[i]) {
f = 1;
}
if (r[i]) {
if (!dis[r[i]]) {
dis[r[i]] = dis[tmp] + 1;
q.push(r[i]);
}
}
}
}
return f;
}
int solve() {
int rt = 0;
for (; BFS();) {
for (int i = 1; i <= rcnt; i++) {
if (!t[i] && DFS(i)) {
rt++;
}
}
}
return rt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> c[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (c[i][j] == '#') {
cnt++;
if (c[i + 1][j] == '#') {
beld[i][j] = ++dcnt;
}
if (c[i][j + 1] == '#') {
belr[i][j] = ++rcnt;
}
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (c[i][j] == '#') {
if (c[i - 1][j] == '#' && c[i][j - 1] == '#') {
to[belr[i][j - 1]].push_back(beld[i - 1][j]);
}
if (c[i + 1][j] == '#' && c[i][j + 1] == '#') {
to[belr[i][j]].push_back(beld[i][j]);
}
if (c[i + 1][j] == '#' && c[i][j - 1] == '#') {
to[belr[i][j - 1]].push_back(beld[i][j]);
}
if (c[i - 1][j] == '#' && c[i][j + 1] == '#') {
to[belr[i][j]].push_back(beld[i - 1][j]);
}
}
}
}
cout << cnt - rcnt - dcnt + solve() << '\n';
return 0;
}
Codeforces 1139E - Maximize Mex
There are multisets, each containing elements. In each operation, delete one element from one of the multisets, then query the maximum obtainable by selecting at most one element from each multiset.
Solution
First consider how to solve the problem when no elements have been deleted.
For each multiset, create a new vertex; for each possible answer, create a new vertex. Then, for a multiset corresponding to vertex , for each element , add an edge from to . This simplified version becomes a maximum bipartite matching problem.
Now add back the deletion operations. We find that this is intractable: deleting one edge may cause the matching to change drastically, and the complexity is unacceptable. Instead, reverse the process: add one edge at a time, then continue forward and augment again. Therefore, this problem can only be solved with the Kuhn algorithm.
Code
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
struct node {
int u, v;
} arr[5050];
int n, m, t[5050], r[5050], d, ot[5050], kil[5050], vis[5050], ans, out[5050];
vector<int> to[5050];
int DFS(int x) {
if (vis[x]) {
return 0;
}
vis[x] = 1;
for (auto i : to[x]) {
if (r[i] == -1) {
r[i] = x;
t[x] = i;
return 1;
}
if (DFS(r[i])) {
r[i] = x;
t[x] = i;
return 1;
}
}
return 0;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> arr[i].u;
if (arr[i].u > m) {
arr[i].u = m + 1;
}
}
for (int i = 1; i <= n; i++) {
cin >> arr[i].v;
}
cin >> d;
for (int i = 1; i <= d; i++) {
cin >> ot[i];
kil[ot[i]] = 1;
}
for (int i = 1; i <= n; i++) {
if (!kil[i]) {
to[arr[i].u].push_back(arr[i].v);
}
}
fill(r + 1, r + m + 1, -1);
for (; ans <= m;) {
fill(vis, vis + m + 2, 0);
if (DFS(ans)) {
ans++;
} else {
break;
}
}
out[d] = ans;
for (int i = d; i > 1; i--) {
to[arr[ot[i]].u].push_back(arr[ot[i]].v);
for (; ans <= m;) {
fill(vis, vis + m + 2, 0);
if (DFS(ans)) {
ans++;
} else {
break;
}
}
out[i - 1] = ans;
}
for (int i = 1; i <= d; i++) {
cout << out[i] << '\n';
}
return 0;
}
Luogu P3355 - Knight Coexistence Problem
There is an chessboard, some positions cannot hold pieces. What is the maximum number of knights that can be placed so that no two knights attack each other?
Solution
Observe that the entire board can be colored so that no two squares of the same color are adjacent; then a knight can attack only a square of the opposite color.
Then we can directly solve a maximum independent set problem in a bipartite graph.
Code
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int kD[8][2] = {{-2, -1}, {-1, -2}, {1, -2}, {2, -1},
{2, 1}, {1, 2}, {-1, 2}, {-2, 1}};
int n, m, ib;
int vis[200020], dis[200020], t[200020], r[200020], ans, u, v, cnta,
x[220][220], cl[220][220], idx, bel[220][220];
vector<int> to[200020];
queue<int> q;
int DFS(int x) {
if (vis[x]) {
return false;
}
vis[x] = true;
for (auto i : to[x]) {
if (!r[i]) {
r[i] = x;
t[x] = i;
return true;
}
if (dis[r[i]] == dis[x] + 1 && DFS(r[i])) {
r[i] = x;
t[x] = i;
return true;
}
}
return false;
}
int BFS() {
fill(vis + 1, vis + cnta + 1, false);
fill(dis + 1, dis + cnta + 1, 0);
for (int i = 1; i <= cnta; i++) {
if (!t[i]) {
q.push(i);
dis[i] = 1;
}
}
int f = 0;
for (; q.size(); q.pop()) {
int tmp = q.front();
for (auto i : to[tmp]) {
if (!r[i]) {
f = 1;
}
if (r[i]) {
if (!dis[r[i]]) {
dis[r[i]] = dis[tmp] + 1;
q.push(r[i]);
}
}
}
}
return f;
}
void din() {
for (; BFS();) {
for (int i = 1; i <= cnta; i++) {
if (!t[i] && DFS(i)) {
ans++;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
x[u][v] = true;
}
cl[1][1] = 1;
if (!x[1][1]) {
bel[1][1] = ++idx;
}
for (int i = 2; i <= n; i++) {
cl[1][i] = cl[1][i - 1] ^ 1;
if (!x[1][i] && cl[1][i]) {
bel[1][i] = ++idx;
} else {
if (!x[1][i]) {
bel[1][i] = ++ib;
}
}
}
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cl[i][j] = cl[i - 1][j] ^ 1;
if (!x[i][j] && cl[i][j]) {
bel[i][j] = ++idx;
} else {
if (!x[i][j]) {
bel[i][j] = ++ib;
}
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (!x[i][j]) {
cnta += cl[i][j];
for (int k = 0; k < 8; k++) {
if (i + kD[k][0] > 0 && j + kD[k][1] > 0 &&
bel[i + kD[k][0]][j + kD[k][1]] &&
!x[i + kD[k][0]][j + kD[k][1]]) {
if (cl[i + kD[k][0]][j + kD[k][1]]) {
to[bel[i + kD[k][0]][j + kD[k][1]]].push_back(bel[i][j]);
} else {
to[bel[i][j]].push_back(bel[i + kD[k][0]][j + kD[k][1]]);
}
}
}
}
}
}
din();
cout << n * n - m - ans << '\n';
return 0;
}
Exercises
- Codeforces 1765A - Access Levels
- AtCoder abc274G - Security Camera 3
- Codeforces 1773D - Dominoes
- Luogu P5030 - Long-Necked Deer Placement
- Luogu P2071 - Seat Arrangement
- LibreOJ 6002 - Minimum Path Cover
References
- Kuhn's Algorithm - Maximum Bipartite Matching
- König's Theorem for Maximum Bipartite Matching and Its Proof
- Implementing Dinitz on bipartite graphs by adamant - Codeforces blogs
- Bondy, John Adrian, and Uppaluri Siva Ramachandra Murty. Graph theory with applications. Vol. 290. London: Macmillan, 1976.
- Chen Yinbo. A Brief Discussion of Matching Algorithms and Their Applications. 2015 Collection of Papers by Candidates for the Chinese National Informatics Olympiad Team.
- Dulmage–Mendelsohn decomposition - Wikipedia
- Notes on Dulmage–Mendelsohn decomposition
Footnotes
-
Bast, Holger; Mehlhorn, Kurt; Schäfer, Guido; Tamaki, Hisao (2006), "Matching algorithms are fast in sparse random graphs", Theory of Computing Systems, 39 (1): 3–14. ↩