匈牙利算法

14 篇文章 0 订阅

很早之前就学过最近再温习一下,毕竟匈牙利算法虽然在解决二分图最大匹配问题上复杂度不如Dinic,但编程复杂度低很多,而且不容易写错。

其实如果对网络流有所涉猎,再来学习二分图最大匹配问题就太好理解了。此处默认读者已经了解了二分图最大匹配的问题模型及基本的网络流知识,若对网络流没有了解可以参见我的另一篇博客网络流的核心思想.


二分图问题可以很容易转化成网络流模型,即在原图中添加一源一汇,源点与左部各点相连,容量为 1 1 1,右部各点与汇点相连,容量为 1 1 1。由是我们只需跑一遍最大流即可求得二分图最大匹配。例如对于下面这幅图

1
2
3
4
5
6

mermaid终于画的像个二分图了

我们只需将其转化为这样的网络

1
1
1
1
1
1
1
1
1
1
1
1
0
1
2
3
4
5
6
7

即可在网络上跑最大流求解。而我们经常用到的网络流算法如Dinic其实是基于Ford-Fulkerson的思想即增广路的思想,我们很容易联想,在二分图这样的特殊图上,是否有更简易的找增广路的办法呢?显然是有的,而且特别暴力。我们重新考虑一个的二分图。我们依次考虑左部的点,然后遍历它的邻点,然后寻找一个可匹配点,然后

1
2
3
4
5

这里mermaid把1画到下面了,但我们还是以编号顺序而不是视觉顺序来考虑

首先考虑左部的 1 1 1,我们考虑它的邻点,发现 4 4 4未匹配,直接产生匹配。

1
2
3
4
5

一个点有且只能产生一对匹配,故当匹配生成后,我们直接考虑左部下一个点 2 2 2

2 2 2只与右部的 4 4 4有边相连,但此时 4 4 4已产生匹配, 2 2 2并不能与 4 4 4产生匹配,怎么处理这种情况?如果选择跳过 2 2 2,那么对于后面的 3 3 3也是一样跳过的情况,最终产生的匹配只有 1 1 1对,显然有问题。联想一下之前介绍的网络流模型解决的方法,其实这里我们就是要找增广

寻找增广路其实有一个很简单暴力的方法,就是看当前已匹配的右部的点对应的左部的点的邻点是否还有能匹配的点,如果有那么就让这个左部的点跟新的点匹配,为当前点腾出空间(显然这是一个递归的过程)。

我们具体情况分析一下,现在考虑左部的点 2 2 2,其唯一相连的右部的点 4 4 4已与左部的 1 1 1匹配。按照上面的说法,我们仅需为 1 1 1找一个新的匹配点。我们遍历 1 1 1的所有邻点, 4 4 4被访问了,所以我们找到右部的 5 5 5,发现 5 5 5尚未匹配,我们可以让 1 1 1匹配 5 5 5,再让 2 2 2匹配之前的 4 4 4,以达到增广目的。

1
2
3
4
5

可以自行在更复杂的图上手动尝试这一操作加深理解。显然,每对一个左部的点做上述操作,至多只会增加 1 1 1对匹配。而这一做法其实是基于一种贪心的思想,即每次操作答案都只会增不会减。

//
// Created by Visors on 2020/10/7.
//
// 题目名:P3386 【模板】二分图最大匹配
// 题目来源:luogu
// 题目链接:https://www.luogu.com.cn/problem/P3386
// 算法:匈牙利算法
// 用途:二分图最大匹配
// 时间复杂度:O(nm)
//

#include <bits/stdc++.h>

using namespace std;

int n, m, vertexNum, edgeNum;

struct Edge {
    int to;     // 边终点
    int next;   // 前向弧

    Edge() = default;

    Edge(int to, int next) : to(to), next(next) {}
};

vector<Edge> edges; // 边集
vector<int> heads;  // 首边集
vector<bool> vis;   // 点访问标记
vector<int> match;  // 点匹配记录

inline void addEdge(int u, int v) {
    edges.emplace_back(v, heads[u]);
    heads[u] = edges.size() - 1;
    edges.emplace_back(u, heads[v]);
    heads[v] = edges.size() - 1;
}

bool dfs(int u) {
    for (int i = heads[u], v; ~i; i = edges[i].next) {
        if (!vis[v = edges[i].to]) {
            vis[v] = true;
            // 如果未匹配,则直接产生新匹配达到增广
            // 如果已匹配,尝试DFS找增广路,若找到,则回溯更新新的匹配
            if (!match[v] || dfs(match[v])) {
//                cout << "augment " << v << " from " << match[v] << " to: " << u << endl;
                match[v] = u;
                return true;
            }
        }
    }
    return false;
}

inline int hungary() {
    int max_match = 0;
    for (int i = 1; i <= n; i++) {
        fill(vis.begin(), vis.end(), false);
        vis[i] = true;
        if (dfs(i)) max_match++;
    }
    return max_match;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m >> edgeNum;
    vertexNum = n + m;
    heads.resize(vertexNum + 1, -1);
    vis.resize(vertexNum + 1);
    match.resize(vertexNum + 1);
    for (int i = 1, u, v; i <= edgeNum; i++) {
        cin >> u >> v;
        addEdge(u, v + n);
    }
    cout << hungary() << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值