匈牙利算法【匹配问题】

匈牙利算法:

匈牙利算法几乎是二分图匹配的核心算法,除了二分图多重匹配外均可使用

匈牙利算法实际上就是一种网络流的思想,其核心就是寻找增广路。具体操作就是嗯。。拉郎配

 

匈牙利算法适用的场合:

  1. 二分图匹配
  2. 链上的匹配问题
  3. 一般图匹配等其他点的个数较小的匹配问题

 

举个以前写的博客的例子

 

通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(-_-||暂时不考虑特殊的性取向),如果一对男女互有好感,那么你就可以把这一对撮合在一起,现在让我们无视掉所有的单相思(好忧伤的感觉),你拥有的大概就是下面这样一张关系图,每一条连线都表示互有好感。

 

本着救人一命,胜造七级浮屠的原则,你想要尽可能地撮合更多的情侣,匈牙利算法的工作模式会教你这样做:

===============================================================================

一: 先试着给1号男生找妹子,发现第一个和他相连的1号女生还名花无主,got it,连上一条蓝线

 

===============================================================================

:接着给2号男生找妹子,发现第一个和他相连的2号女生名花无主,got it

 

===============================================================================

:接下来是3号男生,很遗憾1号女生已经有主了,怎么办呢?

我们试着给之前1号女生匹配的男生(也就是1号男生)另外分配一个妹子。

 

(黄色表示这条边被临时拆掉)

 

与1号男生相连的第二个女生是2号女生,但是2号女生也有主了,怎么办呢?我们再试着给2号女生的原配重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)

 

 

此时发现2号男生还能找到3号女生,那么之前的问题迎刃而解了,回溯回去

 

2号男生可以找3号妹子~~~                  1号男生可以找2号妹子了~~~                3号男生可以找1号妹子

所以第三步最后的结果就是:

 

===============================================================================

: 接下来是4号男生,很遗憾,按照第三步的节奏我们没法给4号男生腾出来一个妹子,我们实在是无能为力了……香吉士同学走好。

 

===============================================================================

这就是匈牙利算法的流程,其中找妹子是个递归的过程,最最关键的字就是“腾”字

其原则大概是:有机会上,没机会创造机会也要上

 

算法步骤(基于二分图)

  1. 如果题目初始的时候给出的是一副一般图,我们可以利用拓扑染色(2-SAT、种类并查集等等)的方式来让他变成一个二分图。
  2. 将二分图分成X部和Y部,我们现在的做法是通过X部去匹配Y部;
  3. 我们从1~cnt_X的去枚举,如果没有被对面的Y给匹配过的,我们试着让他去匹配对应的有链接边的Y部,如果对面的Y部对应的点y是没有被匹配的,或者链接对应y的x是可以换一个没有被匹配过的点的,那么我们就让x去换,并且让我们现在枚举的1~cnt_X去链接对应的点
  4. 在算法上的优化,我们不难发现,如果目前的1~cnt_Y中的某个y在这一回合的匹配中是被利用过了的,那么也就是说,后面的点都是已经被操作过(且为false的),说明我们不用再继续往下跑了,所以可以利用一个vis标记来进行优化。在枚举x的1~cnt_X的同时,我们进入“腾”这个操作的时候,就是需要去对1~cnt_Y进行vis清空操作。

 

算法步骤(基于链)

  1. 链上的匈牙利算法的算法结构并不特殊,只是对于建边的时候需要慎重考虑。
  2. 它是在一条链上的匹配,我们当去寻求匹配对象的时候,我们要尽可能的先把前面的link[x]覆盖,不然当我们匹配到后面的时候却已经被覆盖了,不是最优解。
  3. 所以,我们的建边尽可能是要从后面的结点指向前面的结点,例如i -> j(i > j)
  4. 然后,我们1~N的枚举,当前的点一定是没有被匹配过的,因为操作3保证了。所以,我们直接去寻求匹配即可。

 

例题讲解(不按难度顺序排列)

 


Oil Skimming

 HDU - 4185

  这道题的精髓在于,i点和j点是成对匹配的。也就是说,当我们匹配上j点的时候,事实上,i点同时被j点匹配,所以我们一次操作的时候是需要赋值两次。

            link[v] = u;
            link[u] = v;

也就是这样。

My Code


Taxi Cab Scheme

 HDU - 1350

  这道题就是一道很模版的匈牙利算法在链上的操作了。

  有N个人,他们在特定的时间(hour : minute)要从地点(a, b)前往地点(c, d),然后呢,我们如果说前一个人i的出租车可以开回来再接j这个人,那么他们就可以少叫一辆出租车了。现在,题目想知道最少的需求的出租车的个数。

图文讲解 + 代码


过山车

 HDU - 2063

  匈牙利算法的模板题,这里给出主要的结构。理清楚,我们在哪个位置要清空X的内容,在哪个位置是要去清空Y部的内容,我这里并没有去用拆点或者使用memset()来逃避这两个问题。

bool match(int u)
{
    for(int i=head[u], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(vis[v]) continue;
        vis[v] = true;
        if(!link[v] || match(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}
inline int Hungery()
{
    int ans = 0;
    for(int i=1; i<=M; i++) link[i] = 0;
    for(int i=1; i<=N; i++)
    {
        for(int j=1; j<=M; j++) vis[j] = false;
        if(match(i)) ans++;
    }
    return ans;
}

这两行是主要的代码,要仔细看清哪里用的是X,哪里用的是Y。(N对应X,M对应Y)


棋盘游戏

 HDU - 1281

  看着题意很复杂,但是我们可以简化一下。

  不能横竖给交叉了,实际上指的是每个对应的x坐标对应唯一的y坐标,我们基于这点去对于二维平面进行匈牙利算法即可。复杂度是O(N^{2} * K),其中的O(N^{2})源于匈牙利算法的复杂度。

  判断是否是重要结点,可以自己想想看。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
#define MP(a, b) make_pair(a, b)
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 105;
int N, M, K, head[maxN], cnt, cn_X, cn_Y;
vector<pair<int, int>> vt;
struct Eddge
{
    int nex, to;
    Eddge(int a=-1, int b=0):nex(a), to(b) {}
}edge[maxN * maxN];
inline void addEddge(int u, int v)
{
    edge[cnt] = Eddge(head[u], v);
    head[u] = cnt++;
}
int link[maxN];
bool vis[maxN];
inline bool match(int u)
{
    for(int i=head[u], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(u == cn_X && v == cn_Y) continue;
        if(vis[v]) continue;
        vis[v] = true;
        if(!link[v] || match(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}
inline int Hungery()
{
    int ans = 0;
    for(int i=1; i<=M; i++) link[i] = 0;
    for(int i=1; i<=N; i++)
    {
        for(int j=1; j<=M; j++) vis[j] = false;
        if(match(i)) ans++;
    }
    return ans;
}
inline void init()
{
    cnt = 0; cn_X = cn_Y = 0; vt.clear();
    for(int i=1; i<=N; i++) head[i] = -1;
}
int main()
{
    int Cas = 0;
    while(scanf("%d%d%d", &N, &M, &K) != EOF)
    {
        init();
        for(int i=1, u, v; i<=K; i++)
        {
            scanf("%d%d", &u, &v);
            addEddge(u, v);
            vt.push_back(MP(u, v));
        }
        int const_ans = Hungery(), Important = 0;
        for(int i=0; i<K; i++)
        {
            cn_X = vt[i].first; cn_Y = vt[i].second;
            if(Hungery() < const_ans) Important++;
        }
        printf("Board %d have %d important blanks for %d chessmen.\n", ++Cas, Important, const_ans);
    }
    return 0;
}
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值