import java.util.*; // https://en.wikipedia.org/wiki/Hungarian_algorithm in O(n^2 * m) public class Hungarian { // a[n][m], n <= m, sum(a[i][p[i]] -> min public static int minWeightPerfectMatching(int[][] a) { int n = a.length - 1; int m = a[0].length - 1; int[] u = new int[n + 1]; int[] v = new int[m + 1]; int[] p = new int[m + 1]; int[] way = new int[m + 1]; for (int i = 1; i <= n; ++i) { int[] minv = new int[m + 1]; Arrays.fill(minv, Integer.MAX_VALUE); boolean[] used = new boolean[m + 1]; p[0] = i; int j0 = 0; while (p[j0] != 0) { used[j0] = true; int i0 = p[j0]; int delta = Integer.MAX_VALUE; int j1 = 0; for (int j = 1; j <= m; ++j) if (!used[j]) { int d = a[i0][j] - u[i0] - v[j]; if (minv[j] > d) { minv[j] = d; way[j] = j0; } if (delta > minv[j]) { delta = minv[j]; j1 = j; } } for (int j = 0; j <= m; ++j) if (used[j]) { u[p[j]] += delta; v[j] -= delta; } else minv[j] -= delta; j0 = j1; } while (j0 != 0) { int j1 = way[j0]; p[j0] = p[j1]; j0 = j1; } } int[] matching = new int[n + 1]; for (int i = 1; i <= m; ++i) matching[p[i]] = i; return -v[0]; } // random test public static void main(String[] args) { Random rnd = new Random(1); for (int step = 0; step < 1000; step++) { int max = 9; int n = rnd.nextInt(max) + 1; int m = n + rnd.nextInt(max + 1 - n); int[][] a = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { a[i][j] = rnd.nextInt(100_000) - 50_000; } } int res1 = minWeightPerfectMatching(a); int res2 = minWeightPerfectMatchingSlow(a); if (res1 != res2) { System.err.println(res1 + " " + res2); } } } static int minWeightPerfectMatchingSlow(int[][] a) { int n = a.length - 1; int m = a[0].length - 1; int[] dp = new int[1 << (n + m)]; Arrays.fill(dp, Integer.MAX_VALUE / 2); dp[0] = 0; int res = Integer.MAX_VALUE; for (int mask = 0; mask < dp.length; mask++) { for (int i = 0; i < n; i++) { if ((mask & (1 << i)) == 0) { for (int j = 0; j < m; j++) { if ((mask & (1 << (j + n))) == 0) { dp[mask | (1 << i) | (1 << (j + n))] = Math.min(dp[mask | (1 << i) | (1 << (j + n))], dp[mask] + a[i + 1][j + 1]); if (Integer.bitCount(mask & ((1 << n) - 1)) == n - 1) res = Math.min(res, dp[mask | (1 << i) | (1 << (j + n))]); } } break; } } } return res; } static int minimumWeightPerfectMatchingSlow2(int[][] a) { int n = a.length - 1; int m = a[0].length - 1; int res = Integer.MAX_VALUE; int[] p = new int[n]; for (int i = 0; i < n; i++) p[i] = i; do { int cur = 0; for (int i = 0; i < p.length; i++) cur += a[i + 1][p[i] + 1]; res = Math.min(res, cur); } while (nextArrangement(p, m)); return res; } static boolean nextArrangement(int[] p, int n) { boolean[] used = new boolean[n]; for (int x : p) { used[x] = true; } int m = p.length; for (int i = m - 1; i >= 0; i--) { used[p[i]] = false; for (int j = p[i] + 1; j < n; j++) { if (!used[j]) { p[i++] = j; used[j] = true; for (int k = 0; k < n && i < m; k++) { if (!used[k]) { p[i++] = k; } } return true; } } } return false; } }