欢迎大家访问我的博客:博客传送门
二分图的最大匹配
题目描述
二分图的最大匹配
从题目中给出的二分图匹配的定义可知,匹配其实也是一张图,并且图中的点数是边数的2倍。
如下图所示:二分图的匹配
红色边组成的 4 个结点、2条边就是这个二分图的一个匹配了;
二分图最大匹配定义:最大匹配就是找到一个子图,满足是匹配,并且边数(点数)最多。
如下图所示:二分图的最大匹配
匈牙利算法
匈牙利算法可以用来求解二分图的最大匹配问题。匈牙利算法也被戏称为“相亲算法”,它的大致过程是这样的:
-
有两个点集,分别是男生和女生, X X X点集中全部都是男生, Y Y Y点集中全部都是女生
-
然后男生取追求女生,如果男生 X a X_a Xa对女生 Y b Y_b Yb有好感,并且女生 Y b Y_b Yb也有男生 X a X_a Xa有好感,那么男生 X a X_a Xa就有很大机会可以追到女生 Y b Y_b Yb,否则如果女生 Y b Y_b Yb不喜欢男生 X a X_a Xa,那么男生 X a X_a Xa就不会再追求女生 Y b Y_b Yb,而是继续去寻找其他女生 (不想当
舔狗) -
作为一个男人,要主动出击。因此约定都是男生追女生,即开始先从点集 X X X出发
-
每个男生可以选遍所有他喜欢的女生:如果某位女生还是单身并且没有其他男生追求她,那么 女人 拿来吧你,恭喜你,直接配对;如果特别喜欢的这个女生已经有男朋友了,则这个女生会让她男朋友再去找其他女生(爱情就是这么
不堪一击),又转化成了另一个男生去寻找女生的过程,继续递归找; -
那么最终的结果就有两种情况:
-
由于这个男生的插足,导致最后可能有一个男生被抛弃,这是不道德的行为,所以不能干这种事情!(如下图,结局好惨)
-
由于这个男人的插足,导致所有的男女关系都进行了一次轮换,但是匹配的对数多了一对;(成功
横刀夺爱)
-
以上就是匈牙利算法的相亲过程,算法很简单也很有趣,教会了恋爱丛林法则
匈牙利算法实现
先来给出以下变量的定义:
st[j]
这个是个bool类型数组,用来表示某个女生有没有被男生访问联系过,如果 s t [ j ] = t r u e st[j]=true st[j]=true,表示 j j j这个女生已经某个男生联系过了match[j]
是个int类型的数组,用来表示 j j j这个女生她现任男友的编号,如果 m a t c h [ j ] = a match[j]=a match[j]=a,表示 j j j这个女生她现任男友的编号为 a a a
算法流程:
- 将男生和女生分成点集 X X X和 Y Y Y
- 由于是男生掌握主动权,所以只需要建立有向图,从男生节点向女生节点引出有向边即可
- 从男生点集
X
X
X出发,假设某个男生
u
u
u,遍历
u
u
u这个节点所有的邻接点
v
v
v(即他喜欢的所有女生)
- 如果这个女生 v v v处于单身状态, v v v还没有匹配,那么此时 ( u , v ) (u,v) (u,v)就是一组成功的匹配(在一起了),所以匹配的数量+1
- 如果这个女生
v
v
v虽然有男朋友了,但是她可能对这个男生
u
u
u有点意思,所以让她现任男友
c
c
c去寻找其他女生,假设男生
c
c
c是个
海王(本来就是),他确实有很多青青草原,然后他找到了后宫中的女生 d d d,最终女生 d d d答应了他,于是他俩就在一起了,那么此时女生 v v v就处于单身状态,于是男生 u u u就成功和她在一起了,可以发现此时又成功匹配了一对情侣,所以匹配数目+1;但是如果后宫中的女生 d d d知道男生 c c c是海王,拒绝了他,那么男生 c c c和她女友 v v v说 我还是只爱你一人,因此他俩还是继续在一起,那么很可惜男生 u u u还是单身狗。因此这次 u u u的匹配失败。
代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;//n1表示左侧男生的个数 n2表示右侧女生的个数
int h[N], e[M], ne[M], idx;
//match[j]=a,表示女孩j的现有配对男友是a
int match[N];//右边女生所对应的左边男孩,即女生现在和哪个男生的在一起
bool st[N];//临时预定数组,st[j]=a表示一轮模拟匹配中,女孩j被男孩a预定了。 避免重复搜索女孩
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
//判断x这个男生能不能找到匹配的女生
bool find(int x)
{
//依次遍历自己喜欢的女孩
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];//获取女孩在图中的顶点的编号
//如果j这个女孩之前还没有被考虑过,在这一轮模拟匹配中,这个女孩尚未被预定
if (!st[j])//j这个女孩还没有被男生x访问过
{
//标记j这个女孩被男生x访问过了
st[j] = true;//表示j这个女孩已经被x男孩看上了,被考虑过啦,x男孩已经预定了j这个女孩
//match[j]==0表示j这个女孩还没有匹配任何男孩,即j女孩还没有男朋友 j目前没有任何可以匹配的节点
//find(match[j])=true表示j这个女孩虽然已经匹配了某个男孩,但是她匹配的这个男孩是个海王
//留有后手随时可以找到其他女孩来代替她,那么此时j女孩被甩处于单身状态,j告诉x说我们在一起吧
//那么执着追j女孩的这个x男子就击败了j女孩的所有暗恋者,有机会了
//也就是说j有和它匹配的点(现任男友),但是呢这个点(现任男友)能够通过一些办法来找到新的匹配
if (match[j] == 0 || find(match[j]))
{
match[j] = x;//表示j女孩已经匹配成功了,现有配对男友是x
return true;//返回true表示配对成功
}
}
}
//如果遍历过x男子所喜欢的女生中,都没能找到合适的,自己中意的全部都被预定了。
//配对失败。即没有配对成功,则返回false
return false;
}
int main()
{
scanf("%d%d%d", &n1, &n2, &m);
memset(h, -1, sizeof h);
while (m--)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);//虽然是无向图,但是因为是左侧男生去找女朋友,单向追求,所以用“有向图”就行
}
int res = 0;//res表示匹配的数量
//依次枚举左半部分的男生该去找哪个女孩
for (int i = 1; i <= n1; i++)
{
//要保证每个女孩只被考虑一遍
//因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
//如果不初始化则当男生i进行匹配操作后会修改st的内容 那么会保留这个内容
//当下一个男生i+1进行匹配操作时 就会用到上一个男生i保留的st的内容 就会出错
memset(st, false, sizeof st);//把每个女孩的匹配状态清空,表示这些女孩还没有被考虑过
//如果i这个男子成功的找到了匹配的妹子 那么匹配个数+1
if (find(i))
res++;
}
printf("%d\n", res);
return 0;
}