最大權完美匹配:KM算法的優化


KM算法

設二分圖的兩部分點集分別為 $X=\{X_1, X_2, \ldots, X_n\}$ 和 $Y=\{Y_1, Y_2, \ldots, Y_m\}$, $\left<X_i, Y_j\right>$ 的邊權為 $w_{ij}$.

給兩部分點集分別賦點權 $\{A_i\}, \{B_i\}$, 使得 $A_i+B_j \ge w_{ij}$. 取等的邊的生成子圖叫做相等子圖。那么相等子圖的完美匹配就是最大權匹配。我們需要適當選取權值,使相等子圖有完美匹配。

算法流程如下:

  1. 令 $X=\emptyset$, $B_j=0$.
  2. 對於 $k=1, 2, \ldots, n$:
    1. 在 $X$ 中加入 $X_k$, 取 $A_k=\max\{w_{kj}\}$.
    2. 搜索一條從 $X_k$ 到 $Y$ 中未匹配點的交錯路。
    3. 如果交錯路存在:
      1. 修改匹配。
      2. 令 $k \gets k+1$ 重復 $(2)$.
    4. 如果交錯路不存在,記搜索樹(此時叫做交錯樹 $M$)頂點集與 $X$ 的交為 $X'$, 與 $Y$ 的交為 $Y'$.
      1. 取 $d=\min\{A_i+B_j-w_{ij} \mid X_i \in X', \left<X_i, Y_j\right> \notin M\}$.
      2. 將 $X'$ 中的所有點權減小 $d$, $Y'$ 中的所有點權增大 $d$. 此時 $A_i+B_j \ge w_{ij}$ 仍然滿足,交錯樹上的邊仍然屬於相等子圖,且至少有一條與交錯樹相鄰的相等子圖中的邊。
      3. 重復 $(2.2)$.

KM算法需要對每回修改后的子圖重新搜索交錯路,時間復雜度可達 $O(n^4)$.

優化

由於原交錯樹仍然是可用的,我們考慮不重新搜索交錯路,而是在原交錯樹上直接擴展新邊。

具體地,我們在加入 $X_k$ 的過程中,由 $X_k$ 開始擴展交錯樹。

對於每個 $Y'$ 中的點,記錄它的父結點;對於 $Y_j \in Y \setminus Y'$, 我們維護 $slack_j=\min\{A_i+B_j-w_{ij}\ \mid X_i \in X'\}$, 取得最小值的 $X_i$ 是它的准父結點(有多個任取一個)。

擴展的流程是:

  1. 取出 $d=\min\{slack_j\}$. 特別地,當 $d=0$ 是就是繼續沿原相等子圖擴展。
  2. 將 $X'$ 中的所有點權減小 $d$, $Y'$ 中的所有點權增大 $d$, 相應地,所有 $slack$ 減去 $d$.
  3. 將 $slack_j$ 取得最小值的 $Y_j$(有多個任取一個)加入 $Y'$, $Y_j$ 的父結點就是原准父結點。
  4. 若所加入的 $Y_j$ 還未匹配,說明已經找到交錯路,順着從 $X_k$ 到 $Y_j$ 的路徑匹配。
  5. 若所加入的 $Y_j$ 已經匹配,將其匹配點加入 $X'$, 更新 $Y'$ 中各點的 $slack$ 和准父結點,重復擴展流程。

在實現上,我們記 $match_j$ 表示 $Y_j$ 的匹配點,$pre_j$ 表示 $Y_j$ 的(准)父結點的匹配點,不存在記為 $0$.

示例代碼

示例:假設 $n=m \le 500$, 所有邊權和答案絕對值小於 $10^{18}$. 輸入 $n$ 和邊權,輸出 $Y_j$ 的匹配點。

 1 #include <bits/stdc++.h>
 2 const int N=501;
 3 int n, match[N];
 4 bool vis[N];
 5 long long f[N][N], a[N], b[N], slack[N], pre[N];
 6 template<class T1, class T2> bool cmin(T1 &a, const T2 &b)
 7 {
 8     return b<a?(a=b, true):false;
 9 }
10 template<class T1, class T2> bool cmax(T1 &a, const T2 &b)
11 {
12     return a<b?(a=b, true):false;
13 }
14 int main()
15 {
16     scanf("%d", &n);
17     for(int i=1; i<=n; ++i) {
18         for(int j=1; j<=n; ++j)
19             scanf("%d", f[i]+j);
20         a[i]=*std::max_element(f[i]+1, f[i]+n+1);
21     }
22     for(int i=1; i<=n; ++i) {
23         int x=0, cho;
24         memset(vis+1, 0, n);
25         memset(pre+1, 0, n*sizeof(int));
26         memset(slack+1, 63, n*sizeof(long long));
27         match[0]=i;
28         do {
29             int u=match[x];
30             long long min=1e18;
31             vis[x]=true;
32             for(int v=1; v<=n; ++v) {
33                 if(!vis[v]) {
34                     long long t=a[u]+b[v]-f[u][v];
35                     if(cmin(slack[v], t))
36                         pre[v]=x;
37                     if(cmin(min, slack[v]))
38                         cho=v;
39                 }
40             }
41             for(int j=0; j<=n; ++j) {
42                 if(vis[j]) {
43                     a[match[j]]-=min;
44                     b[j]+=min;
45                 } else
46                     slack[j]-=min;
47             }
48             x=cho;
49         } while(match[x]);
50         while(x) {
51             match[x]=match[pre[x]];
52             x=pre[x];
53         }
54     }
55     for(int i=1; i<=n; ++i)
56         printf("%d%c", match[i], " \n"[i==n]);
57     return 0;
58 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM