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("");
}