第一次看到这个题目时,我正坐在电脑前啃着算法书。HDU2063"过山车"乍看像个娱乐项目,实则是道经典的二分图匹配问题。题目描述很简单:有M个女生和N个男生想坐过山车,但每排只能坐一男一女,且只有相互有好感的组合才能同坐。我们需要找出最多能有多少对组合成功上车。
这场景让我想起大学时的联谊活动。组织者总要费尽心思把互有好感的同学安排在一起,而匈牙利算法就是解决这类"配对问题"的绝佳工具。它由匈牙利数学家Dénes Kőnig在1931年提出,时间复杂度为O(V*E),特别适合处理这类二分图最大匹配问题。
匈牙利算法的精妙之处在于它的"腾挪"策略。想象你是个红娘,手头有几对互有好感的男女。当你想给新人配对时,如果发现心仪对象已被占用,不是直接放弃,而是尝试让被占用的那位另寻新欢。用专业术语说,就是不断寻找"增广路径"。
具体步骤可以拆解为:
增广路径是算法的核心概念。它是指一条起点和终点都是未匹配点,且匹配边和非匹配边交替出现的路径。例如路径A-B-C-D中,A和D未匹配,A-B是匹配边,B-C是非匹配边,C-D是匹配边。
找到这样的路径后,我们可以通过"取反"操作增加匹配数:把路径上的匹配边变为非匹配边,非匹配边变为匹配边。这样操作后,匹配数就会增加1。
对于过山车这道题,我推荐使用邻接表存储二分图。具体可以这样定义:
cpp复制const int MAXN = 510;
vector<int> G[MAXN]; // 邻接表存图
int match[MAXN]; // 记录匹配结果
bool used[MAXN]; // 标记是否被访问
其中G数组存储女生到男生的好感关系,match数组记录每个男生的匹配对象,used数组用于DFS时的访问标记。
递归实现是最直观的方式。核心函数如下:
cpp复制bool dfs(int v) {
used[v] = true;
for (int i = 0; i < G[v].size(); ++i) {
int u = G[v][i];
int w = match[u];
if (w < 0 || (!used[w] && dfs(w))) {
match[u] = v;
return true;
}
}
return false;
}
这个函数尝试为女生v寻找匹配。它会遍历所有v有好感的男生u:
对于大规模数据,DFS可能栈溢出。这时可以用BFS实现:
cpp复制bool bfs(int v) {
queue<int> q;
q.push(v);
used[v] = true;
while (!q.empty()) {
int v = q.front();
q.pop();
for (int u : G[v]) {
int w = match[u];
if (w < 0) {
// 找到增广路径
return true;
}
if (!used[w]) {
used[w] = true;
q.push(w);
}
}
}
return false;
}
BFS版本通过队列避免了递归深度问题,更适合处理大规模图。
结合题目要求,完整AC代码可以这样写:
cpp复制#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 510;
vector<int> G[MAXN];
int match[MAXN];
bool used[MAXN];
bool dfs(int v) {
used[v] = true;
for (int u : G[v]) {
int w = match[u];
if (w < 0 || (!used[w] && dfs(w))) {
match[u] = v;
return true;
}
}
return false;
}
int main() {
int K, M, N;
while (cin >> K && K) {
cin >> M >> N;
// 初始化
for (int i = 1; i <= M; ++i) G[i].clear();
memset(match, -1, sizeof(match));
// 读入数据
for (int i = 0; i < K; ++i) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
}
// 匈牙利算法
int res = 0;
for (int v = 1; v <= M; ++v) {
memset(used, 0, sizeof(used));
if (dfs(v)) ++res;
}
cout << res << endl;
}
return 0;
}
最坏情况下,算法需要遍历所有边,对每个顶点执行DFS/BFS,因此时间复杂度为O(V*E)。对于过山车这道题,V和E都在500以内,完全在可接受范围。
匈牙利算法不仅用于解题,在现实中有广泛应用:
匈牙利算法是解决二分图匹配的特化算法,比通用最大流算法更高效。如果用Dinic算法解最大流,时间复杂度是O(E√V),而匈牙利算法是O(V*E)。对于稀疏图,匈牙利算法往往更快。
KM算法用于带权二分图的最大权匹配,时间复杂度O(V^3)。如果只需要最大匹配而不考虑权重,匈牙利算法是更好的选择。
在实际编码中,我发现几个值得注意的点:
一个容易忽略的细节是:题目中女生编号从1开始,男生也是从1开始。所以在初始化时,循环要从1开始而不是0。这种边界条件在实际比赛中经常成为失分点。