KM 算法

二分图的最大权完美匹配

不妨先看一些定义:

  • 顶标

    全称为“顶点标记值”。记左部点 $i$ 的顶标为 $lx_i$,右部点 $j$ 的顶标为 $ly_j$,那么要求顶标要满足 $lx_i+ly_j \ge w(i,j)$,其中 $w(i,j)$ 表示 $i$ 到 $j$ 那条边的边权。

  • 相等子图

    即原图中满足 $lx_i+ly_j = w(i,j)$ 的边构成的子图。

    • 结论:若相等子图中存在完美匹配,则这个完美匹配就是二分图的带权最大完美匹配。
    • 证明:完美匹配的边权和为 $(\sum_{i=1}^{n}lx_i)+(\sum_{i=1}^{n}ly_i)$。由于 $\forall i,j$,$lx_i+ly_j \ge w(i,j)$,故而整张二分图中,不存在有其他匹配的边权值和大于当前匹配的边权之和。

我们利用上述结论,成功的把问题转换为了:求一组合适的顶标,使得相等子图存在完美匹配。

有一种做法就是对于每一个 $i$,调整顶标使得有新边加入以达到能在相等子图里找到增广路、匹配的目的。若每一个 $i$ 都在上一个的基础上执行了这样的操作,便能得到一种完美匹配的方法。

  • 不妨宏观匈牙利算法来明确一些性质:每次从未匹配边开始,以未匹配边、匹配边交错的形式形成一颗交错树,其中左部节点沿非匹配边访问到右部节点,右部节点沿匹配边找到原来匹配的左部节点。且未找到增广路之前,不会改变已有的匹配。

    假设目前的相等子图里找不到增广路了,我们需要一种调整顶标的方法,使得原图中存在的边现在有,且至少一条原来没有的边新图有了。

  • 调整方法:我们还是设左部节点为 $i$,右部节点为 $j$。之后把所有的所有访问到过的 $lx_i$ 加上 $v$、$ly_j$ 减去 $v$。

    进一步转化为如何求一个合适的 $v$,使得满足上述条件。

    因为左部节点的访问是被动的(即被右部节点沿匹配边访问到),且考虑 $i \notin \text{交错树},j \notin \text{交错树}$ 没有意义($\notin \text{交错树}$ 的顶标都没有变化),所以我们只用考虑 $i \in \text{交错树}$,且只用考虑有连边的 $i,j$。

    分情况来讨论:

    • $j \in \text{交错树}$。

    那么连接两点的这条边显然是匹配边,那么两边一个加 $v$,一个减 $v$,显然 $a_i+b_j=w(i,j)$,依旧是新相等子图中的边。

    • $j \notin \text{交错树}$。

    在所有访问过的 $lx$ 加 $v$,$ly$ 减 $v$ 之后满足顶标的性质——$lx_i+ly_j \ge w(i,j)$,当且仅当 $v \le lx_i+ly_j-w(i,j)$。若在所有的 $lx_i+ly_j-w(i,j)$ 中取最小的值作为 $v$ 的值,那么就能达成“既满足顶标的性质,又能至少有一条边加入新相等子图”。

具体的,有以下步骤:

  • 枚举点 $i$

    思想在于不断通过调整顶标加入边以使得 $i$ 能找到匹配。

    故重复以下过程直至 $i$ 找到匹配。

    • 进行寻找增广路同时获得合适的 $v$。

      时间复杂度 $O(n+m)$。

    • 调整顶标。

      时间复杂度 $O(n)$。

时间复杂度 $O(n(n+m))=O(n^2+nm)$。

#include <bits/stdc++.h>
#define FL(i, a, b) for(int i = (a); i <= (b); i++)
#define FR(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
typedef long long ll;
const int N = 510; const ll INF = 1e17;
int n, m, vis[N], pre[N], match[N];
ll lx[N], ly[N], w[N][N], slack[N];
void aug(int s){
    int p, id = 0, q; ll v; match[0] = s;
    FL(i, 1, n) slack[i] = INF, pre[i] = 0;
    while(match[q]){
        p = match[q = id], vis[q] = 1, v = INF;
        FL(i, 1, n) if(!vis[i]){
            if(slack[i] > lx[p] + ly[i] - w[p][i])
                slack[i] = lx[p] + ly[i] - w[p][i], pre[i] = q;
            if(slack[i] < v) v = slack[id = i];
        }
        FL(i, 0, n){
            if(vis[i]) lx[match[i]] -= v, ly[i] += v;
            else slack[i] -= v;
        }
    }
    for(; q; q = pre[q]) match[q] = match[pre[q]];
}
ll KM(){
    FL(i, 1, n){
        lx[i] = -INF, ly[i] = 0;
        FL(j, 1, n) lx[i] = max(lx[i], w[i][j]);
    }
    FL(i, 1, n) memset(vis, 0, sizeof(vis)), aug(i);
    ll ret = 0; FL(i, 1, n) ret += lx[i] + ly[i];
    return ret;
}
int main(){
    scanf("%d%d", &n, &m);
    FL(i, 1, n) FL(j, 1, n) w[i][j] = -INF; 
    FL(i, 1, m){
        int u, v; ll c;
        scanf("%d%d%lld", &u, &v, &c);
        w[u][v] = max(w[u][v], c);
    }
    printf("%lld\n", KM());
    FL(i, 1, n) printf("%d ", match[i]);
    return 0;
}

二分图的最大权匹配

注意到这个模型和上述的二分图最大权完美匹配的差别为:它不要求是完美匹配。故而考虑删去所有负权边,再把不存在边的点对之间连上权值为 $0$ 的边。最后跑 $\text{KM}$ 即可。

posted @ 2023-08-12 20:01  徐子洋  阅读(47)  评论(0)    收藏  举报  来源