KM 算法

二分图的最大权完美匹配

不妨先看一些定义:

  • 顶标

    全称为“顶点标记值”。记左部点 i 的顶标为 lxi,右部点 j 的顶标为 lyj,那么要求顶标要满足 lxi+lyjw(i,j),其中 w(i,j) 表示 ij 那条边的边权。

  • 相等子图

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

    • 结论:若相等子图中存在完美匹配,则这个完美匹配就是二分图的带权最大完美匹配。
    • 证明:完美匹配的边权和为 (i=1nlxi)+(i=1nlyi)。由于 i,jlxi+lyjw(i,j),故而整张二分图中,不存在有其他匹配的边权值和大于当前匹配的边权之和。

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

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

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

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

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

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

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

    分情况来讨论:

    • j交错树

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

    • j交错树

    在所有访问过的 lxvlyv 之后满足顶标的性质——lxi+lyjw(i,j),当且仅当 vlxi+lyjw(i,j)。若在所有的 lxi+lyjw(i,j) 中取最小的值作为 v 的值,那么就能达成“既满足顶标的性质,又能至少有一条边加入新相等子图”。

具体的,有以下步骤:

  • 枚举点 i

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

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

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

      时间复杂度 O(n+m)

    • 调整顶标。

      时间复杂度 O(n)

时间复杂度 O(n(n+m))=O(n2+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 的边。最后跑 KM 即可。

posted @   徐子洋  阅读(17)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示