0x68.圖論 - 二分圖的匹配

聲明:
本系列博客是《算法競賽進階指南》+《算法競賽入門經典》+《挑戰程序設計競賽》的學習筆記,主要是因爲我三本都買了 按照《算法競賽進階指南》的目錄順序學習,包含書中的少部分重要知識點、例題解題報告及我個人的學習心得和對該算法的補充拓展,僅用於學習交流和複習,無任何商業用途。博客中部分內容來源於書本和網絡(我儘量減少書中引用),由我個人整理總結(習題和代碼可全都是我自己敲噠)部分內容由我個人編寫而成,如果想要有更好的學習體驗或者希望學習到更全面的知識,請於京東搜索購買正版圖書:《算法競賽進階指南》——作者李煜東,強烈安利,好書不火系列,謝謝配合。


下方鏈接爲學習筆記目錄鏈接(中轉站)

學習筆記目錄鏈接


ACM-ICPC在線模板


這篇寫的有些水,主要是好多內容都可以用網絡流解決而且網絡流更優

二分圖應用的幾個重要定理

最大匹配數:最大匹配的匹配邊的數目

最小點覆蓋數:選取最少的點,使任意一條邊至少有一個端點被選擇

最大獨立數:選取最多的點,使任意所選兩點均不相連

最小路徑覆蓋數:對於一個 DAG(有向無環圖),選取最少條路徑,使得每個頂點屬於且僅屬於一條路徑。路徑長可以爲 0(即單個點)。

定理1:最大匹配數 = 最小點覆蓋數(這是 Konig 定理)

定理2:最大匹配數 = 最大獨立數

定理3:最小路徑覆蓋數 = 頂點數 - 最大匹配數

二分圖

- 概述

二分圖又稱作二部圖,是圖論中的一種特殊模型。設G=(V,E)是一個無向圖,如果頂點V可分割爲兩個互不相交的子集(A,B),並且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬於這兩個不同的頂點集(i in A,j in B),則稱圖G爲一個二分圖。
在這裏插入圖片描述
如上圖就是一個標準的二分圖

性質
二分圖不存在長度爲奇數的環

一、二分圖的判定

此題來源於《挑戰程序設計競賽》

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
DFS

vector<int> G[MAX_V];//圖的表示
int V;//頂點數
int color[MAX_V];//頂點i的顏色1或-1
//把頂點染成1或-1
bool dfs(int v,int c)
{
	color[v]=c;//頂點v染成c
	for (int i = 0; i < G[v].size(); ++i)
	{
		//相鄰點同色,返回false
		if(color[G[v][i]]==c)
			return false;
		//相鄰點沒有染色,就將其染色爲-c,繼續dfs,若最終仍爲false,則返回false
		if(color[G[v][i]]==0 && !dfs(G[v][i],-c))
			return false;
	}

	//所有頂點染過色返回true
	return true;
}
void solve()
{
	//如果是連通圖,則一次dfs即可訪問所有位置
	//但題目沒有說明,故要依次檢查各個頂點的情況
	for (int i = 0; i < V; ++i)
	{
		if (color[i]==0)
		{
			//如果頂點還沒有染色,染成1
			if (!dfs(i,1))
			{
				cout<<"No"<<endl;
				return;
			}
		}
	}
	cout<<"Yes"<<endl;
}

以及BFS


int n,m;
vector<int> g[MAXN];
int color[MAXN];
void init()
{
	for(int i=0;i<n;i++){
		g[i].clear();
		color[i]=-1;
	}
}
int bfs()
{
	queue<int>q;
	q.push(0);
	color[0]=1;
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i=0;i<g[t].size();i++){
			if(color[g[t][i]]==color[t])//相鄰節點顏色相同,不是二分圖
			return 1;
			else if(color[g[t][i]]==-1)//未染色
			{
				color[g[t][i]]=-color[t];
				q.push(g[t][i]);
			}
		}
	}
	return 0;
}

1.P1155 雙棧排序(二分圖的染色判斷+鏈式前向星)

P1155 雙棧排序

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述

P1155 雙棧排序(二分圖的染色判斷+鏈式前向星)#

2.luogu P1525 關押罪犯(並查集/二分圖判定+二分)

在這裏插入圖片描述
我哭了,以後遇見stl的各種返回值都先強轉int再說

for (int i = 0; i <= (int)e[x].size()-1; i++) {

不然是真的找不到bug

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e4;
const int M = 5e5+7;
/*
void add(int x,int y,int z){
    ver[++tot] = y;
    nex[tot] = head[x];
    head[x] = tot;
}
*/
vector<int>e[N];

struct node{
    int x,y,z;
    bool operator<(const node &t)const {
        return z > t.z;
    }
}p[M];
int vis[N];
int n,m;
bool dfs(int x,int color){
    vis[x] = color;
    for (int i = 0; i <= (int)e[x].size()-1; i++) {
        int y = e[x][i];
        if(vis[y]){
            if(vis[y] == color)return 0;
        }
        else {
            if(!dfs(y,3 - color))return 0;
        }
    }
    return 1;
}

inline bool pd(int now) {
	for (int i = 1; i <= n; i++) e[i].clear();
	for (int i = 1; i <= m; i++) {
		if (p[i].z <= now) break;
		e[p[i].x].push_back(p[i].y);
		e[p[i].y].push_back(p[i].x);
	}
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= n; i++)
		if (!vis[i] && !dfs(i, 1)) return 0;
	return 1;
}


