专栏文章
【Note】二分图最大匹配 - boy and girl(P3386 题解)
P3386题解参与者 5已保存评论 9
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 9 条
- 当前快照
- 1 份
- 快照标识符
- @mioyh6dn
- 此快照首次捕获于
- 2025/12/03 03:13 3 个月前
- 此快照最后确认于
- 2025/12/03 03:13 3 个月前
本文介绍匈牙利算法,博客食用更佳。
引入
一个无向图 , 中所有点可以被分为两个点集 和 。 中任意边均满足:该边所连两点分别属于 和 。这样的图就叫二分图。
二分图最大匹配问题就是:选出一个边集 ,使任意一点所连的边中只有一条属于 ,问 中最多可以有多少边?
很难看对吧,换个大家喜闻乐见的方式
目前有 个男孩和 个女孩,其中只能男女配对,男孩女孩间互有好感。
目前你已经知道那些男孩和女孩互有好感,你的任务是配对出尽量多的搭档。(对!仅仅是搭档!)
算法介绍
具体的,若男孩 和女孩 互有好感,就建一条连接 和 的边。于是,我们就能拿到一张关系表二分图。接下来枚举每个男孩(强制 ,可以优化常数),让男孩(设他编号为 )进入找搭档流程(即在二分图上跑如下流程的 DFS):
- 枚举和这个男孩互有好感的女孩。
- 如果该女孩还没被配对,那就组合成功。
- 如果该女孩已经被配对了,那就把她之前的搭档拆掉(惊)!让之前和她搭档的男孩(不妨设他编号为 )重新找一个搭档(即男孩 进入找搭档流程)。
- 如果男孩 找到了新搭档,就把男孩 和当前枚举的女孩配对。
- 如果男孩 没找到新搭档,就让男孩 考虑下一个和他互有好感的女孩。
- 如果男孩 枚举完所有和他互有好感的女孩后都没戏,那他就只能和中国剩余定理(中国单身狗定理)殊途同归,成功落单了。
正确性及时间复杂度证明
Berge 定理
我们在二分图上找一条路径,若路径由选中的边(即该边所连的男孩和女孩决定配对)和没选中的边交替组成,且第一条边和最后一条边均未被选,我们称该路径为增广路径。容易发现,我们把任意增广路上的边的被选状态反转(即之前没选的选上,之前选了的又不选了),方案依然合法,且被选中的边数还比之前多一条(即多了一对搭档)。
由此可知,存在增广路径就代表还有更优方案,最终我们就得到了 Berge 定理:一个匹配是最大匹配当且仅当图中不存在增广路径。
而匈牙利算法为每个男孩尝试寻找增广路径:若找到,匹配数增加。若找不到,当前匹配数已最大化。综上,可证匈牙利算法的正确性。
设 为左侧节点数(男孩数,较小侧), 为右侧节点数(女孩数), 为总边数。
单次 DFS 的复杂度:DFS 遍历 的所有出边(即好感女孩),并通过维护一个布尔型数组确保每个女孩在单次 DFS 中仅被访问一次,每条边最多被访问一次(当检查女孩 时)。最坏情况下,单次的 DFS 时间复杂度为 (需遍历所有边)。
遍历所有 个男孩,对每个男孩跑 DFS,执行 次。每次 DFS 最坏为 。所以,总时间复杂度为 。
代码实现
注释里已经讲的很清楚了,自己看代码吧。
CPP#include<bits/stdc++.h>
using namespace std;
int n,m,E,mat[1003],ans;
//mat[i]:和编号为i的girl配对的boy的编号
bool vis[1003],F;
//vis:编号为i的girl在这一轮有(true)没有(false)找过boyfriend
vector<int> e[502];//存边:only boy->girl
//将题意转化为:
//有n个男孩,m个女孩和E个边
//若1个男孩和1个女孩互有好感,可以配对,则称为有1个边
//问最多能组成多少对lovers(a boy and a girl)
//编号为i的男孩找girlfriend,找到返回true,没找到返回false
bool f(int u){
for(int v:e[u]) if(!vis[v]){
// 枚举和编号为u的男孩互有好感且本轮还没找过boyfriend的女孩
vis[v]=true;//编号为i的girl找过boyfriend了
if(mat[v]==0||f(mat[v])){//可否配对
mat[v]=u;
return true;
}
}
return false;
}
int main(){
scanf("%d%d%d",&n,&m,&E);
if(n>m) swap(n,m),F=true;//强制定义人少的那一方是男孩
for(int i=1,u,v;i<=E;i++){
scanf("%d%d",&u,&v);
if(F) swap(v,u);
// 如故男孩是因为强制转换变为人少的那一方,那这里也要转换
e[u].push_back(v+n);
// 防止boy和girl编号重复,给girl的编号加上n
}
for(int i=1;i<=n;i++){//枚举每一个男孩
memset(vis,0,sizeof(vis));
if(f(i)) ans++;//若编号为i的男孩配对成功了,那么ans加1
}
printf("%d",ans);
return 0;
}
相关推荐
评论
共 9 条评论,欢迎与作者交流。
正在加载评论...