【学习笔记】二分图匹配
前言
二粉兔集合,全体起立!
一些定义
- 二分图:将能将原图点集 \(V\) 分成两个集合 \(A,B\),且 \(A∩B=∅,A∪B=V\),使得所有边的端点一个在 \(A\) 中,一个在 \(B\) 中的图
- 匹配:一个边的集合,集合内的任意两个边都没有公共端点,那么集合内的边是匹配边,不在集合内但在原图边集内的边是非匹配边,匹配边的端点是匹配点,其它点是非匹配点
- 最大匹配:一个图所有的匹配中,包含匹配边最多的匹配的匹配边数
- 交替路:从非匹配点开始,走非匹配边,匹配边,非匹配边......的路径
- 增广路:以非匹配点结束的交替路
- 完美匹配:所有点都是匹配点的匹配
- 完备匹配:使得 \(A \textup{ or } B\) 集合的所有点都是匹配点的匹配
- 最佳匹配:带权二分图的权值最大的完备匹配
- 独立集:点的集合,集合内任意两个点没有边相连
一些性质
- 二分图等价于无奇环
- 二分图中,最大独立集点数加最大匹配点数等于总点数
匈牙利算法
等着 ctrl + c
KM 算法
- 交替路:DFS 过程中,所有访问过的节点以及这些节点形成的边构成的树(即寻找增广路过程中形成的树)
- 顶标:每个顶点赋予的顶点标记值。满足:\(a_i+b_j\ge w(i,j)\)
- 相等子图:满足 \(a_i+b_j=w(i,j)\) 的边构成的子图
- 定理:当相等子图中存在完美匹配时,这个完美匹配就是二分图的带权最大匹配
- 证明:显而易见
- 此处我们设结点较少的集合为 \(A\),初始时对 \(A\) 的每一个顶点设置顶标,顶标的值为该点关联的最大边的权值,\(B\) 的顶点顶标为 \(0\)。
- 对于 \(A\) 中的每个顶点,在相等子图中利用匈牙利算法找一条增广路径,如果没有找到,则修改顶标,扩大相等子图,继续找增广路径。
如果从 \(A\) 中的某个点 \(A_i\) 出发在相等子图中没有找到增广路径,我们是如何修改顶标的呢?如果我们没有找到增广路径,则我们一定找到了许多条从 \(A_i\) 出发并结束于 \(A\) 的匹配边与未匹配边交替出现的路径,即交替路。我们将交替路中 \(A\) 的顶点顶标减去一个值 \(d\),交替路中属于 \(B\) 的顶点顶标加上一个值 \(d\)。其中 \(d=a_i+b_j-w(i,j)\)。
-
两端都在交替路中的边 \((i,j)\),其顶标和没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
-
两端都不在交替路中的边 \((i,j)\),其顶标也没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
-
\(x\) 不在交替路中,\(y\) 在交替路中的边 \((i,j)\),它的顶标和会增大。它原来不属于相等子图,现在仍不属于相等子图。
-
\(x\) 在交替路中,\(y\) 不在交替路中的边 \((i,j)\),它的顶标和会减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
我们修改顶标的目的就是要扩大相等子图。为了保证至少有一条边进入相等子图,我们可以在交错树的边中寻找顶标和与边权之差最小的边,这就是前面说的 \(d\)。将交错树中属于 A 的顶点减去 \(d\),交错树中属于 B 的顶点加上 \(d\)。则可以保证至少有一条边扩充进入相等子图。
- 当每个点都找到增广路径时,此时意味着每个点都在匹配中,即找到了二分图的完备匹配。该完备匹配即为二分图的带权最大匹配
关于相等子图的一些性质:
- 在任意时刻,相等子图的顶标和即为所有顶点的顶标和
- 扩充相等子图后,相等子图的顶标和将会减小
- 当相等子图的最大匹配为原图的完备匹配时,匹配边的权值和等于所有顶点的顶标和,此匹配即为二分图的带权最大匹配
模板题:给定一个邻接矩阵,然后求二分图的最大权匹配。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 310;
const int inf = 0x3f3f3f3f;
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int n, match[N]; // 匈牙利算法必备
bool va[N], vb[N]; // 记录 va 与 vb 是否遍历过
ll upd[N], w[N][N], delta; // 计算顶标修改值
ll la[N], lb[N]; // KM 算法的顶标
bool dfs(int u) {
va[u] = 1;
for(int v = 1; v <= n; v++) {
if (vb[v]) continue;
if (la[u] + lb[v] - w[u][v] == 0) {
vb[v] = 1;
if (!match[v] || dfs(match[v])) {
match[v] = u;
return 1;
}
} else upd[v] = min(upd[v], la[u] + lb[v] - w[u][v]);
}
return 0;
}
ll km() {
for (int i = 1; i <= n; i++) {
la[i] = -inf;
lb[i] = 0;
for (int j = 1; j <= n; j++) {
la[i] = max(la[i], w[i][j]);
match[j] = 0;
}
}
for (int i = 1; i <= n; i++) {
while (1) {
memset(va, 0, sizeof(va));
memset(vb, 0, sizeof(vb));
memset(upd, inf, sizeof(upd));
if (dfs(i)) break;
delta = inf;
for (int j = 1; j <= n; j++)
if (!vb[j]) delta = min(delta, upd[j]);
for (int j = 1; j <= n; j++) {
if (va[j]) la[j] -= delta;
if (vb[j]) lb[j] += delta;
}
}
}
ll ans = 0;
for(int i = 1; i <= n; i++)
ans += w[match[i]][i];
return ans;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
while (cin >> n && n != -1) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> w[i][j];
cout << km() << "\n";
}
return 0;
}

浙公网安备 33010602011771号