二分图匹配——从入门到入土

基础知识

形式化定义:

二分图:G=(L,R,E),满足 (u,v)E 都有 uL,vRuR,vL

可知“图中没有长度为奇数的环”是这个图是二分图的充分必要条件。

图的匹配是一个 EmE 满足 iV(G是二分图时V=LR) 满足 (u,v),(w,x)Em[u=i]+[v=i]+[w=i]+[x=i]>1

图的极大匹配是一个 EME 满足 iE  EM 满足 EMi 也是一个匹配。

二分图最大匹配

匈牙利算法(KM 算法)

这个算法很简单。

每一次找到二分图上一个 uLvR 并且 v 没有被匹配的路径,把答案加一。

在找路的过程中搜出来的一棵树叫交错树,树上从 u 开始的路径叫一条交错路,成功找到的;一条路径叫增广路。

有一种 Dinic 的感觉,但是图没分层 Dinic 假了

单次增广时间复杂度 O(|V|+|E|)

很简单。

每次增广的时候最坏会遍历整张图,于是时间复杂度就是 O(|V|+|E|)

总共会增广 O(|V|)

还是很简单。

每一次找到一条增广路时答案加一,答案是 O(|V|) 的,所以增广轮数就是 O(|V|) 的。

把这两个东西乘起来,总共的时间复杂度是 O(|V|(|V|+|E|)) 的。

#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
  1. BFS 出层次图;

  2. DFS 出阻塞流(层次图的最大流);

  3. 把阻塞流合并;

  4. 更新残量网络。

Hopcroft-Karp
  1. BFS 出层次图;

  2. DFS 出这时候的额外匹配;

  3. 合并匹配;

  4. 更新图。

这俩不是一样的吗?

对,对,就是一样的。

所以根据 Dinic 在这种特殊图上的时间复杂度,我们得到——

这个算法的时间复杂度是 O(|E|min(|E|12,|V|12)) 的。

稠密图上 |E|=O(|V|2),所以是大概 O(|V|3) 的;

稀疏图上 |E|=O(|V|),所以是大概 O(|E||V|12) 的。

只有 |V|=2000,|E|=4×106 才能卡掉。

而且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;
}
posted @   hhc0001  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示