From 50e7848f3d6e98b057a3609bf63addeb8cf4154d Mon Sep 17 00:00:00 2001 From: Joshua Andersson Date: Thu, 9 Jul 2026 15:07:12 +0200 Subject: [PATCH 1/7] Improve weightedmatching --- content/graph/WeightedMatching.h | 63 ++++++++++++++------------------ 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index 17f0b5eba..a605eac2a 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -1,47 +1,38 @@ /** - * Author: Benjamin Qi, chilli - * Date: 2020-04-04 + * Author: Jeremy Lim, Joshua Andersson + * Date: 2026-07-09 * License: CC0 - * Source: https://github.com/bqi343/USACO/blob/master/Implementations/content/graphs%20(12)/Matching/Hungarian.h * Description: Given a weighted bipartite graph, matches every node on * the left with a node on the right such that no * nodes are in two matchings and the sum of the edge weights is minimal. Takes - * cost[N][M], where cost[i][j] = cost for L[i] to be matched with R[j] and + * cost[N][M], where cost[i][j] = cost for L[i] to be matched with R[j], and * returns (min cost, match), where L[i] is matched with - * R[match[i]]. Negate costs for max cost. Requires $N \le M$. + * R[match[i]]. Negate costs for max cost. Requires $N \le M$ * Time: O(N^2M) - * Status: Tested on kattis:cordonbleu, stress-tested + * Status: Tested on kattis:cordonbleu, kattis:engaging, stress-tested */ #pragma once -pair hungarian(const vector &a) { - if (a.empty()) return {0, {}}; - int n = sz(a) + 1, m = sz(a[0]) + 1; - vi u(n), v(m), p(m), ans(n - 1); - rep(i,1,n) { - p[0] = i; - int j0 = 0; // add "dummy" worker 0 - vi dist(m, INT_MAX), pre(m, -1); - vector done(m + 1); - do { // dijkstra - done[j0] = true; - int i0 = p[j0], j1, delta = INT_MAX; - rep(j,1,m) if (!done[j]) { - auto cur = a[i0 - 1][j - 1] - u[i0] - v[j]; - if (cur < dist[j]) dist[j] = cur, pre[j] = j0; - if (dist[j] < delta) delta = dist[j], j1 = j; - } - rep(j,0,m) { - if (done[j]) u[p[j]] += delta, v[j] -= delta; - else dist[j] -= delta; - } - j0 = j1; - } while (p[j0]); - while (j0) { // update alternating path - int j1 = pre[j0]; - p[j0] = p[j1], j0 = j1; - } - } - rep(j,1,m) if (p[j]) ans[p[j] - 1] = j - 1; - return {-v[0], ans}; // min cost +template pair> +weighted_matching(vector> &C) { + int i = sz(C), m = sz(C[0]), c, j, s, r; + vector dist(m), potential(m); + vi row_match(i), col_match(m, -1), cols(m), prev(m); + T d, nd, cost = 0; + for (; i--;) { + rep(c, 0, m) dist[c] = C[i][c], cols[c] = c,prev[c]=i; + for (s = 0;;) { + for (j = s; j < m; j++) if (c = cols[j], + nd = dist[c] - potential[c], j == s || d > nd) + d = nd, swap(cols[s], cols[j]); + if (!~(r = col_match[c = cols[s++]])) break; + rep(j, 0, m) if (dist[j] > (nd = C[r][j]-C[r][c] + + dist[c])) dist[j] = nd, prev[j] = r; + } + for (cost += dist[c]; s--;) j = cols[s], + potential[j] = dist[j] - d; + for (; r != i; swap(c, row_match[r])) + r = col_match[c] = prev[c]; + } + return {cost, row_match}; } From 32cbf58d63ee781d621d1d110b2ca3e3c9989ed0 Mon Sep 17 00:00:00 2001 From: Joshua Andersson Date: Thu, 9 Jul 2026 15:24:12 +0200 Subject: [PATCH 2/7] Add source for weighted matching --- content/graph/WeightedMatching.h | 1 + 1 file changed, 1 insertion(+) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index a605eac2a..ee08b8713 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -2,6 +2,7 @@ * Author: Jeremy Lim, Joshua Andersson * Date: 2026-07-09 * License: CC0 + * Source: https://github.com/scipy/scipy/blob/main/scipy/optimize/rectangular_lsap/rectangular_lsap.cpp * Description: Given a weighted bipartite graph, matches every node on * the left with a node on the right such that no * nodes are in two matchings and the sum of the edge weights is minimal. Takes From 0c81fe38167856d820e174a7272566c5d8bf3b6a Mon Sep 17 00:00:00 2001 From: Joshua Andersson Date: Thu, 9 Jul 2026 15:34:20 +0200 Subject: [PATCH 3/7] Update weighted matching tests --- content/graph/WeightedMatching.h | 2 +- stress-tests/graph/WeightedMatching.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index ee08b8713..be8dbc0de 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -8,7 +8,7 @@ * nodes are in two matchings and the sum of the edge weights is minimal. Takes * cost[N][M], where cost[i][j] = cost for L[i] to be matched with R[j], and * returns (min cost, match), where L[i] is matched with - * R[match[i]]. Negate costs for max cost. Requires $N \le M$ + * R[match[i]]. Negate costs for max cost. Requires $1 \le N \le M$ * Time: O(N^2M) * Status: Tested on kattis:cordonbleu, kattis:engaging, stress-tested */ diff --git a/stress-tests/graph/WeightedMatching.cpp b/stress-tests/graph/WeightedMatching.cpp index 01f295550..ce34b2353 100644 --- a/stress-tests/graph/WeightedMatching.cpp +++ b/stress-tests/graph/WeightedMatching.cpp @@ -8,7 +8,7 @@ void test(int N, int mxCost, int iters) { for (int it = 0; it < iters; it++) { - int n = randRange(0, N), m = randRange(0, N); + int n = randIncl(1, N), m = randIncl(1, N); if (n > m) swap(n, m); @@ -29,7 +29,7 @@ void test(int N, int mxCost, int iters) { } mcmf.setpi(s); auto maxflow = mcmf.maxflow(s, t); - auto matching = hungarian(cost); + auto matching = weighted_matching(cost); assert(maxflow.first == n); assert(maxflow.second == matching.first); int matchSum = 0; @@ -40,7 +40,6 @@ void test(int N, int mxCost, int iters) { used.insert(matching.second[i]); } assert(matchSum == matching.first); - return; } } signed main() { From f0857050b426d7e23221d3152dccd93004d3996c Mon Sep 17 00:00:00 2001 From: Joshua Andersson Date: Thu, 9 Jul 2026 16:35:35 +0200 Subject: [PATCH 4/7] Improve weightedmatching formatting --- content/graph/WeightedMatching.h | 43 +++++++++++++------------ stress-tests/graph/WeightedMatching.cpp | 2 +- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index be8dbc0de..495dd8501 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -8,7 +8,7 @@ * nodes are in two matchings and the sum of the edge weights is minimal. Takes * cost[N][M], where cost[i][j] = cost for L[i] to be matched with R[j], and * returns (min cost, match), where L[i] is matched with - * R[match[i]]. Negate costs for max cost. Requires $1 \le N \le M$ + * R[match[i]]. Negate costs for max cost. Requires $N \le M$. * Time: O(N^2M) * Status: Tested on kattis:cordonbleu, kattis:engaging, stress-tested */ @@ -16,24 +16,25 @@ template pair> weighted_matching(vector> &C) { - int i = sz(C), m = sz(C[0]), c, j, s, r; - vector dist(m), potential(m); - vi row_match(i), col_match(m, -1), cols(m), prev(m); - T d, nd, cost = 0; - for (; i--;) { - rep(c, 0, m) dist[c] = C[i][c], cols[c] = c,prev[c]=i; - for (s = 0;;) { - for (j = s; j < m; j++) if (c = cols[j], - nd = dist[c] - potential[c], j == s || d > nd) - d = nd, swap(cols[s], cols[j]); - if (!~(r = col_match[c = cols[s++]])) break; - rep(j, 0, m) if (dist[j] > (nd = C[r][j]-C[r][c] + - dist[c])) dist[j] = nd, prev[j] = r; - } - for (cost += dist[c]; s--;) j = cols[s], - potential[j] = dist[j] - d; - for (; r != i; swap(c, row_match[r])) - r = col_match[c] = prev[c]; - } - return {cost, row_match}; + int i = sz(C), m = i ? sz(C[0]) : 0, c, j, s, r; + vector dist(m), potential(m); + vi row_match(i), col_match(m, -1), cols(m), prev(m); + T d, nd, cost = 0; + while (i--) { + rep(c,0,m) dist[c] = C[i][c], cols[c] = c, prev[c] = i; + for (s = 0;;) { + for (j = s; j < m; j++) { + c = cols[j], nd = dist[c] - potential[c]; + if (j == s || d > nd) d = nd, swap(cols[s], cols[j]); + } + if (!~(r = col_match[c = cols[s++]])) break; + rep(j,0,m) if (dist[j] > (nd = C[r][j]-C[r][c]+dist[c])) + dist[j] = nd, prev[j] = r; + } + for (cost += dist[c]; s--;) + j = cols[s], potential[j] = dist[j] - d; + for (; r != i; swap(c, row_match[r])) + r = col_match[c] = prev[c]; + } + return {cost, row_match}; } diff --git a/stress-tests/graph/WeightedMatching.cpp b/stress-tests/graph/WeightedMatching.cpp index ce34b2353..352fe5686 100644 --- a/stress-tests/graph/WeightedMatching.cpp +++ b/stress-tests/graph/WeightedMatching.cpp @@ -8,7 +8,7 @@ void test(int N, int mxCost, int iters) { for (int it = 0; it < iters; it++) { - int n = randIncl(1, N), m = randIncl(1, N); + int n = randIncl(0, N), m = randIncl(0, N); if (n > m) swap(n, m); From ca51c06ee6f0a4f2e7ea7c2771275d9cd8be078f Mon Sep 17 00:00:00 2001 From: Joshua Bergman Andersson <38278628+Matistjati@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:52:25 +0200 Subject: [PATCH 5/7] Update content/graph/WeightedMatching.h Co-authored-by: Simon Lindholm --- content/graph/WeightedMatching.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index 495dd8501..909cd5711 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -23,7 +23,7 @@ weighted_matching(vector> &C) { while (i--) { rep(c,0,m) dist[c] = C[i][c], cols[c] = c, prev[c] = i; for (s = 0;;) { - for (j = s; j < m; j++) { + rep(j,s,m) { c = cols[j], nd = dist[c] - potential[c]; if (j == s || d > nd) d = nd, swap(cols[s], cols[j]); } From 4928f69a03ff5bd4304bfb555efe25fd7319438c Mon Sep 17 00:00:00 2001 From: Joshua Bergman Andersson <38278628+Matistjati@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:52:31 +0200 Subject: [PATCH 6/7] Update content/graph/WeightedMatching.h Co-authored-by: Simon Lindholm --- content/graph/WeightedMatching.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index 909cd5711..166483340 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -32,7 +32,7 @@ weighted_matching(vector> &C) { dist[j] = nd, prev[j] = r; } for (cost += dist[c]; s--;) - j = cols[s], potential[j] = dist[j] - d; + potential[cols[s]] = dist[cols[s]] - d; for (; r != i; swap(c, row_match[r])) r = col_match[c] = prev[c]; } From 8c160aa297b77dac041a433876ef5509bd34f11a Mon Sep 17 00:00:00 2001 From: Joshua Bergman Andersson <38278628+Matistjati@users.noreply.github.com> Date: Sat, 11 Jul 2026 12:34:09 +0200 Subject: [PATCH 7/7] Update content/graph/WeightedMatching.h Co-authored-by: Simon Lindholm --- content/graph/WeightedMatching.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/graph/WeightedMatching.h b/content/graph/WeightedMatching.h index 166483340..e02d0de6f 100644 --- a/content/graph/WeightedMatching.h +++ b/content/graph/WeightedMatching.h @@ -16,7 +16,7 @@ template pair> weighted_matching(vector> &C) { - int i = sz(C), m = i ? sz(C[0]) : 0, c, j, s, r; + int i = sz(C), m = i ? sz(C[0]) : 0, c, s, r; vector dist(m), potential(m); vi row_match(i), col_match(m, -1), cols(m), prev(m); T d, nd, cost = 0;