P3386 【模板】二分图最大匹配
这是一道经典的二分图最大匹配模板题。解决这类问题的标准算法是匈牙利算法(Hungarian Algorithm)。
1. 基本概念
- 二分图:图中的顶点可以分为两个互不相交的集合(左部 \(L\) 和右部 \(R\)),图中的每条边所连接的两个顶点分别属于这两个不同的集合。
- 匹配:在二分图中,一个边的集合,其中任意两条边都没有公共端点。
- 最大匹配:边数最多的那个匹配。
2. 核心算法:匈牙利算法
匈牙利算法的核心思想是寻找增广路(Augmenting Path)。
算法步骤:
- 从左部点 \(1\) 到 \(n\) 依次尝试为每个点 \(u\) 寻找匹配。
- 对于点 \(u\),遍历它所有连接的右部点 \(v\):
- 如果 \(v\) 还没有被匹配,则直接将 \(u\) 和 \(v\) 匹配。
- 如果 \(v\) 已经和某个点 \(p\) 匹配了,我们就尝试为 \(p\) “腾位子”——即递归地看 \(p\) 能不能改选其他的右部点。
- 如果成功“腾位子”或 \(v\) 原本就空闲,则匹配成功,总匹配数 \(+1\)。
- 注意点:为了防止在一次 DFS 中陷入死循环,需要使用
vis数组标记当前路径中已经访问过的右部点。
3. 代码实现 (C++)
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 505;
vector<int> adj[MAXN]; // 邻接表,只存储从左部点指向右部点的边
int match[MAXN]; // match[v] 表示右部点 v 当前匹配的左部点编号
bool vis[MAXN]; // 标记右部点在单次寻找增广路中是否被访问过
int n, m, e;
bool dfs(int u) {
for (int v : adj[u]) {
if (!vis[v]) {
vis[v] = true; // 标记该右部点已尝试
// 如果 v 未匹配,或者能给 v 原配的左部点 match[v] 找到新欢
if (match[v] == 0 || dfs(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> e;
for (int i = 0; i < e; ++i) {
int u, v;
cin >> u >> v;
// 题目给出的 v 是右部点编号,直接存储即可
adj[u].push_back(v);
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, false, sizeof(vis)); // 每次为新点找匹配时,都要重置标记
if (dfs(i)) {
ans++;
}
}
cout << ans << endl;
return 0;
}
4. 关键细节解析
-
重边处理:题目提示不保证没有重边。在匈牙利算法中,重边不影响结果的正确性,因为
dfs会遍历所有边,重边只是多了一次尝试机会,不会导致错误。 -
复杂度:
- 时间复杂度为 \(O(n \cdot e)\)。在本题中 \(500 \times 50000 = 2.5 \times 10^7\),在 1s 的时限内绰绰有余。
- 空间复杂度为 \(O(n + m + e)\),主要由邻接表占用。
-
为什么
vis数组要重置?每当我们尝试为一个新的左部点 \(i\) 寻找匹配时,
vis记录的是在“为 \(i\) 找匹配”的过程中,哪些右部点已经被考虑过了,防止 DFS 递归中出现死循环。

浙公网安备 33010602011771号