分析
我们会发现这道题本质上是一个线性规划问题:
\[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; }
   |