最近刚学了二分图匹配,发篇博客分享一下。
要了解二分图匹配首先要知道二分图是什么:
“设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。”
摘自百度百科
用人话来说就是把一个图中的点分成两个集合,然后一个集合中的点只能和另一个集合中的点连边,这样的一个图就是二分图。
不要嫌烦,我们再来看看一些相关名词:
匹配:再图中选择若干个边,任意两个边没有公共顶点。
最大匹配:最大的匹配方案。
那么什么是二分图匹配呢?顾名思义就是在一个二分图中寻找他的最大匹配。
知道了二分图匹配的定义,我们接下来用一个生(wu)动(liao)的例子来理解二分图匹配的应用以及算法流程:
在遥远的未来,拥有世界上大部分财富的人同时拥有了这世界上大部分的女人,这导致大部分正常男人根本就没有伴侣。
政府为了解决这个问题,取消了原有的婚姻制度,取而代之的是一个强制性的婚姻制度。在这个婚姻制度中,一旦年龄超过18岁,公民便会被强制匹配一个伴侣,必须与其共度余生。而政府为了公民的婚姻更加和谐,会事先评估每个人的性格,然后尽可能将性格合适的匹配在一块。然而不一定能让所以人都找到性格合适的伴侣,这时就需要“逼婚员”的努力,让大家的婚姻尽可能的和谐。
逼婚员今天的任务可以用下面这张丑陋的图表示:
(方形点表示男生,椭圆点表示女生,边表示他们性格合适)
逼婚员都收过专业的训练,他们匹配都有特殊的方法。
让我们看看逼婚员怎么处理今天的任务吧。首先他把男生A和女生A匹配在了一块。
然后同时又看到男生B和女生A也匹配,而且男生A除了和女生A匹配,还和女生B匹配,于是就把女生B匹配给了男生A,女生A匹配给男生B。
剩下三个直接匹配就完事了。。。
通过上面的例子可以看到匈牙利算法的思想非常暴力,就是对于个边,能连就直接连,不能连就尝试让之前的点给当前点腾出来一个点。
现在我们来看看代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, m, e, ans = 0,
link[N]; bool vis[N];//link[v]表示v连向的点, vis表示某个点是否被访问过。
vector<int> g[4*N];//vector存图
//快读
inline int read() {
register int x = 0, t = 1; register char ch = getchar();
while ((ch < '0' || ch > '9') && ch != '-')ch = getchar();
if (ch=='-') { t = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9'){ x = (x<<3) + (x<<1) + (ch^48); ch = getchar(); }
return x*t;
}
//算法核心
bool dfs(int x) {
for (int i = 0; i < g[x].size(); i++) {
int v = g[x][i];
//如果没被访问
if (!vis[v]) {
vis[v] = 1;
if (link[v] == -1 || dfs(link[v])) { //若是v还没有被配对,就把v配对给x,否则让link[v]腾出v给它。
link[v] = x; //把v连接到x
return 1; //表示x能配对到点
}
}
} return 0; //x不能配对到点
}
int main() {
memset(link, -1, sizeof(link));
n = read(), m = read(), e = read();
for (int i = 1; i <= e; i++) {
int u = read(), v = read();
if (u > n || v > m || u < 1 || v < 1) continue;
g[u].push_back(v+n); //建边,注意一定要是单向边
} for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
if (dfs(i)) ans++; //如果能匹配到答案加一
} cout << ans;
return 0;
}