匈牙利演算法——海王們的渣男渣女行為

扇與她盡失發表於2021-08-06

二分圖定義

二分圖又稱作二部圖,是圖論中的一種特殊模型。 設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 '\n'
#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", (n))
#define pdd(n,m) printf("%d %d\n",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 '\n'
#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", (n))
#define pdd(n,m) printf("%d %d\n",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";//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;
}

相關文章