『筆記』二分圖

Frather發表於2021-03-06

定義

如果一張無向圖的 \(N\) 個節點( \(N \geq 2\) ),可以分成 \(U\)\(V\) 兩個非空集合,其中 \(U \cap V = \Phi\) ,並且在同一集合內的點之間都沒有邊相連,那麼稱這張無向圖為一張二分圖

\(U\)\(V\) 分別為二分圖的左部和右部。

頂點集 \(U\)\(V\) 被稱為是圖的兩個部分。

等價的 , 二分圖 可以被定義成 圖中所有的環都有偶數個頂點

圖片來自@Lucky Block

二分圖判定

一張無向圖是二分圖,當且僅當圖中不存在奇環

證明

如果某個圖是二分圖,那麼它至少有兩個結點,且所有迴路的長度均為偶數,任何無迴路的圖均是二分圖。

一旦新增一條邊後圖中出現了迴路,且長度一定為奇數,則該圖就不再是二分圖。

思想

根據上述定理,可以用染色法進行二分圖判定。

用黑白兩種顏色標記圖中的節點,當一個節點被標記後,它的所有相鄰節點應該被標記成與它相反的顏色。每個點只標記一次。

若標記過程中產生衝突,則說明存在奇環。

二分圖染色一般基於 \(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

圖片來自@Lucky Block

上圖中的選擇方案即為原圖的一種匹配。

二分圖最大匹配

雲:

在二分圖中,包含邊數最多的一組匹配被稱為二分圖的最大匹配

學長又云:

給定一張圖 \(G\) , 其中邊數最多的匹配 , 即該圖的最大匹配

@圖片來自@Lucky Block

匈牙利演算法(增廣路演算法)

核心

對於一匹配 \(M\) ,增廣路徑是指從 \(M\) 中未使用的頂點開始 , 並從 \(M\) 中未使用的頂點結束的交替路徑 。

可以證明 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經。

即尋找增廣路徑 , 它是一種用 增廣路徑 求 二分圖最大匹配的演算法。

  1. \(S=\Phi\) ,即所有邊都是非匹配邊。

  2. 尋找增廣路 \(path\) ,把路徑上所有邊的匹配狀態取反,得到一個更大的匹配 \(S'\)

  3. 重複第 \(2\) 步,知道圖中不存在增廣路。

觀摩學長給出的一個樣例

@圖片來自@Lucky Block

  • \(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

例題

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 飛行員配對方案問題

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

相關文章