1

二分图最大匹配之匈牙利算法

如此之菜的笔者现在才学会

板子题 - Luogu P3386 【模板】二分图最大匹配
挂个链接 - https://www.luogu.com.cn/prob...
题面 - 给定一个二分图,其左部点的个数为n,右部点的个数为m,边数为 e,求其最大匹配的边数

Now,让我们开始学习匈牙利算法

匈牙利是用增广路求最大匹配的算法
增广路的定义,若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径,M为G的最大匹配当且仅当不存在M的增广路径

红红火火恍恍惚惚
你是不是很懵呀,是不是感觉如此高大上,其实非常简单,笔者是故意说的这么复杂的,换一种说法你就明白了

笔者就用牛和牛栏来解释吧(其实本来想用撮合情侣来解释的)

现在有n头牛,m个牛栏,牛只能在它喜欢的牛栏中且一个牛栏一头牛,告诉你每头牛喜欢哪些牛栏,问你最多能安排多少对牛和牛栏

要你干什么你明白了吧,现在就是怎么做的问题了

现在开讲匈牙利算法(这次是正经的)

举个栗子吧,这样会清晰一些

图片.png

这是题目给你的条件

下面就要利用这些条件进行匈牙利算法了

先看到牛1,去找它喜欢的牛栏有没有空着的
发现它喜欢的牛栏1空着,那就占下牛栏1

图片.png

牛1有牛栏了,下面看牛2,再去找牛2喜欢的牛栏有没有空着的
发现它喜欢的牛栏2空着,那就占下牛栏2

图片.png

接着看牛3,去找牛3喜欢的牛栏有没有空着的
它喜欢的牛栏没有空位
这时候他就要让其它的牛设法给腾出自己想要的牛栏,这是个递归的过程
首先找到牛3喜欢的牛栏1现在的主人牛1手上,于是便让牛1给它腾位置
牛1只好去找别的位置,发现自己喜欢的牛栏没有也没有空位
于是它找到牛栏2现在的主人牛2,让牛2给自己腾位置
牛2发现自己还有牛栏3空着,便占下牛栏3,开始递归回溯
牛2把牛栏2腾给了牛1,牛1占下牛栏2
牛1把牛栏1腾给了牛3,牛3占下牛栏1

图片.png

最后是牛4,去找牛4喜欢的牛栏又没有空着的
发现没有,便递归请其它牛帮它腾牛栏,结果腾不出来(递归过程可以自己尝试梳理一下)

于是最终答案就是这样

图片.png

这样的递归如何用程序实现呢
请看下方(我叫它DFS,似乎更多是叫Find)

bool DFS(int x){
    vis[x]=1; //标记已访问过该点
    for(int i=0; i<v[x].size(); i++)
        if(have[v[x][i]]==0){ //如果有喜欢的牛栏是空的 
            have[v[x][i]]=x; //占下该牛栏 
            return true;  
        }
    //否则没有喜欢的牛栏是空的 
    for(int i=0; i<v[x].size(); i++){ //依次访问喜欢的牛栏的现主人,请求其腾出牛栏 
        int temp=have[v[x][i]]; 
        if(vis[temp]!=0){
            continue; //与上面的vis[]相呼应,用于避免重复访问从而进入死循环 
        }
        if(DFS(temp)){ //如果能腾出牛栏 
            have[v[x][i]]=x; //占下该牛栏 
            return true; 
        }
    }
    return false;
}

一定要记得判段重复啊(笔者就在这上面被坑了,直接死循环爆栈)

OK,这就基本结束啦~o(* ̄▽ ̄*)o

最后照例奉上完整程序和精心设计的注解

//Luogu P3386 [普及+/提高] - 二分图最大匹配 
//https://www.luogu.com.cn/problem/P3386
//匈牙利算法 

#include<bits/stdc++.h>
using namespace std;

int have[505]={0}; //记录牛栏的主人 
int vis[505]; //标记已访问 
vector<int> v[505]; //存放牛喜欢的牛栏 

bool DFS(int x){
    vis[x]=1; //标记已访问过该点
    for(int i=0; i<v[x].size(); i++)
        if(have[v[x][i]]==0){ //如果有喜欢的牛栏是空的 
            have[v[x][i]]=x; //占下该牛栏 
            return true;  
        }
    //否则没有喜欢的牛栏是空的 
    for(int i=0; i<v[x].size(); i++){ //依次访问喜欢的牛栏的现主人,请求其腾出牛栏 
        int temp=have[v[x][i]]; 
        if(vis[temp]!=0){
            continue; //与上面的vis[]相呼应,用于避免重复访问从而进入死循环 
        }
        if(DFS(temp)){ //如果能腾出牛栏 
            have[v[x][i]]=x; //占下该牛栏 
            return true; 
        }
    }
    return false;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout); 
    
    int N, M, E;
    int ans=0;
    cin >> N >> M >> E;
    
    for(int i=1; i<=E; i++){
        int a, b;
        cin >> a >> b;
        v[a].push_back(b);
    }
    
    for(int i=1; i<=N; i++){
        for(int j=1; j<=N; j++)
            vis[j]=0; //vis[]记得每次都要清零 
        if(DFS(i))
            ans++;
    }

    cout << ans;
    
    return 0;
}

最后的最后,有问题欢迎在下方评论区提出呀~


zhatcx
169 声望2 粉丝

在下程序猿,专攻C++,致力于信息学竞赛