『筆記』二分圖
阿新 • • 發佈:2021-03-07
# **定義**
如果一張無向圖的 $N$ 個節點( $N \geq 2$ ),可以分成 $U$ , $V$ 兩個非空集合,其中 $U \cap V = \Phi$ ,並且在同一集合內的點之間都沒有邊相連,那麼稱這張無向圖為一張**二分圖**。
$U$ , $V$ 分別為二分圖的左部和右部。
頂點集 $U$ , $V$ 被稱為是圖的兩個部分。
等價的 , 二分圖 可以被定義成 **圖中所有的環都有偶數個頂點**。
![圖片來自@Lucky Block](https://i.loli.net/2021/03/06/t17PTrLv9hznwmk.png)
# **二分圖判定**
> 一張無向圖是二分圖,當且僅當圖中不存在**奇環**。
## **證明**
如果某個圖是二分圖,那麼它至少有兩個結點,且所有迴路的長度均為**偶數**,任何無迴路的圖均是二分圖。
一旦新增一條邊後圖中出現了**迴路**,且**長度一定為奇數**,則該圖就不再是二分圖。
## **思想**
根據上述定理,可以用**染色法**進行二分圖判定。
用黑白兩種顏色標記圖中的節點,當一個節點被標記後,它的所有相鄰節點應該被標記成與它相反的顏色。每個點只標記一次。
若標記過程中產生衝突,則說明存在奇環。
> 二分圖染色一般基於 $DFS$ 。
時間複雜度為 $O ( N + M )$ 。
# **實現**
```cpp
/*
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
![圖片來自@Lucky Block](https://i.loli.net/2021/03/06/Xs3N6HwaqyvfDVU.png)
上圖中的選擇方案即為原圖的一種匹配。
# **二分圖最大匹配**
雲:
> 在二分圖中,包含邊數最多的一組匹配被稱為二分圖的**最大匹配**。
學長又云:
> 給定一張圖 $G$ , 其中邊數最多的匹配 , 即該圖的**最大匹配**。
![@圖片來自@Lucky Block](https://i.loli.net/2021/03/06/37J1mUbeVIBuaTx.png)
## **匈牙利演算法(增廣路演算法)**
### **核心**
>> 對於一匹配 $M$ ,增廣路徑是指從 $M$ 中未使用的頂點開始 , 並從 $M$ 中未使用的頂點結束的交替路徑 。
>>
>> 可以證明 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經。
>
> 即尋找增廣路徑 , 它是一種用 **增廣路徑** 求 二分圖最大匹配的演算法。
1. 設 $S=\Phi$ ,即所有邊都是非匹配邊。
2. 尋找增廣路 $path$ ,把路徑上所有邊的匹配狀態取反,得到一個更大的匹配 $S'$ 。
3. 重複第 $2$ 步,知道圖中不存在增廣路。
### **觀摩學長給出的一個樣例**
![@圖片來自@Lucky Block](https://pic.superbed.cn/item/5dc3e0d88e0e2e3ee95a90eb.png)
+ 對 $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$ 無匹配點
則此二分圖的一種最大匹配為 :
![圖片來自@Lucky Block](https://ae01.alicdn.com/kf/H17509784e3ee424fa9d656e2be4a4b50A.png)
# **例題**
## **P3386 【模板】二分圖匹配**
[P3386 【模板】二分圖匹配](P3386 【模板】二分圖匹配)
+ 如題,模板題是了。
```cpp
/*
Name: P3386 【模板】二分圖匹配
Solution: 二分圖匹配
By Frather_
*/
#include
#include
#include
#include
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 飛行員配對方案問題**
[P2756 飛行員配對方案問題](https://www.luogu.com.cn/problem/P2756)
+ 匹配完成後輸出各點匹配物件非 0 的即可。
```cpp
/*
Name: P2756 飛行員配對方案問題
Solution: 二分圖匹配
By Frather_
*/
#include
#include
#include
#include
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
百度百科