【图论】KM算法

https://github.com/lydrainbowcat/tedukuri/blob/master/配套光盘/正文包含的程序片段/0x68 bipartite_graph_match.cpp

// 非递归版,O(n^3),UOJ80 https://uoj.ac/problem/80
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int N = 405;
int w[N][N]; // 边权
long long la[N], lb[N], upd[N]; // 左、右部点的顶标
bool va[N], vb[N]; // 访问标记:是否在交错树中
int match[N], last[N]; // 右部点匹配了哪一个左部点
int n, nl, nr, m, boy[N];

void search(int x) {
    memset(va, 0, sizeof(va));
    memset(vb, 0, sizeof(vb));
    memset(last, 0, sizeof(last));
    for (int j = 1; j <= n; j++) upd[j] = 1ll << 60;
    int y = 0;
    match[0] = x;
    do {
        va[x] = true;
        long long delta = 1ll << 60; // inf
        int next_y = 0;
        for (int j = 1; j <= n; j++)
            if (!vb[j]) {
                if (upd[j] > la[x] + lb[j] - w[x][j]) {
                    upd[j] = la[x] + lb[j] - w[x][j];
                    last[j] = y;
                }
                if (upd[j] < delta) {
                    delta = upd[j];
                    next_y = j;
                }
            }
        // 当delta=0时,相当于沿着相等子图向下搜索一层
        // 当delta>0时,相当于直接回到最小边(新加入相等子图的边)处开始搜索
        if (delta) {
            for (int j = 1; j <= n; j++) { // 修改顶标
                if (va[j]) la[j] -= delta;
                if (vb[j]) lb[j] += delta;
                else upd[j] -= delta;
            }
        }
        vb[y = next_y] = true;
    } while (x = match[y]); // 直到找到增广路
    while (y) {
        match[y] = match[last[y]];
        y = last[y];
    }
}

long long KM() {
    for (int i = 1; i <= n; i++) {
        la[i] = -(1 << 30); // -inf
        lb[i] = 0;
        for (int j = 1; j <= n; j++)
            la[i] = max(la[i], 1ll * w[i][j]);
    }
    for (int i = 1; i <= n; i++) search(i);
    long long ans = 0;
    for (int i = 1; i <= n; i++)
        ans += w[match[i]][i];
    return ans;
}

int main() {
    cin >> nl >> nr >> m;
    n = max(nl, nr);
    for (int i = 1; i <= m; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        w[x][y] = z;
    }
    cout << KM() << endl;
    for (int i = 1; i <= nr; i++)
        if (match[i] && w[match[i]][i]) boy[match[i]] = i;
    for (int i = 1; i <= nl; i++) printf("%d ", boy[i]);
    puts("");
}

posted @ 2021-02-20 09:53  purinliang  阅读(101)  评论(0编辑  收藏  举报