圖的匹配與網路流

rlc202204發表於2024-03-10

1 圖的匹配

1.1 基礎知識

1.1.1 匹配的定義

對於一個無向圖 \(G = (V,E)\),定義一個集合 \(M \sube E\) 是這張圖的一個匹配當且僅當對於不存在 \(u,v,w \in V\) 使得 \((u,v) \in M\)\((u,w) \in M\)

一個匹配的大小定義為所含邊數的多少。

最大匹配指一張圖中最大的匹配。

考慮到一般圖的匹配比較難,我們一下討論的都是二分圖的匹配。

定義一個二分圖 \(G=(X,Y,E)\)

1.1.2 增廣軌

對於一個匹配 \(M\),如果存在一條路徑滿足:

  1. 起點和終點均是未匹配點。

  2. 路徑上的邊恰好為 \(\not\in M, \in M, \not\in M ... \not\in M\) 這樣交錯的。

這條路徑稱為當前匹配下的一條增廣軌

1.1.3交錯軌

交錯軌只要求起點未匹配,終點任意。

1.2 最大匹配演算法

1.2.1 樸素最大匹配演算法

最大匹配與增廣軌

定理: 一個二分圖 \(G\) 的匹配 \(M\) 不存在增廣軌 \(\iff\) \(M\) 是最大匹配。

證明:

如果存在一條增廣軌 \(P\),則 \(M \oplus P\) 將會是原匹配多一的匹配,得證。

如果 \(M\) 沒有增廣軌,我們要證明它是最大匹配。

假設 \(M\) 不是最大匹配,不妨設 \(M^*\) 是一個最大匹配。

\(T = M \oplus M^*\),由於匹配中每個點度數不超過 \(1\),所以 \(T\) 中點度數不超過 \(2\),說明 \(T\) 是由若干個環和路徑組成的。

如果一個點有兩條鄰邊,則這兩條顯然一條屬於 \(M\),一條屬於 \(M^*\)

如果是環,則兩個集合中的邊數量相等,由於 \(|M| < |M^*|\),說明剩下的路徑中,一定有一條路徑 \(M^*\) 中的邊更多,這說明這條路徑是一條增廣軌,和假設矛盾!所有定理得證。

尋找增廣軌

為了尋找 \(M\) 的增廣軌,我們可以先找到其所有交錯軌,進而找到增廣軌。

我們初始是一個空集 \(S\),然後對其進行擴充套件:

  1. \(X\) 所有未匹配點全部加入 \(S\) 中。

  2. 重複以下操作知道不能進行:取出 \(S\) 中還未處理的 \(X\) 中的點 \(x\),找到所有 \((x,y) \in E,(x,y) \not\in M\)\(y\),將 \(y\) 加入 \(S\),並將所有的 \((y,z) \in E,(y,z) \in M\)\(z\) 加入 \(S\)

如果第二步時某一時刻 \(y\) 未被匹配,說明我們找到了一條以 \(y\) 結尾的增廣軌,否則不存在。

最終演算法

我們可以不斷找到增廣軌,然後更新匹配,直到最大匹配。由於最大匹配至多為 \(n\),而找一次增廣軌按照上面的做法是 \(O(m)\) 的一次 bfs,所以總時間複雜度是 \(O(nm)\) 的。

這裡以 P3386 【模板】二分圖最大匹配 洛谷模板題為例。

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505;

int n, m, E;
vector<int> e[N * 2]; 

int mch[N * 2] = {0}, p[N * 2] = {0};
bool vis[N * 2] = {false};

bool bfs() {//找增廣軌 
	queue<int> q;
	for (int i = 1; i <= n + m; i++) {
		vis[i] = false, p[i] = 0;
		if (mch[i] == 0 && i <= n) 
			q.push(i), vis[i] = true;
	}
	while (!q.empty()) {
		int h = q.front();
		q.pop();
		for (auto i: e[h]) {
			if (p[i] != 0)
				continue;
			p[i] = h;
			if (mch[i] == 0) {
				for (int j = i; j != 0;) {
					mch[j] = p[j]; 
					int nxt = mch[p[j]];
					mch[p[j]] = j;
					j = nxt;
				}
				return true;
			}
			if (!vis[mch[i]])
				vis[mch[i]] = true, q.push(mch[i]);
		}
	}
	return false;
}

int main() {
	cin >> n >> m >> E;
	for (int i = 1, u, v; i <= E; i++) {
		cin >> u >> v;
		e[u].push_back(v + n);
		e[v + n].push_back(u);
	}	
	while (true) {
		if (!bfs())
			break;
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans += (mch[i] != 0);
	cout << ans << endl;
	return 0;
}

1.2.3 匈牙利演算法

1.2.4 Hopcroft-Karp 演算法

相關文章