# 匈牙利演算法 Hungarian Algorithm 匈牙利演算法又稱 Kuhn-Munkres 演算法 (簡稱 KM) 是用於尋找二分圖最大權匹配。在打競程時,不常遇到這類題目,就算遇到也算是很好發揮,把問題轉化再套模板即可。然而,其原理卻相當複雜,理解原理後也有利於理解如何轉化匹配類型的題目,因此這篇會把整套原理說明清楚 閱讀之前可以先看前篇三篇[匹配與霍爾定理](https://hackmd.io/@ShanC/Matching-and-Halls-Theorem)、[Kőnig 定理](https://hackmd.io/@ShanC/konig-theorem)、[擴充路徑演算法](https://hackmd.io/@ShanC/Augmenting-Path-Algorithm),有利於了掌握匹配問題的脈絡 ## 最小權點覆蓋 Minimum Weighted Cover ### 情境 1 有一個農業公司擁有 $n$ 塊玉米田與 $n$ 個加工廠。每一塊田地可以生產的玉米產量各有不同,每一個加工廠的產能也不同。從第 $i$ 塊田地運送到第 $j$ 個加工廠可以賺到 $w_{i,j}$ 元。公司想要找一組好的匹配使獲利最大化 我們可以把他們的關係化成二分圖,田地 $X=\{x_1, x_2, ..., x_n\}$、加工廠 $Y=\{y_1, y_2,...,y_n\}$,將獲利 $w_{i, j}$ 作為權重擺在邊 $x_i~y_j$ 上,這問題就會轉化成二分圖最大權匹配問題 ### 情境 2 承上述情境,有一天政府認為玉米產量過剩,因此希望可以付錢使停止生產與製造。若公司同意不繼續在第 $i$ 塊田地種玉米,那麼政府就會付 $u_i$ 元給公司;若公司同意不繼續在第 $j$ 個加工廠生產產品,那麼政府就會付 $v_j$ 元給公司。如果 $u_{i}+v_{j} < w_{i, j}$,則公司會繼續使用邊 $x_i~y_j$ 來獲利,不會接受政府的錢。為了可以停止生產與製造,政府必須花費 $u_{i}+v_{j} \geq w_{i, j}$ 對於所有 $i, j$。政府希望可以最小化 $\sum u_i+\sum v_j$ 我們稱這問題是一個最小權點覆蓋問題 ### 權重點覆蓋 Weighted Covering 一組權重覆蓋,是一組標記 (label) 的選擇,分別是 $u_1, u_2, ..., u_n$ 與 $v_1, v_2, ..., v_n$。這些組選擇會使得 $u_{i}+v_{j} \geq w_{i, j}$。而一個覆蓋 $(u, v)$ 的花費 (cost) $c(u, v)$ 是 $\sum u_i+\sum v_j$。最小權點覆蓋 (minimum weighted cover) 就是指其中最小的花費。對於一個有最大權完美匹配 $M$ 的二分圖來說,設 $w(M)$ 為最大權重,則 $c(u, v) \geq w(M)$ ### 舉例說明 在下圖中,可以看到一張圖有邊權與點權,點權的加總是 $4+8+3+(-2)=13$,而最大匹配權重 $w(M)=6+6=12$。不難發現 $13 = c(u, v) \geq w(M)=12$,且 $\begin{cases}v_1+u_1=4+3\ge w_{1,1}=6 \\ v_2+u_1=8+3\ge w_{2,1}=9 \\ v_2+u_2=8+(-2)\ge w_{2,2}=6\end{cases}$ <img src="https://hackmd.io/_uploads/SJZiWgauyg.png" style="margin: 0 auto; display: block; width: 400px"> $~$ 若把 $v_1$ 的數值減少為 $3$,那我們依然會得到 $c(u, v) \geq w(M)$,且 $v_1+u_1\ge w_{1,1}$ <img src="https://hackmd.io/_uploads/S1DTMgpdke.png" style="margin: 0 auto; display: block; width: 400px"> $~$ 當一張圖並沒有點權時,我們可以自由設計點權,使對於所有 $i, j$, $u_{i}+v_{j} \geq w_{i, j}$,且 $c(u, v) \geq w(M)$。在匈牙利演算法當中,我們會運用到這個性質 ## Egerváry 定理 ### 簡介 Egerváry 定理是 Kőnig 定理的拓展。簡單來說,Jenő Egerváry 引入了權重的概念到 Kőnig 定理當中,因此這兩定理又被稱為 Kőnig-Egerváry 定理 ### 條件 Egerváry 定理必須符合三個條件: 1. 二分圖 2. 非負邊權 3. 存在一個匹配使所有點都被匹配到 ### Egerváry 定理 對於一張帶邊權的二分圖,若其邊權不為負,且存在一個匹配 $M$ 使所有點都被匹配到 (即完美匹配),則最小權點覆蓋問題等價於最大權匹配問題 換句話說,有一帶權點覆蓋 $c(u, v)=w(M)$ 若且唯若 $M$ 包含了邊 $x_i~y_j$ 使 $u_i+v_j=w_{i, j}$,在此情況下有最佳解 ## 等效子圖 Equality Subgraph 一張有權二分圖的等效子圖 $G_{u, v}$ 是 $K_{n, n}$ 的生成圖 (即包含所有節點的圖),其所有邊皆符合 $u_i+v_j=w_{i, j}$。只要我們調整點權,就會產生不同的等效子圖 <center class = "half"> <img src="https://hackmd.io/_uploads/Hyn2-LCd1e.png" style="width: 350px"> <img src="https://hackmd.io/_uploads/HkPJzURuJe.png" style="width: 350px"> </center> <p class="text-center"> 右圖為左圖的等效子圖 </p> ## 匈牙利演算法 Hungarian Algorithm 有了以上材料,我們可以來說明匈牙利演算法到底如何運行 ### 想法 根據 Egerváry 定理的描述,其實整個演算法要達到的目標僅有兩個: - 更新連邊,使邊權最大化 - 更新點權,使點權最小化 要達成這些目標,我們需要不斷的調整權重,直到 $c(u, v)=w(M)$ ### 演算法 先定義一個名詞 excess,是指對於 $i, j$,$u_i+v_j-w_{i,j}$ #### 初始化 令 $(u, v)$ 為一個點覆蓋,使 $u_{i} = max_{j}\{w_{i,j}\}$、$v_j=0$ #### 迭代 找出等效子圖 $G_{u,v}$ 中的最大匹配 $M$。若 $M$ 是一個完美匹配,則停止迭代,並回傳 $M$ 為最大權完美匹配。否則,令 $Q$ 是在 $G_{u, v}$ 中大小為 $|M|$ 的點覆蓋,再令 $R=X\cap Q$、$T=Y\cap Q$,又令最小 excess 為 $\epsilon=min\{u_i+v_j-w_{i,j}: x_i\in X-R, ~y_j\in Y-T\}$。將 $x_i\in X-R$ 中的 $u_i$ 減去 $\epsilon$,且 $y_j\in T$ 中的 $v_j$ 加上 $\epsilon$,然後再次迭代 其中,尋找最大匹配 $M$ 的演算法,可以使用擴充路徑演算法。從另一角度來看,擴充路徑演算法算是匈牙利演算法的子程式 ### 舉例說明 註 : 這邊以下所使用的 $x, y$ 為節點, $u, v$ 分別為節點 $x, y$ 的點權 首先,將 $v_j$ 設為 $0$。為了要符合 $u_{i}+v_{j} \geq w_{i, j}$,我們必須挑選相鄰節點 $x_i$ 最大邊權的邊。換句話說,就是設 $u_{i} = max_{j}\{w_{i,j}\}$ 我們可以觀察下圖其中一個節點 $v_1$ 為什麼要選擇 $9$,而不是 $6$。因為選擇 $9$,才會符合 $u_{i}+v_{j} \geq w_{i, j}$ <img src="https://hackmd.io/_uploads/H1XLQIAdyx.png" style="margin: 0 auto; display: block; width: 400px"> $~$ 接下來下一個步驟,我們需要去找出等效子圖 $G_{u, v}$。並找出等效子圖中的最大匹配 $M$。會發現右圖的最大基數匹配小於 $n$,因此不是最大權匹配,在這種情況需要繼續迭代 <center class = "half"> <img src="https://hackmd.io/_uploads/H1XLQIAdyx.png" style="width: 350px"> <img src="https://hackmd.io/_uploads/B1vL5LCOkx.png" style="width: 350px"> </center> $~$ 我們可以找出大小為 $|M|$ 的獨立集 $Q = \{x_1, x_2, x_3, y_1\}$,其中 $R=\{x_1, x_2, x_3\}$、$T=\{y_1\}$。由此可以找出 $X-R=\{x_4,x_5\}$、$Y-T=\{y_2, y_3, y_4, y_5\}$,因此算出 $\epsilon=1$。我們將 $X-R=\{x_4,x_5\}$ 的點權減去 $\epsilon$、$T=\{y_1\}$ 的點權加上 $\epsilon$,得到下左圖的新權重,以及右圖的新等效子圖與新的匹配。由於此時的匹配並不是完美匹配,我們繼續迭代 可以注意的是,此時的 $\sum u_i+\sum v_j=31$,而 $w(M)=28$ <center class = "half"> <img src="https://hackmd.io/_uploads/SJbwxD0_1l.png" style="width: 350px"> <img src="https://hackmd.io/_uploads/rJaIZPAdJg.png" style="width: 350px"> </center> $~$ 我們可以再找出大小為 $|M|$ 的獨立集 $Q=\{x_1, x_3, y_1, y_2\}$,其中 $R=\{x_1, x_3\}$、$T=\{y_1, y_2\}$。由此可以找出 $X-R=\{x_2,x_4, x_5\}$、$Y-T=\{y_3, y_4, y_5\}$,因此算出 $\epsilon=2$。我們將 $X-R=\{x_2,x_4,x_5\}$ 的點權減去 $\epsilon$、$T=\{y_1, y_2\}$ 的點權加上 $\epsilon$,得到下左圖的新權重,以及右圖的新等效子圖與新的匹配 可以注意到,此時 $\sum u_i+\sum v_j=w(M)=28$。根據 Egerváry 定理,我們已經找到了最大權完美匹配,因此可以停止迭代並回傳答案 <center class = "half"> <img src="https://hackmd.io/_uploads/S1SU4wRd1g.png" style="width: 350px"> <img src="https://hackmd.io/_uploads/BJVdEvAuJx.png" style="width: 350px"> </center> ## 程式實作匈牙利演算法 實作主要可以分為原始的 $O(n^4)$ 版跟優化過的 $O(n^3)$ 版 然後又依走訪方法不同分為 DFS 與 BFS 兩種版本 我兩種都放,這樣比較有趣一點 ### 原始算法 這版本是原本尚未經過優化的版本 在實作上,我們可以定義一個 $y$ 的鬆弛量 slack,即為程式碼中的 $\text{sy[ ]}$,且每次開始找擴充路時初始化為無窮大,這樣才能找到最小值。在尋找擴充路的過程中,檢查邊 $x_i~y_j$ 時,如果它不在等效子圖中,則讓 $\text{slack[j]}$ 變成原值與 $\text{lx[i] + ly[j] - w[i, j]}$ 的較小值。如此一來,$y$ 節點的 slack 值中的最小值作為 $\epsilon$ 值即可 需要注意的是,在運算時,$Y-T$ 的節點 slack 值都要減去 $\epsilon$ ```cpp int g[MAXN][MAXN], lx[MAXN], ly[MAXN]; // g: 鄰接矩陣, lx, ly: 標記值 int my[MAXN], sy[MAXN], n; // my: y 的配對點, sy: slack 優化 bool vx[MAXN], vy[MAXN]; // 紀錄 x, y 是否拜訪過 bool dfs(int x) { // 擴充路徑演算法 if (vx[x]) return false; vx[x] = true; for (int y = 0, excess; y < n; ++y) { if (vy[y]) continue; excess = lx[x] + ly[y] - g[x][y]; if (!excess) { // excess = 0, 代表找到了匹配邊 vy[y] = true; if (my[y] == -1 || dfs(my[y])) { my[y] = x; return 1; } } else if (sy[y] > excess) sy[y] = excess; // 找最小 excess } return false; } int hungarian() { memset(ly, 0, sizeof(int) * n); memset(my, -1, sizeof(int) * n); for (int x = 0; x < n; ++x) { lx[x] = -INF; for (int y = 0; y < n; ++y) lx[x] = max(lx[x], g[x][y]); // X 的標記值取每 row 的最大值 } for (int x = 0; x < n; x++) { for (int y = 0; y < n; y++) // 記得清成 INF sy[y] = INF; while (true) { memset(vx, 0, sizeof(bool) * n); // 先將拜訪清單清空 memset(vy, 0, sizeof(bool) * n); if (dfs(x)) // 若找到新的擴充路徑就離開 break; // 找出在 X-R 與 Y-T 之間連邊的值中 // 最小的當作 epsilon int epsilon = INF; for (int y = 0; y < n; ++y) { if (!vy[y]) epsilon = min(epsilon, sy[y]); } // 修改 x, y 標記的值 u, v for (int j = 0; j < n; ++j) { if (vx[j]) // X - R lx[j] -= epsilon; if (vy[j]) // T ly[j] += epsilon; else // Y - T sy[j] -= epsilon; } } } // 算答案 int ans = 0; for (int y = 0; y < n; ++y) ans += g[my[y]][y]; return ans; } ``` ### 原始算法時間複雜度 - 第一層迴圈執行 $n$ 次: $O(n)$ - 第二層迴圈最多 $n$ 次: $O(n)$ - 擴充路徑演算法 dfs: $O(n^2)$ 總時間複雜度: $O(n)\times O(n)\times O(n^2)=O(n^4)$ ### 優化版本 位於修改 $\text{lx, ly, sy}$ 後的地方增加了一個迴圈,用於判斷該次的修改是否產生可行的擴充路徑。對於新增加的 $\epsilon=0$ 的邊,若他有機會是擴充路徑的話,會對此邊連向的節點 $y$ 進行 BFS 判斷有沒有擴充路徑 ```cpp // 變數大概都和上面差不多 typedef long long ll; int n, mx[MXN], my[MXN], pa[MXN]; // pa: 維護路徑的東西 ll g[MXN][MXN], lx[MXN], ly[MXN], sy[MXN]; bool vx[MXN], vy[MXN]; void init(int _n) { // 初始化 n = _n; for (int i = 1; i <= n; i++) // 1-based fill(g[i], g[i] + n + 1, 0); } void addEdge(int x, int y, ll w) { // 加邊 g[x][y] = w; } void augment(int y) { // 把擴充路徑都反轉 for (int x, z; y; y = z) x = pa[y], z = mx[x], my[y] = x, mx[x] = y; } void bfs(int st) { for (int i = 1; i <= n; ++i) sy[i] = INF, vx[i] = vy[i] = false; queue<int> q; q.push(st); while (true) { while (q.size()) { // 擴充路徑演算法 int x = q.front(); q.pop(); vx[x] = true; for (int y = 1; y <= n; ++y) if (!vy[y]) { ll excess = lx[x] + ly[y] - g[x][y]; if (!excess) { pa[y] = x; if (!my[y]) { // 若沒有匹配就代表有擴充路徑 augment(y); // 所以就反轉 return; } vy[y] = true, q.push(my[y]); } else if (sy[y] > excess) pa[y] = x, sy[y] = excess; } } // 找出在 X-R 與 Y-T 之間連邊的值中 // 最小的當作 epsilon ll epsilon = INF; for (int y = 1; y <= n; ++y) if (!vy[y] && epsilon > sy[y]) epsilon = sy[y]; // 修改 x, y 標記的值 u, v for (int j = 1; j <= n; ++j) { if (vx[j]) lx[j] -= epsilon; if (vy[j]) ly[j] += epsilon; else sy[j] -= epsilon; } // 這裡就是他優化奇妙的地方 for (int y = 1; y <= n; ++y) { if (!vy[y] && sy[y] == 0) { if (!my[y]) { augment(y); return; } // 在這裡加進去後只會跑一次反轉路徑 vy[y] = true, q.push(my[y]); } } } } ll hungarian() { fill(mx, mx + n + 1, 0); fill(my, my + n + 1, 0); fill(ly, ly + n + 1, 0); fill(lx, lx + n + 1, -INF); for (int x = 1; x <= n; ++x) for (int y = 1; y <= n; ++y) lx[x] = max(lx[x], g[x][y]); for (int x = 1; x <= n; ++x) bfs(x); ll ans = 0; for (int y = 1; y <= n; ++y) ans += g[my[y]][y]; // 答案存在 my[] return ans; } ``` ### 優化版本的時間複雜度 - $\text{for}$ 迴圈執行 $n$ 次: $O(n)$ - 外層 $\text{while}$ 迴圈最多 $n$ 次: $O(n)$ - 最多 $n$ 次執行$\text{while}$: $O(n)$ - 擴充路徑演算法: $O(n^2)$ 總時間複雜度: $O(n)\times (O(n)\times O(n) + O(n^2))=O(n^3)$ ## 用矩陣說明匈牙利演算法 因為用圖來看實在太麻煩了,所以我們可以用 $n\times n$ 矩陣來處理匈牙利演算法 註 : 因為中文太複雜了我總是分不清列與行,因此我就用 row 與 column 來說明,以防自己寫錯 如上面例子,我們再來處理一次這張二分圖,但這次使用矩陣來處理 <img src="https://hackmd.io/_uploads/H1XLQIAdyx.png" style="margin: 0 auto; display: block; width: 400px"> $~$ 如上圖,我們可以得到以下鄰接矩陣,裡面每一個元素都表示邊權 $w_{i,j}$ $\begin{bmatrix} 0&0&6&9&0 \\ 0&6&1&0&0 \\ 0&1&2&0&0 \\ 8&7&0&3&5 \\ 6&0&0&0&3 \end{bmatrix}$ 我們可以定義一個 excess 矩陣 $c_{i, j}$,將每 row 都減去其中的最大值,使每一個元素皆為 $u_i+v_j-w_{i,j}$。此時,$u=\{9,6,2,8,4\}$、$v=\{0,0,0,0,0\}$ $c_{i, j}=\begin{bmatrix} 9&9&3&0&9 \\ 6&0&5&6&6 \\ 2&1&0&2&2 \\ 0&1&8&5&3 \\ 0&6&6&6&3 \end{bmatrix}$ 如果將 excess 矩陣對照等效子圖 $G_{u, v}$,會發現一組匹配會對應到一個 $0$ 的集合,使此集合沒有兩個 $0$ 共用同一個 row 或 colum,下圖以底線表示匹配邊。建議可以畫一條直線或橫線覆蓋行或列來確認之 畫直線與橫線最少的線條覆蓋所有的 $0$ 之後,會發現直線對應 $T\subset Q$、橫線對應 $R\subset Q$ 此時 $u=\{9,6,2,8,6\}$、$v=\{0,0,0,0,0\}$ $\begin{bmatrix} 9&9&3&0&9 \\ 6&0&5&6&6 \\ 2&1&0&2&2 \\ 0&1&8&5&3 \\ 0&6&6&6&3 \end{bmatrix} \longrightarrow \begin{array}{cc} & \begin{array}{ccc} T & ~ & ~ & ~ & ~ &\end{array} \\ \begin{array}{ccc} R \\ R \\ R \\ X-R \\ X-R \\ \end{array} & \left[\begin{array}{ccc} 9&9&3&\underline{0}&9 \\ 6&\underline{0}&5&6&6 \\ 2&1&\underline{0}&2&2 \\ 0&1&8&5&3 \\ \underline{0}&6&6&6&3 \end{array} \right] \end{array}$ 接下來,藉由直接對照 $c_{i, j}$, 很快就能找出 $\epsilon=1$。我們將 $X-R$ 減去 $\epsilon$,$T$ 加上 $\epsilon$,便會得到以下矩陣。藉由這個矩陣,我們可以試著用最少的線覆蓋所有 $0$ 的邊權,並找出 $R$ 與 $T$ 注意此時的 $u=\{9,6,2,7,5\}$、$v=\{1,0,0,0,0\}$ $\begin{bmatrix} 10&9&3&0&9 \\ 7&0&5&6&6 \\ 3&1&0&2&2 \\ 0&0&7&4&2 \\ 0&5&5&5&2 \end{bmatrix} \longrightarrow \begin{array}{cc} & \begin{array}{ccccc} T & T & ~ & ~ & ~ \end{array} \\ \begin{array}{ccc} R \\ X-R \\ R \\ X-R \\ X-R \\ \end{array} & \left[\begin{array}{ccc} 10&9&3&\underline0&9 \\ 7&\underline0&5&6&6 \\ 3&1&\underline0&2&2 \\ 0&0&7&4&2 \\ \underline0&5&5&5&2 \end{array} \right]\end{array}$ 接下來,對 $c_{i, j}$ 再找一次最小 excess,找出 $\epsilon=2$。我們將 $X-R$ 減去 $\epsilon$,$T$ 加上 $\epsilon$,便會得到以下矩陣。藉由這個矩陣,我們可以試著用最少的線覆蓋所有 $0$ 的邊權,並找出 $R$ 與 $T$ 注意此時的 $u=\{9,6,2,5,3\}$、$v=\{3,2,0,0,0\}$ $\begin{bmatrix} 12&11&3&0&9 \\ 7&0&3&4&4 \\ 3&1&0&2&2 \\ 0&0&5&2&0 \\ 0&5&3&0&0 \end{bmatrix} \longrightarrow \begin{array}{cc} & \begin{array}{ccccc} & & T & T & T & ~ \end{array} \\ \begin{array}{ccc} \\ \\ \\ R \\ R \\ \end{array} & \left[\begin{array}{ccc} 12&11&3&\underline0&9 \\ 7&\underline0&3&4&4 \\ 3&1&\underline0&2&2 \\ 0&0&5&2&\underline0 \\ \underline0&5&3&0&0 \end{array} \right]\end{array}$ 到此會發現,已經找到一組 $Q=R\cup T$,使 $|Q|=|M|$,這就代表已經找到一組最大權完美匹配 (即畫底線的邊),因此我們停止迭代並回傳答案 註 : 如果在網路上搜尋,會發現另一種矩陣的執行方法。另一種方法是先將每 row 的最小值扣除,再將沒有 $0$ 的 column 扣除最小值,接下來再執行減去 $\epsilon$ 的步驟。然而,另一種方法並沒有辦法處理 $K_{n, n}$ 有未連邊,也就是邊權為 $0$ 的情況,也就是網路上的方法只能解決二分圖是 $n$-regular 的狀況,且邊權大於 $0$ 的情況。因此,使用本文上述方法才是正確的通用解法 ## 例題說明 題目來源: [Codeforces 1107 F. Vasya and Endless Credits](https://codeforces.com/contest/1107/problem/F) ~~這題根本閱讀題~~ ### 題目 有人想要不勞而獲買一台車,已知銀行有 $n$ 個貸款方案,每個貸款方案會在一開始給一筆錢 $a_i$,然後接下來 $k_i$ 個月都要扣 $b_i$ 元。然而,這個人可以在存到錢買到車之後就開車烙跑,問說買車的錢最多是多少 ### 限制 $1\le n \le 500$ $1\leq a_i, b_i, k_i \leq 10^9$ 每一個方案只能使用一次 每個月最多只能最多申請一個方案 ### 題解 根據題目,最多只會有 $n$ 個月,因此找 $n$ 個方案與 $n$ 個月的最大權完美匹配就好了,邊權就是的定義如下面程式碼 ### 程式碼 ```cpp /* 以上略 */ int main(){ ll n, a, b, k; cin >> n; init(n); for(int i = 1; i <= n; i++){ cin >> a >> b >> k; for(int j = 0; j < n; j++) addEdge(i, j + 1, max(0LL, a - min((ll)j, k) * b)); } cout << hungarian() << '\n'; return 0; } ``` ## 常見的二分圖最大完美匹配變化題 ### 二分圖最小權完美匹配 加負號在每一條邊的邊權,即可反轉大小關係。雖然這樣看似違反 Egerváry 定理的限制,但其實很多人寫的程式實作不會因此受影響。如果會影響的話,就抓個最大值來減去所有邊權,這樣依樣可以轉化成可以解的問題 ### 若二分圖為 $K_{n, m}$ 且 $n> m$ 在大小為 $m$ 的獨立集補上點與權重為 $0$ 的邊,使 $n=m$。如果要求最大權,則將負權邊設為 $0$ ## 備註 匈牙利演算法是由美國數學家 Harold Kuhn 在 1955 年提出 (參見[原文: The Hungarian method for the assignment problem](https://onlinelibrary.wiley.com/doi/10.1002/nav.3800020109)),會取名「匈牙利」是為了紀念兩位匈牙利數學家 [Dénes Kőnig](https://en.wikipedia.org/wiki/D%C3%A9nes_K%C5%91nig) 與 [Jenő Egerváry](https://en.wikipedia.org/wiki/Jen%C5%91_Egerv%C3%A1ry),也就是提出 Kőnig-Egerváry 定理的那兩位數學家,因為這個演算法有很多內容都是基於那兩位匈牙利數學家的貢獻 其中,Dénes Kőnig 曾寫出第一本圖論的書,英譯為 [Theory of finite and infinite graphs](https://archive.org/details/theoryoffinitein0000koni/page/422/mode/2up) (書裡面很多表示方法都不是現代圖論會用的,而且圖偏少,沒事不要讀),可說是現代圖論領域的開山祖之一 在 1957 年數學家 [James Munkres](https://en.wikipedia.org/wiki/James_Munkres) (就是寫出那本很經典的 Topology 的那位),證出匈牙利演算法是 $O(n^4)$,因此此演算法又被稱為 Kuhn-Munkres 演算法。在之後又有人將演算法優化到 $O(n^3)$ 從某些角度看來,擴充路徑演算法只是匈牙利演算法的子程式,有許多資料 (中文居多) 會錯誤地將擴充路徑演算法講成匈牙利演算法 ## 題目練習 [OnlineJudge 11383 - Golden Tiger Claw](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2378) (匈牙利演算法觀念裸題) [OnlineJudge 1045 - The Great Wall Game](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3486) [OnlineJudge 10746 - Crime Wave - The Sequel](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1687) [OnlineJudge 10888 - Warehouse](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1829) [ICPC2005 台北站 - Optimal Bus Route Design](https://vjudge.net/problem/UVALive-3353) [ICPC2015 台北站 - Task Review](https://vjudge.net/problem/UVALive-7463) [ICPC2007 台北站 - Sightseeing](https://vjudge.net/problem/UVALive-4039) ---- ## 參考資料 - D.B.West - Introduction to Graph Theory 2/e - Introduction to Algorithms, Fourth Edition - [日月卦長的模板庫 - [ Kuhn-Munkres Algorithm ] 二分圖最大權完美匹配KM算法](https://sunmoon-template.blogspot.com/2016/05/kuhn-munkres-algorithm.html) - [台師大演算法筆記 - matching](https://web.ntnu.edu.tw/~algo/Matching.html) - [Wikipedia - Hungarian algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm) - [Wikipedia - Kőnig's theorem (graph theory)](https://en.wikipedia.org/wiki/K%C5%91nig%27s_theorem_(graph_theory)) - [How do we OPTIMALLY assign drivers to riders? (Hungarian Algorithm) | Bipartite Matchings](https://www.youtube.com/watch?v=T4TrFA39AJU) - [OI Wiki 二分图最大权匹配](https://oi-wiki.org/graph/graph-matching/bigraph-weight-match/) - [海大競程 - flow and match](https://hackmd.io/@LeeShoWhaodian/2024flow) - [CP-algorithms - Hungarian algorithm for solving the assignment problem](https://cp-algorithms.com/graph/hungarian-algorithm.html) - [Sharonwg26 - 匈牙利演算法 (Hungarian Algorithm )](https://hackmd.io/@SW/BkM2kzbj8/%2FWudWPU1rQiijpxOiDLGy7g) - [14-4: 匈牙利算法 Hungarian Algorithm](https://youtu.be/bSoZQkxc1Zw?si=rTHJmHnED9F5jlHg) - [2qbingxuan - 二分圖最大權匹配](https://omeletwithoutegg.github.io/2020/11/16/Maximum-Weight-Bipartite-Matching/) ---- > [ShanC 程式競賽筆記](https://hackmd.io/@ShanC/B1ouGxqcC) > 作者: ShanC > 更新: 2025/2/5