二分图入门

Bipartite Graph

引入

二分图是一类特殊的, 它可以划分为俩个集合,且每个集合内部的点互不相连,例如:

典型二分图

那么对于二分图显然有俩个性质

性质

  • 如果俩个集合中的所有点分别染成红色和黑色,可以发现,每一条边都是连接一个黑点和一个白点
  • 二分图不存在长度为奇数的环

对于性质二的证明

因为一共有俩个集合,所以每次从一个点出发(无论哪个集合),每次走,都是从一个集合到另一个集合(由性质一得),显然只有走偶数次才可以回到同一个集合

证毕。

判定

转化问题为:我们需要知道是否可以将图中的顶点分成两个满足条件的集合?

显然,直接枚举答案集合的话实在是太慢了,我们需要更高效的方法考虑上文提到的性质,我们可以使用 \(DFS\) 或者 \(BFS\) 来遍历这张图。如果发现了奇环,那么就不是二分图,否则是,非常非常的简单哈

应用详解

这里随便写点,主要是二分图这个东西可以延展出来很多东西,这个玩意儿还可以转换为网络流模型,真的是好东西哈。

二分图最大匹配

二分图最大匹配、完美匹配和匈牙利算法

最小点覆盖问题

定义: 最小点覆盖是选最少的点(作为一个集合),满足图中每条边至少有一个端点在被选的点集中

使用 \(könig\) 定理:二分图中的最大匹配数等于这个图中的最小点覆盖数,即二分图中,最小点覆盖 \(=\) 最大匹配

证明:

将二分图点集分成左右两个集合,使得所有边的两个端点都不在一个集合。

考虑如下构造:从左侧未匹配的节点出发,按照匈牙利算法中增广路的方式走,即先走一条未匹配边,再走一条匹配边。由于已经求出了最大匹配,所以这样的增广路一定以匹配边结束。在所有经过这样「增广路」的节点上打标记。则最后构造的集合是:所有左侧未打标记的节点和所有右侧打了标记的节点。

首先,易证这个集合的大小等于最大匹配。打了标记的节点一定都是匹配边上的点,一条匹配的边两侧一定都有标记(在增广路上)或都没有标记,所以两个节点中必然有一个被选中。

其次,这个集合是一个点覆盖。一条匹配边一定有一个点被选中,而一条未匹配的边一定是增广路的一部分,而右侧端点也一定被选中。

同时,不存在更小的点覆盖。为了覆盖最大匹配的所有边,至少要有最大匹配边数的点数。(引自 \(OI-Wiki\)

证毕。

二分图最大独立集

定义:最大独立集是选最多的点,满足两两之间没有边相连

解法:

因为在最小点覆盖中,任意一条边都被至少选了一个顶点,所以对于其点集的补集,任意一条边都被至多选了一个顶点,所以不存在边连接两个点集中的点,且该点集最大

因此二分图中:最大独立集 \(=\) 最小点覆盖。

KM算法

二分图最大权匹配

给定一个二分图,两边的点数都为 \(n\) ,给出若干条边,每条边有一个权值,求最大的完美匹配的值

20230510 又是临近学考的一天,终于领悟了 \(KM\) 的精髓了,太妙了!!!

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500;
int n;
int lx[maxn]; // 记录左顶点的值
int ly[maxn]; // 记录右顶点的值
int link[maxn]; // 记录每轮右顶点匹配的左顶点
int w[maxn][maxn]; // 邻接矩阵 记录边权
bool s[maxn]; // 记录每轮匹配过的左顶点
bool t[maxn]; // 记录每轮匹配过的右顶点
bool check(int x) { // check 某个左顶点是非匹配过
    s[x] = 1;
    for (int i = 1; i <= n; i++) {
        if (lx[x] + ly[i] == w[x][i] && !t[i]) {
            t[i] = 1;
            if (!link[i] || check(link[i])) {
                link[i] = x;
                return 1;
            }
        }
    }
    return 0;
}
void update() {
    int a = 1 << 30;
    for (int i = 1; i <= n; i++) {
        if (s[i]) {
            for (int j = 1; j <= n; j++) {
                if (!t[j]) {
                    a = min(a, lx[i] + ly[j] - w[i][j]);
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        if (s[i]) lx[i] -= a;
        if (t[i]) ly[i] += a;
    }
}
void km() {
    for (int i = 1; i <= n; i++) {
        link[i] = lx[i] = ly[i] = 0;
        for (int j = 1; j <= n; j++) {
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++) {
        while (1) {
            for (int j = 1; j <= n; j++) s[j] = t[j] = 0;            
            if (check(i)) break;
            else update();
        }
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> w[i][j];
        }
    }
    km();
    int ans1 = 0;
    int ans2 = 0;
    for (int i = 1; i <= n; i++) {
        ans1 += lx[i] + ly[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            w[i][j] *= -1;
        }
    }
    km();
    for (int i = 1; i <= n; i++) {
        ans2 += lx[i] + ly[i];
    }
    cout << -ans2 << '\n' << ans1;
    return 0;
}

注意:\(KM\) 算法需要左部和右部完全匹配

补充的定理:

最大匹配数:最大匹配的匹配边的数目

最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择

最大独立数:选取最多的点,使任意所选两点均不相连

最小路径覆盖数:对于一个 \(DAG\)(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 \(0\)(即单个点)。

定理1:最大匹配数 \(=\) 最小点覆盖数(这是 \(Konig\) 定理)

定理2:最大匹配数 \(=\) 最大独立数

定理3:最小路径覆盖数 \(=\) 顶点数 \(-\) 最大匹配数

二分图相关知识(较好理解版)

posted @ 2023-08-02 08:00  Furthe77oad  阅读(13)  评论(1编辑  收藏  举报