定義
如果一張無向圖的 \(N\) 個節點( \(N \geq 2\) ),可以分成 \(U\) , \(V\) 兩個非空集合,其中 \(U \cap V = \Phi\) ,並且在同一集合內的點之間都沒有邊相連,那麼稱這張無向圖為一張二分圖。
\(U\) , \(V\) 分別為二分圖的左部和右部。
頂點集 \(U\) , \(V\) 被稱為是圖的兩個部分。
等價的 , 二分圖 可以被定義成 圖中所有的環都有偶數個頂點。
二分圖判定
一張無向圖是二分圖,當且僅當圖中不存在奇環。
證明
如果某個圖是二分圖,那麼它至少有兩個結點,且所有迴路的長度均為偶數,任何無迴路的圖均是二分圖。
一旦新增一條邊後圖中出現了迴路,且長度一定為奇數,則該圖就不再是二分圖。
思想
根據上述定理,可以用染色法進行二分圖判定。
用黑白兩種顏色標記圖中的節點,當一個節點被標記後,它的所有相鄰節點應該被標記成與它相反的顏色。每個點只標記一次。
若標記過程中產生衝突,則說明存在奇環。
二分圖染色一般基於 \(DFS\) 。
時間複雜度為 \(O ( N + M )\) 。
實現
/*
By 《演算法競賽進階指南》
*/
void dfs(int x, int col)
{
賦值 v[x] ← col;
對於與 x 相連的每條無向邊(x, y) if (v[y] = 0)
dfs(y, 3 - col) else if (v[y] = col)
{
不是二分圖;
return;
}
}
主函式中
{
for (i = 1 → N)
if (v[i] = 0)
dfs(i, 1);
判定無向圖是二分圖;
}
二分圖匹配
雲:
“任意兩條邊都沒有公共端點”的邊的集合被稱為圖的一組。
學長雲:
給定一張圖 \(G\) , 在 \(G\) 的一子圖 \(M\) 中 , \(M\) 的邊集中的任意兩條邊都沒有共同的端點 , 則稱 \(M\) 是一個匹配。
by @Lucky Block
上圖中的選擇方案即為原圖的一種匹配。
二分圖最大匹配
雲:
在二分圖中,包含邊數最多的一組匹配被稱為二分圖的最大匹配。
學長又云:
給定一張圖 \(G\) , 其中邊數最多的匹配 , 即該圖的最大匹配。
匈牙利演算法(增廣路演算法)
核心
對於一匹配 \(M\) ,增廣路徑是指從 \(M\) 中未使用的頂點開始 , 並從 \(M\) 中未使用的頂點結束的交替路徑 。
可以證明 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經。
即尋找增廣路徑 , 它是一種用 增廣路徑 求 二分圖最大匹配的演算法。
-
設 \(S=\Phi\) ,即所有邊都是非匹配邊。
-
尋找增廣路 \(path\) ,把路徑上所有邊的匹配狀態取反,得到一個更大的匹配 \(S'\) 。
-
重複第 \(2\) 步,知道圖中不存在增廣路。
觀摩學長給出的一個樣例
-
對 \(Yugari\) 進行匹配 :
其直接連線點 \(Reimu\) 未被匹配 , 則將 \(Yugari\) 與 \(Reimu\) 進行匹配
-
對 \(Marisa\) 進行匹配 :
其直接連線點 \(Patchouli\) 未被匹配 , 則將 \(Marisa\) 與 \(Patchouli\) 進行匹配
-
對 \(Suika\) 進行匹配 :
其直接連線點 \(Reimu\) 被匹配 , 檢查 \(Reimu\) 的匹配點 \(Yugari\) 能否尋找到其他匹配點
-
\(Yugari\) 可與 \(Yuyuko\) 進行匹配 , 則將 \(Yugari\) 與 \(Yuyuko\) 進行匹配
由於\(Yugari\) 匹配物件改變 , \(Reimu\) 未被匹配 , 則將 \(Suika\) 與 \(Reimu\) 進行匹配
-
-
對 \(Aya\) 進行匹配 :
其直接連線點 \(Reimu\) 被匹配 , 檢查 \(Reimu\) 的匹配點 \(Suika\) 能否尋找到其他匹配點
-
\(Suika\) 無其他匹配點 , 不可將 \(Suika\) 與其他結點進行匹配
由於 \(Suika\) 匹配物件不可改變 , \(Reimu\) 被匹配 , 則 \(Aya\) 無匹配點
-
則此二分圖的一種最大匹配為 :
例題
P3386 【模板】二分圖匹配
[P3386 【模板】二分圖匹配](P3386 【模板】二分圖匹配)
- 如題,模板題是了。
/*
Name: P3386 【模板】二分圖匹配
Solution: 二分圖匹配
By Frather_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
/*=========================================快讀*/
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
/*=====================================定義變數*/
int n, m, t;
const int _ = 10010;
struct edge
{
int from;
int to;
int nxt;
} e[_];
int cnt, head[_];
bool vis[_];
int mc[_];
int ans;
/*===================================自定義函式*/
void add(int from, int to)
{
e[++cnt].from = from;
e[cnt].to = to;
e[cnt].nxt = head[from];
head[from] = cnt;
}
bool check(int u_)
{
for (int i = head[u_]; i; i = e[i].nxt)
if (!vis[e[i].to])
{
vis[e[i].to] = true;
if (!mc[e[i].to] || check(mc[e[i].to]))
{
mc[e[i].to] = u_;
return true;
}
}
return false;
}
/*=======================================主函式*/
int main()
{
n = read();
m = read();
t = read();
for (int i = 1; i <= t; i++)
{
int u = read();
int v = read();
add(u, v);
}
for (int i = 1; i <= n; i++)
{
memset(vis, false, sizeof(vis));
if (check(i))
ans++;
}
printf("%d\n", ans);
return 0;
}
P2756 飛行員配對方案問題
- 匹配完成後輸出各點匹配物件非 0 的即可。
/*
Name: P2756 飛行員配對方案問題
Solution: 二分圖匹配
By Frather_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
/*=========================================快讀*/
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
/*=====================================定義變數*/
int n, m, t;
const int _ = 10010;
struct edge
{
int from;
int to;
int nxt;
} e[_];
int cnt, head[_];
bool vis[_];
int mc[_];
int ans;
/*===================================自定義函式*/
void add(int from, int to)
{
e[++cnt].from = from;
e[cnt].to = to;
e[cnt].nxt = head[from];
head[from] = cnt;
}
bool check(int u_)
{
for (int i = head[u_]; i; i = e[i].nxt)
if (!vis[e[i].to])
{
vis[e[i].to] = true;
if (!mc[e[i].to] || check(mc[e[i].to]))
{
mc[e[i].to] = u_;
return true;
}
}
return false;
}
/*=======================================主函式*/
int main()
{
n = read();
m = read();
while (1)
{
int u = read();
int v = read();
if (u == -1 || v == -1)
break;
add(u, v);
}
for (int i = 1; i <= n; i++)
{
memset(vis, false, sizeof(vis));
if (check(i))
ans++;
}
printf("%d\n", ans);
for (int i = n + 1; i <= m; i++)
if (mc[i])
printf("%d %d\n", mc[i], i);
return 0;
}
寫在後面
本文圖片均來自 @Lucky Block。
----至已逝去的不是學長大大(((不是
鳴謝:
《演算法競賽進階指南》
@Lucky Block
OI-wiki
百度百科
Luogu