匈牙利算法——海王们的渣男渣女行为

二分图定义

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。

在这里插入图片描述

二分图的判定

给多组点,我们该如何判断能不能构成二分图呢?

至少有两个顶点且没有奇环的图才能构成二分图

在这里插入图片描述

判断二分图最常见的方法就是染色法,顾名思义,染色法就是使用黑白两种颜色对所有点都染上色,且相连的点点颜色不同,如果可以实现,则是二分图

可以用dfs和bfs去写,我这里给出bfs的代码

bool bfs(){
    queue<int>q;
    q.push(1);//将第一个点塞进去
    co[1] = 1;//染色颜色1
    while (!q.empty()){
        int u = q.front();q.pop();
        for(int i = 1; i <= n; ++i){
            if(tr[u][i]){//用的是邻接矩阵,所以判断u到i是否相连
                if(!co[i]){//如果没有染色
                    co[i] = co[u] == 1 ? 2 : 1;//就染上和u不同的颜色
                    q.push(i);//塞进队列
                }
                else if(co[i] == co[u])return false;//如果颜色和u相同,说明出现奇环,返回false即可
            }
        }
    }
    return true;
}

思考:

如果是一边加边,一边询问能否构成二分图

思路:

种类并查集

类似于上次那个食物链的题,1-n是假设A是在左边,n+1 - 2*n是假设A在右边,find(A)应该等于find(B+n),find(A + n)应该等于find(B), 每次给出新的边的时候,就判断一下,如果符合,就和类似于上面去emerge一下

二分图最大匹配

二分图最大匹配的定义

给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配。选择这样的边数最大的子集称为图的最大匹配问题(maximal matching problem)。

通俗点讲,就是海王们的配对

有一堆男海王,一堆女海王,各自都有倾慕的对象们,你是月老,负责牵线,在不考虑gay的情况下,要尽可能多的去给男海王与女海王来一一配对,这就是二分图的最大匹配

是不是及其生动形象

匈牙利算法(海王们的渣男渣女行为)

先引入题目背景:

有一场宴会,男孩和女孩组成舞伴,并且他们必须互相喜欢才能成为舞伴,一个男孩可能喜欢0个或多个女孩,一个女孩也可以喜欢0个或多个男孩,但一个男孩和他喜欢地女孩成为舞伴以后就不能和其他他喜欢的女孩成为舞伴,女孩亦是如此,问最多可以有多少对舞伴

