一想到二分图最大匹配,我就不禁想起那几个少年抢女朋友的故事,所以今天我就来说一下二分图匹配的解决方法——匈牙利算法。
例题
洛谷3386 【模板】二分图匹配
题目描述
给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数。
输入格式
第一行,n, m, e。
第二至(e + 1)行,每行两个正整数u, v,表示u, v有一条连边。
输出格式
共一行,二分图最大匹配。
输入输出样例
输入
1 1 1
1 1
输出
1
说明/提示
1 <= n, m <= 1000, 1 <= e <= n * m。
因为数据有坑,可能会遇到 v>m 或者 u>n 的情况,所以请把 v>m 或者 u>n 的数据自觉过滤掉。
二分图最大匹配——匈牙利算法
先说最大匹配是什么:二分图最大匹配其实就是在已有边中选取尽量多的边,但得保证每个点最多出现一次。那么最大匹配数也就是能选取的最多边数,这里为了方便理解我们可以想象成n个男生去追m个女生,每个男生如果说对一个女生有好感(二者间有一条边),那么他们就有希望在一起,但是因为只能一夫一妻,所以我们希望能组成尽量多的情侣,这就是二分图最大匹配。举个例子,下图就是一个二分图的最大匹配,其中红边就是匹配边。
这里先大致说一下匈牙利算法的实现:匈牙利算法就是每次从左侧的一个点去找增广路,之后找到一条增广路径后,就将这条增广路径中所有原来的选中路径变为不选中,而原来不选中的路径改为选中。
这里具体地解释说明一下,算法主要需要维护两个数组,我就叫opp数组和vis数组了。opp[i]表示右侧点i所连接的左侧点,就是女生i当前跟哪个男生好。vis[i]表示左侧点i是否被访问过,就是男生i是否已经找到对象。
之后我们去枚举男生x,每次我们去选一个他感兴趣的女生,如果这个女生尚未有男生匹配,就让他俩在一起。或者如果这个女生有男生x0匹配了,那么我们就让男生x去跟当前女生匹配的这个男生x0抢,如果说当前女生匹配的男生x0还能去跟其他女生在一起,我们就让女生和当前男生x在一起,让原来女生匹配的男生x0去跟其他女生匹配。但如果说男生x0只能和这一个女生匹配,那么对不起了男生x,你只能去找其他你喜欢的女性。这里对于每个男生找女生的代码我就用递归来写了。
bool find(int x) // 返回男生x是否可以找到心仪的女生
{
if (vis[x]) return false; // 如果已经枚举过了就不用再找了
vis[x] = true; // 标记
for (int i = 0; i < (int) g[x].size(); i++) { // 枚举男生感兴趣的女生
int y = g[x][i]; // 获取女生y
if (opp[y] == 0 /*女生未被任何人匹配*/ || find(opp[y]) /*当前女生匹配的男生还能找到其他女生*/) {
opp[y] = x; // 使女生选择男生x
return true; // 返回男生x找到了心仪的女生
}
}
return false; // 返回男生x是个单身狗
}
这里要务必记住每次从一个男生开始找时,要将vis数组清零。还有此题有个坑,即输入的x可能大于n,输入的y也可能大于m,你省略这些不合理的数据就行了。
最后算一下算法的时间复杂度:先得枚举男生n次,之后每次还要调用find函数,其中find最多跑m次,所以总时间复杂度为O(n * m)。
代码
# include <cstdio>
# include <algorithm>
# include <cmath>
# include <cstring>
# include <vector>
using namespace std;
const int N_MAX = 1000, E_MAX = 1000000;
int n, m, e;
vector <int> g[N_MAX + 10];
int opp[N_MAX + 10];
bool vis[N_MAX + 10];
void addEdge(int x, int y)
{
g[x].push_back(y);
}
bool find(int x)
{
if (vis[x]) return false;
vis[x] = true;
for (int i = 0; i < (int) g[x].size(); i++) {
int y = g[x][i];
if (opp[y] == 0 || find(opp[y])) {
opp[y] = x;
return true;
}
}
return false;
}
int hungary()
{
memset(opp, 0, sizeof(opp));
int ans = 0;
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
ans += find(i);
}
return ans;
}
int main()
{
scanf("%d%d%d", &n, &m, &e);
for (int i = 1; i <= e; i++) {
int x, y;
scanf("%d%d", &x, &y);
if (x > n || y > m) continue;
addEdge(x, y);
}
printf("%d\n", hungary());
return 0;
}