问题
给定一个二分图,左部有 (n) 个点,右部有 (m) 个点,边 ((u_i, v_j)) 的边权为 (A_{i,j})。求该二分图的最大权完美匹配。
转化
问题可以写成线性规划的形式,设 (f_{i, j}) 表示匹配中是否有边 ((u_i, v_j)),求
转为对偶问题:
在这个问题中,(hu) 和 (hv) 又称作“顶标”。
分析
根据互补松弛定理,如果 (f_{i,j}=1),则有 (hu_i+hv_j=A_{i,j})。这给出了判定一组顶标是否最优的方式:
一组顶标 (hu, hv) 最优,当且仅当子图 (H = left{(i, j) mid hu_i + hv_j = A_{i,j}right}) 存在完美匹配。
做法
首先给出一个满足 (hu_i+hv_j ge A_{i,j}) 的顶标(不一定最优)(例如,令 (hu_i = hv_j = +infty)),并维护对应的匹配。然后尝试在不破坏条件的情况下修改顶标,使得匹配可以被增广。
具体而言,遍历 (i) 从 (1) 到 (n),每次尝试将 (i) 加入到匹配中(类似于求二分图最大匹配的匈牙利算法)。我们可以求出以 (i) 为根的交错树,如果已经存在增广路,那么直接增广便是,否则我们需要修改顶标来使交错树生长。设交错树中的左部点集为 (S),右部点集为 (T),那么必有 (|S| = |T| + 1)(交错树的性质)。令 (Delta = min{hu_i + hv_j - A_{i,j} mid i in S land j notin T}),那么将 (S) 中的点顶标减去 (Delta),将 (T) 中的点顶标加上 (Delta),可以验证得到的新的顶标依然满足 (hu_i+hv_j ge A_{i,j}) 的限制,且原图中的匹配一定包含于对应的新图 (H') 中,此外,新图中至少增加了一条边,这使得我们的交错树可以继续生长,直到找到增广路为止。
代码(洛谷 P6577)
#define DEBUG 0 #include <cstdio> #include <cassert> #include <vector> template <class T> using Arr = std::vector<T>; #define int long long const int INF = 1e8; signed main() { int n, m; scanf("%lld%lld", &n, &m); struct edge_t { int v, w; }; Arr<Arr<edge_t>> e(2 * n); for (int i = 0; i < m; ++i) { int l, r, w; scanf("%lld%lld%lld", &l, &r, &w); --l; r += n - 1; e[l].push_back({r, w}); e[r].push_back({l, w}); } Arr<int> match(2 * n, -1), h(2 * n, INF); for (int i = 0; i < n; ++i) { Arr<int> vis(2 * n, false); // 是否在当前求出的交错树中,即 $S cup T$ Arr<int> upd(2 * n, n * INF); // 最小的 Δ 值 Arr<int> from(2 * n, -1); // 维护增广路所用,即交错树上的父亲 int p = i; vis[p] = true; int d, dp; // Δ 及其对应的 j while (true) { d = n * INF, dp = -1; // 求出 Δ for (auto [to, w] : e[p]) if (!vis[to]) { int delta = h[p] + h[to] - w; if (delta < upd[to]) upd[to] = delta, from[to] = p; } for (int j = n; j < 2 * n; ++j) if (!vis[j] && upd[j] < d && from[j] != -1) d = upd[j], dp = j; assert(~dp); // 修改顶标 h[i] -= d; for (int j = n; j < 2 * n; ++j) if (vis[j]) h[j] += d, h[match[j]] -= d; else upd[j] -= d; // 找到增广路 if (match[dp] == -1) break; // 生长交错树 vis[dp] = true; vis[match[dp]] = true; p = match[dp]; } // 增广 while (~dp) { match[dp] = from[dp]; int tmp = match[from[dp]]; match[from[dp]] = dp; dp = tmp; } } long long ans = 0; for (int i = 0; i < 2 * n; ++i) ans += h[i]; printf("%lldn", ans); for (int i = n; i < 2 * n; ++i) printf("%lld ", match[i] + 1); puts(""); return 0; }