分析

我们会发现这道题本质上是一个线性规划问题:

\[h_i + l_j \ge w_{i,j}\] \[h_i \ge 0 (\forall 1 \le i \le n)\] \[l_j \ge 0 (\forall 1 \le j \le n)\] \[\operatorname{minimize} \sum_{i = 1}^{n} h_i + \sum_{j = 1}^{n} l_j\]

我们考虑它的对偶线性规划。

对偶线性规划

所谓对偶线性规划是指:

  • 原问题中的每个变量都变为对偶问题中的一个限制条件;
  • 原问题中的每个限制条件都变为对偶问题中的一个变量;
  • 原问题若是求目标函数的最大值,则对偶问题是求最小值,反之亦然。

另外还有“强对偶定理”,通俗的来说就是原问题的最优解等于对偶问题的最优解。

例如本题的对偶线性规划为:

\[\sum_{j = 1}^{n} y_{i, j} \le 1 (\forall 1 \le i \le n) \] \[\sum_{i = 1}^{n} y_{i, j} \le 1 (\forall 1 \le j \le n) \] \[y_{i, j} \ge 0 \] \[\operatorname{maximize} \sum_{(i, j)} w_{i, j} y_{i, j}\]

注意到这正好是二分图最大权匹配问题,所以可以用 KM 算法解决。

(绕了一个大圈……事实上 KM 算法本身的原理就是解决二分图最大权匹配问题的对偶问题“二分图最小顶标和问题”,即本问题。换句话说,KM 算法本身就会保证算法结束后顶标最小。)

代码

KM 算法的模版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 500 + 5;
const int INF = 1e9;

int e[MAXN][MAXN], la[MAXN], lb[MAXN], slack[MAXN];
int match[MAXN], last[MAXN];
bool va[MAXN], vb[MAXN];
int n;

bool dfs(int u, int fa) {
va[u] = true;
for(int v = 1; v <= n; v++) {
if(vb[v]) continue;
if(la[u] + lb[v] == e[u][v]) {
vb[v] = true;
last[v] = fa;
if(!match[v] || dfs(match[v], v)) {
match[v] = u;
return true;
}
} else if(slack[v] > la[u] + lb[v] - e[u][v]) {
slack[v] = la[u] + lb[v] - e[u][v];
last[v] = fa;
}
}
return false;
}

void KM() {
fill(match + 1, match + n + 1, 0);
fill(last + 1, last + n + 1, 0);
for(int i = 1; i <= n; i++) {
la[i] = -INF;
lb[i] = 0;
for(int j = 1; j <= n; j++) {
la[i] = max(la[i], e[i][j]);
}
}
for(int i = 1; i <= n; i++) {
fill(va + 1, va + n + 1, false);
fill(vb + 1, vb + n + 1, false);
fill(slack + 1, slack + n + 1, INF);
int st = 0; match[st] = i;
while(match[st]) {
if(dfs(match[st], st)) break;
int delta = INF;
for(int j = 1; j <= n; j++) {
if(!vb[j] && delta > slack[j]) {
delta = slack[j];
st = j;
}
}
for(int j = 1; j <= n; j++) {
if(va[j]) la[j] -= delta;
if(vb[j]) lb[j] += delta; else slack[j] -= delta;
}
vb[st] = true;
}
while(st) {
match[st] = match[last[st]];
st = last[st];
}
}
}

int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &e[i][j]);
}
}
KM();
int ans = 0;
for(int i = 1; i <= n; i++) {
printf("%d ", la[i]);
ans += la[i];
}
printf("\n");
for(int i = 1; i <= n; i++) {
printf("%d ", lb[i]);
ans += lb[i];
}
printf("\n");
printf("%d\n", ans);
}

return 0;
}