int main(){
    cin>>n>>m;
    over(i,1,m)
        scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
    sort(p+1,p+1+m);
    int l = 0,r = p[1].z;
    while(l < r){
        int mid = (l + r) >> 1;
        if(pd(mid))r = mid;//如果是二分圖,可以把答案縮的更小
        else l = mid + 1;
    }
    cout<<l<<endl;
    return 0;
}

二、二分圖的最大匹配

1.匈牙利算法

時間複雜度爲 O(NM)O(NM)

算法步驟大致如下:

首先從任意一個未配對的點u開始,選擇他的任意一條邊( u - v ),如此時 還未配對,則配對成功,配對數加一,若 v 已經配對,則嘗試尋找 v 的配對的另一個配對(該步驟可能會被遞歸的被執行多次),若該嘗試成功,則配對成功,配對數加一。

若上一步配對不成功,那麼重新選擇一條未被選擇過的邊,重複上一步,直到點 的所有的邊都被選擇過爲止。

對剩下每一個沒有被配對的點執行步驟 1,直到所有的點都嘗試完畢。

在這裏插入圖片描述

可以發現,當嘗試對節點 3 進行匹配時,走過了一條路徑(3-4-1-5-2-6),最後找到了新的匹配方案,我們把這樣的道路叫做 增廣路 ,其本質是一條起點和終點都是未匹配節點的路徑。

匈牙利算法執行的過程也可以看作是不斷尋找增廣路的過程,當在當前匹配方案下再也找不到增廣路,那麼當前匹配方案便是最大匹配了。

2.luogu P3386 【模板】二分圖最大匹配

P3386 【模板】二分圖最大匹配
在這裏插入圖片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e3;
const int M = 5e4+7;
int n,m,ans,e;
//int head[N],ver[M],nex[M],tot;
bool vis[N];
int match[N];
int f[N][N];
/*void add(int x,int y,int z){
    ver[++tot] = y;nex[tot] = head[x];head[x] = tot;
}*/
bool dfs(int x){
    over(i,1,m)
        if(!vis[i] && f[x][i]){//沒有被訪問過且有路
            vis[i] = 1;
            if(!match[i] || dfs(match[i])){//未被匹配或者匹配項有其他匹配
                match[i] = x;//就匹配
                return 1;
            }
        }
    return 0;
}

int main(){
    cin>>n>>m>>e;
    over(i,1,e){
        int x,y;
        scanf("%d%d",&x,&y);
        if(y <= m)
        f[x][y] = 1;//因爲有重邊所以選鄰接矩陣而且數據範圍不大
    }
    over(i,1,n){
        memset(vis,0,sizeof vis);//注意每次都要置0
        if(dfs(i))ans++;
    }
    printf("%d\n",ans);
    return 0;
}

三、二分圖的多重匹配

在二分圖最大匹配中,每個點(不管是X方點還是Y方點)最多隻能和一條匹配邊相關聯,然而,我們經常遇到這種問題,即二分圖匹配中一個點可以和多條匹配邊相關聯,但有上限,或者說,Li表示點i最多可以和多少條匹配邊相關聯。

二分圖多重匹配分爲二分圖多重最大匹配與二分圖多重最優匹配兩種,分別可以用最大流與最大費用最大流解決。

(1)二分圖多重最大匹配:
在原圖上建立源點S和匯點T,S向每個X方點連一條容量爲該X方點L值的邊,每個Y方點向T連一條容量爲該Y方點L值的邊,原來二分圖中各邊在新的網絡中仍存在,容量爲1(若該邊可以使用多次則容量大於1),求該網絡的最大流,就是該二分圖多重最大匹配的值。

(2)二分圖多重最優匹配:
在原圖上建立源點S和匯點T,S向每個X方點連一條容量爲該X方點L值、費用爲0的邊,每個Y方點向T連一條容量爲該Y方點L值、費用爲0的邊,原來二分圖中各邊在新的網絡中仍存在,容量爲1(若該邊可以使用多次則容量大於1),費用爲該邊的權值。求該網絡的最大費用最大流,就是該二分圖多重最優匹配的值。

四、二分圖的帶權匹配

設 G 爲帶邊權的二分圖,尋找 G 邊權和最大最大匹配稱爲最大權匹配問題。
首先要保證是最大匹配,然後再找最大邊權。

有兩種解法:費用流和KM算法。

我選擇費用流。
然後就KM懶得寫(學)了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章