[图论] KM 算法
KM 算法
二分图最大权完美匹配之所以不容易求解,是因为它不能像最大匹配一样贪心寻找增广路,或者说难以构造一种直接贪心的策略。这时候我们考虑先给原问题确定一个子图,并想方设法保证子图上可以用最大匹配贪心求解,如果这个子图包含了最大权完美匹配,那么答案能直接用最大匹配求解了。
不难发现该算法的难点是保证子图可以贪心这一点。我们用以下方法解决这个问题:
给每个点赋一个顶标值,用
这是因为任意一个完美匹配的权值是
于是问题转化为如何给顶标数组
我们发现对于后一种情况,存在以下方法修改顶标使得增广路可能增加:
- 确定定值
dt 。对于S 中的所有点,使其顶标减去dt ;对于T 中的所有点,使其定标增加dt 。
这样
最后的小问题就是如何确定
令
我们用 BFS 写法实现,这样可以保证对于每个点,最多扩容
如果你用 DFS 实现,那么首先你无法做到求解最大匹配和扩容并行,因此复杂度不是并列的,而是
代码
实现时注意一点问题:
对于没有边的
同理,
/* N_Cat x K_crane */
#include <bits/stdc++.h>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
const int N = 510;
const long long INF = 1e10;
int n, m;
int px[N], py[N], rec[N]; long long lx[N], ly[N], mp[N][N], slack[N]; long long sum;
queue < int > q; bool vx[N], vy[N];
inline void get(int pos) // 更新增广路
{
int nxt = 0;
while(pos)
{
nxt = px[rec[pos]];
px[rec[pos]] = pos;
py[pos] = rec[pos];
pos = nxt;
}
}
inline void bfs(int st)
{
memset(vx, 0, sizeof(vx));
memset(vy, 0, sizeof(vy));
for(int i = 1; i <= n; ++i) slack[i] = 3 * INF;
while(!q.empty()) q.pop(); q.push(st);
while(true)
{
while(!q.empty())
{
int pos = q.front(); q.pop(); vx[pos] = true;
for(int to = 1; to <= n; ++to)
{
if(vy[to]) continue; // 之前确定了增广不了就没必要再试一次了
if(lx[pos] + ly[to] - mp[pos][to] < slack[to])
{
slack[to] = lx[pos] + ly[to] - mp[pos][to];
rec[to] = pos;
if(slack[to] == 0) // 在交错树内部
{
vy[to] = true;
if(!py[to]) {get(to); return ;} // 找到增广路,结束该轮算法
else q.push(py[to]); // 对其匹配的左部点继续尝试增广
}
}
}
}
long long dt = INF;
for(int i = 1; i <= n; ++i) if(!vy[i]) dt = min(dt, slack[i]);
for(int i = 1; i <= n; ++i)
{
if(vx[i]) lx[i] -= dt; // 在 S 里
if(vy[i]) ly[i] += dt; // 在 T 里
else slack[i] -= dt; // 在 T' 里
}
for(int i = 1; i <= n; ++i) // 看看调整顶标之后是不是有新的增广方案
{
if(!vy[i] && !slack[i])
{
vy[i] = true;
if(!py[i]) {get(i); return ;}
else q.push(py[i]);
}
}
}
}
int main()
{
// freopen("text.in", "r", stdin);
// freopen("prog.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
mp[i][j] = -INF;
for(int i = 1, x, y, w; i <= m; ++i)
{
cin >> x >> y >> w;
mp[x][y] = max(mp[x][y], 1ll * w);
}
for(int i = 1; i <= n; ++i) lx[i] = INF;
for(int i = 1; i <= n; ++i) bfs(i);
for(int i = 1; i <= n; ++i) sum += mp[i][px[i]];
cout << sum << '\n';
for(int i = 1; i <= n; ++i) cout << py[i] << " "; cout << '\n';
return 0;
}
/*
*/
其他
KM 算法带给我们的并不只是一个求解二分图的“自动程序”。
正如 Johnson 算法启发我们在网络流中使用势能法优化掉多轮 SPFA 一样,KM 算法带给我们一个结论:
- 考虑如下线性规划问题:给
x, y 两数组赋值,满足对于任意i, j ,均有x_i + y_j \geq w_{i, j} ,要求最小化\sum x + \sum y 。
对于这个问题,现在你无需用线性规划的理论去对偶转化它,直接用 KM 算法的组合意义就能证明这个最小值取到以
学完 KM 之后,你可以尝试一下这道题。