二分图匹配——从入门到入土
基础知识
形式化定义:
二分图:
可知“图中没有长度为奇数的环”是这个图是二分图的充分必要条件。
图的匹配是一个
图的极大匹配是一个
二分图最大匹配
匈牙利算法(KM 算法)
这个算法很简单。
每一次找到二分图上一个
在找路的过程中搜出来的一棵树叫交错树,树上从
有一种 Dinic 的感觉,但是图没分层 Dinic 假了
单次增广时间复杂度
很简单。
每次增广的时候最坏会遍历整张图,于是时间复杂度就是
总共会增广 轮
还是很简单。
每一次找到一条增广路时答案加一,答案是
把这两个东西乘起来,总共的时间复杂度是
#include <bits/stdc++.h> using namespace std; int n, m, k, t[550], a, b, r[550], id[550], ans, vis[550]; vector<int> to[550]; int DFS(int x) { if(vis[x]) { return 0; } vis[x] = 1; for(auto i : to[x]) { if(!r[i]) { r[i] = x; t[x] = i; return 1; } if(DFS(r[i])) { r[i] = x; t[x] = i; return 1; } } return 0; } void KM() { for(int i = 1; i <= n; i++) { fill(vis + 1, vis + n + 1, 0); ans += DFS(i); } } int main() { for(cin >> n >> m >> k; k--; ) { cin >> a >> b; to[a].push_back(b); } KM(); cout << ans << '\n'; for(int i = 1; i <= n; i++) { if(t[i]) { cout << i << ' ' << t[i] << '\n'; } } return 0; }
Hopcroft-Karp 算法
Dinic 永远的神
其实跟 KM 算法几乎一模一样,只改了一个地方:每一次只增广最短的增广路。
欸?听起来跟 Dinic 很像?
我们比较一下 Dinic 与 Hopcroft-Karp 的流程 买一送一:
Dinic
-
BFS 出层次图;
-
DFS 出阻塞流(层次图的最大流);
-
把阻塞流合并;
-
更新残量网络。
Hopcroft-Karp
-
BFS 出层次图;
-
DFS 出这时候的额外匹配;
-
合并匹配;
-
更新图。
这俩不是一样的吗?
对,对,就是一样的。
所以根据 Dinic 在这种特殊图上的时间复杂度,我们得到——
这个算法的时间复杂度是
稠密图上
稀疏图上
只有
而且Dinic 是“天选之子”时间跑不满......
#include <bits/stdc++.h> using namespace std; int n, m, k, t[100010], a, b, r[100010], id[100010], ans, vis[100010], dis[100010]; vector<int> to[100010]; queue<int> q; //DFS 出额外匹配 int DFS(int x) { if(vis[x]) { return 0; } vis[x] = 1; for(auto i : to[x]) { //更新图 if(!r[i]) { r[i] = x; t[x] = i; return 1; } if(dis[r[i]] == dis[x] + 1 && DFS(r[i])) { r[i] = x; t[x] = i; return 1; } } return 0; } //BFS 出层次图 int BFS() { fill(vis + 1, vis + n + 1, 0); fill(dis + 1, dis + n + 1, 0); for(int i = 1; i <= n; i++) { if(!t[i]) { q.push(i); dis[i] = 1; } } int f = 0; for(; q.size(); q.pop()) { int tmp = q.front(); for(auto i : to[tmp]) { if(!r[i]) { f = 1; } if(r[i]) { if(!dis[r[i]]) { dis[r[i]] = dis[tmp] + 1; q.push(r[i]); } } } } return f; } void dinic() { for(; BFS(); ) { // 合并匹配 for(int i = 1; i <= n; i++) { if((!t[i]) && DFS(i)) { ans++; } } } } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); for(cin >> n >> m >> k; k--; ) { cin >> a >> b; to[a].push_back(b); } cout << ans << '\n'; for(int i = 1; i <= n; i++) { if(t[i]) { cout << i << ' ' << t[i] << '\n'; } } return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析