算法思想:(划重点!!!

  • 对于一个男孩x,如果他喜欢对女孩y没有舞伴,那他们俩就成为了舞伴
  • 如果y已经有舞伴了,假设y的舞伴叫z。z看这情况心想:嘶,看架势我有可能被绿,这不行,我得先找好下家,我从来都是绿别人,哪有被反绿的,所以 z 就会去找他的其他喜欢的女孩,
    • 如果 z 发现自己可以找到新的喜欢的舞伴,那 z 就绿了 y ,然后y也绿了z, x 就顺理成章的和 y 在一起,这样最大匹配值就+1了
    • 如果 z 找不到新的舞伴,那这个时候 y 就觉得:啊,我们好歹情侣一场,有感情了,你找不到下家的话,我就不绿你了。
    • 这个时候 x 内心:小丑竟是我自己
  • z 去找下家的时候,其实又回到了 x 找舞伴的情况,也就是回到了起点,这就形成了递归其实

你以为是 z 绿了y,但一开始其实是 y 要绿了z,所以到底是谁先绿了谁,有待商榷(bushi

引申出来的人生哲理

  • 下手要趁早,因为越晚去邀请人家,配对成功的可能性就比较小,因为人家舞伴都满了,就不跟你一起(所以表白要趁早
  • 做人不要太渣,二分图匹配的过程就展示了一个绿人者终被绿的过程,你根本不知道你会在什么时候被什么人绿(手动狗头

代码实现

代码实现的过程包括两部分:dfs函数与一个for循环

bool dfs(int u){ //邻接矩阵建图的代码 
    for(int i = 1; i <= n; ++i){//对 u 这个点去循环他喜欢的人
        if(!vis[i] && tr[u][i]){//如果他喜欢的 i 没有出现在此次x找舞伴的过程中
            vis[i] = 1;
            if(!link[i] || dfs(link[i])){//如果 i 没有舞伴,或者 i 的舞伴可以绿他,就安排 i 和 u 在一起
                link[i] = u;
                return true;
            }
        }
    }
    return false;
}


bool dfs(int u){ //链式前向星建图的代码
    for(int i = head[u]; i != -1; i = tr[i].next){
        int v = tr[i].to;
        if(!vis[v]){
            vis[v] = 1;
            if(link[v] == -1 || dfs(link[v])){
                link[v] = u;
                return true;
            }
        }
    }
    return false;
}
for(int i = 1; i <= n; ++i){//对每个人都去找舞伴
            mem(vis, 0);
            if(dfs(i))++ans;
}

时间复杂度

邻接矩阵建图法:(O(n^3)),n是点数

链式前向星建图法: (O(n*m)),m是总边数

König定理

二分图中的最大匹配数等于这个图中的最小点覆盖数

什么是最小点覆盖数?

每个点覆盖以他为端点的所有边,选择最少的点来覆盖所有的边,这些点个个数就叫做最小点覆盖数

(选最少点海王,能联系到其他的所有的海王

这里就不证明了,下次一定下次一定(bushi

Girls and Boys

题目描述:

和上面那个差不多,这里说的是两个人之间有个"romantically involved"的亲密关系,让你找到一个集合,使得所有点没有联系,就是求最大独立集。

思路:

最大独立集 = 点总数 - 最大匹配数

我这里使用的是链式前向星建图法

注意⚠️:链式前向星是存边,所以得开大一点的空间,顺便吐槽一下hduoj,数组开的不够大你居然爆TLE,而不是RE,害我我debug一个点都不知道哪里有问题,重写一遍才意识到,可恶至极!

#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<string>
#include<vector>
#include<sstream>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define endl '
'
#define inf 0x3f3f3f3f
#define MAX 500 + 50
#define mod 1000000007
#define lowbit(x) (x & (-x))
#define sd(n) scanf("%d",&n)
#define sdd(n,m) scanf("%d %d",&n,&m)
#define pd(n) printf("%d
", (n))
#define pdd(n,m) printf("%d %d
",n, m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
typedef unsigned long long ull;
//不开longlong见祖宗!不看范围见阎王!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

int k, n, m, x, ans, tot;
int link[MAX];
bool vis[MAX];
int head[MAX];
struct ran{
    int to, next;
}tr[100005];
string s;
char y;

void add(int u, int v){//链式前向星建图
    tr[++tot].to = v;
    tr[tot].next = head[u];
    head[u] = tot;
}

bool dfs(int u){
    for(int i = head[u]; i != -1; i = tr[i].next){
        int v = tr[i].to;
        if(!vis[v]){
            vis[v] = 1;
            if(link[v] == -1 || dfs(link[v])){
                link[v] = u;
                return true;
            }
        }
    }
    return false;
}

void init(){//多组输入,一定要记得初始化
    mem(link, -1);
    mem(head, -1);
    ans = tot = 0;
}

int main(){
    while (cin>>n) {
        init();
        for(int i = 0; i < n; ++i){
            cin>>s;cin>>y>>k>>y;//这个题的读入真傻逼,奇奇怪怪的
            while (k--) {
                sd(x);
                add(i, x);
            }
        }
        for(int i = 0; i < n; ++i){
            mem(vis, 0);
            if(dfs(i))++ans;
        }
        cout<<n - ans/ 2<<endl;
    }  
    return 0;
}

The Accomodation of Students

题目描述:

有一群学生,一些人可能彼此认识,一些人可能不认识,认识不存在传染性,如A认识B,B认识C,但A不能就此认识C

你需要将所有学生分成两组,同一组的学生互不认识,如果可以的话,再将认识的两个人放在一个房间里,问最多能需要多少个房间;如果不能就输出No

思路

这个题首先要判能不能构成二分图

如果可以构成,再继续使用匈牙利算法去进行二分图最大匹配

这次用的是邻接矩阵的建图方法

#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<string>
#include<vector>
#include<sstream>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define endl '
'
#define inf 0x3f3f3f3f
#define MAX 200 + 50
#define mod 1000000007
#define lowbit(x) (x & (-x))
#define sd(n) scanf("%d",&n)
#define sdd(n,m) scanf("%d %d",&n,&m)
#define pd(n) printf("%d
", (n))
#define pdd(n,m) printf("%d %d
",n, m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
typedef unsigned long long ull;
//不开longlong见祖宗!提交不看数据范围见祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

int n, m, x, y;
bool vis[MAX];
int tr[MAX][MAX];
int co[MAX];
int link[MAX];

bool bfs(){
    queue<int>q;
    q.push(1);
    co[1] = 1;
    while (!q.empty()){
        int u = q.front();q.pop();
        for(int i = 1; i <= n; ++i){
            if(tr[u][i]){
                if(!co[i]){
                    co[i] = co[u] == 1 ? 2 : 1;
                    q.push(i);
                }
                else if(co[i] == co[u])return false;
            }
        }
    }
    return true;
}

bool dfs(int u){
    for(int i = 1; i <= n; ++i){
        if(!vis[i] && tr[u][i]){
            vis[i] = 1;
            if(!link[i] || dfs(link[i])){
                link[i] = u;
                return true;
            }
        }
    }
    return false;
}

void init(){
    mem(tr, 0);
    mem(link, 0);
    mem(co, 0);
}

int main(){
    while (sdd(n, m) != EOF) {
        init();
        while (m--) {
            sdd(x, y);
            tr[x][y] = 1;
        }
        if(!bfs() || n == 1)cout<<"No
";//n=1有坑!
        else{
            int ans = 0;
            for(int i = 1; i <= n; ++i){
                mem(vis, 0);
                if(dfs(i))++ans;
            }
            cout<<ans<<endl;
        }
    }
    return 0;
}
不是所有的牛奶都叫特仑苏,也不是所有的人都叫猪猪
原文地址:https://www.cnblogs.com/chelsea0901/p/15110294.html