package class_2022_03_5_week; import java.util.Arrays; // 来自微软 // 在N*N的正方形棋盘中,有N*N个棋子,那么每个格子正好可以拥有一个棋子 // 但是现在有些棋子聚集到一个格子上了,比如: // 2 0 3 // 0 1 0 // 3 0 0 // 如上的二维数组代表,一共3*3个格子 // 但是有些格子有2个棋子、有些有3个、有些有1个、有些没有 // 请你用棋子移动的方式,让每个格子都有一个棋子 // 每个棋子可以上、下、左、右移动,每移动一步算1的代价 // 返回最小的代价 public class Code02_ToAllSpace { // 暴力解 // 作为对数器 public static int minDistance1(int[][] map) { int n = 0; int m = 0; for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { n += Math.max(0, map[i][j] - 1); m += map[i][j] == 0 ? 1 : 0; } } if (n != m || n == 0) { return 0; } int[][] nodes = new int[n][2]; int[][] space = new int[m][2]; n = 0; m = 0; for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { for (int k = 2; k <= map[i][j]; k++) { nodes[n][0] = i; nodes[n++][1] = j; } if (map[i][j] == 0) { space[m][0] = i; space[m++][1] = j; } } } return process1(nodes, 0, space); } public static int process1(int[][] nodes, int index, int[][] space) { int ans = 0; if (index == nodes.length) { for (int i = 0; i < nodes.length; i++) { ans += distance(nodes[i], space[i]); } } else { ans = Integer.MAX_VALUE; for (int i = index; i < nodes.length; i++) { swap(nodes, index, i); ans = Math.min(ans, process1(nodes, index + 1, space)); swap(nodes, index, i); } } return ans; } public static void swap(int[][] nodes, int i, int j) { int[] tmp = nodes[i]; nodes[i] = nodes[j]; nodes[j] = tmp; } public static int distance(int[] a, int[] b) { return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]); } // 正式方法 // KM算法 public static int minDistance2(int[][] map) { int n = 0; int m = 0; for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { n += Math.max(0, map[i][j] - 1); m += map[i][j] == 0 ? 1 : 0; } } if (n != m || n == 0) { return 0; } int[][] nodes = new int[n][2]; int[][] space = new int[m][2]; n = 0; m = 0; for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { for (int k = 2; k <= map[i][j]; k++) { nodes[n][0] = i; nodes[n++][1] = j; } if (map[i][j] == 0) { space[m][0] = i; space[m++][1] = j; } } } int[][] graph = new int[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { graph[i][j] = -distance(nodes[i], space[j]); } } return -km(graph); } public static int km(int[][] graph) { int N = graph.length; int[] match = new int[N]; int[] lx = new int[N]; int[] ly = new int[N]; boolean[] x = new boolean[N]; boolean[] y = new boolean[N]; int[] slack = new int[N]; int invalid = Integer.MAX_VALUE; for (int i = 0; i < N; i++) { match[i] = -1; lx[i] = -invalid; for (int j = 0; j < N; j++) { lx[i] = Math.max(lx[i], graph[i][j]); } ly[i] = 0; } for (int from = 0; from < N; from++) { for (int i = 0; i < N; i++) { slack[i] = invalid; } Arrays.fill(x, false); Arrays.fill(y, false); while (!dfs(from, x, y, lx, ly, match, slack, graph)) { int d = invalid; for (int i = 0; i < N; i++) { if (!y[i] && slack[i] < d) { d = slack[i]; } } for (int i = 0; i < N; i++) { if (x[i]) { lx[i] = lx[i] - d; } if (y[i]) { ly[i] = ly[i] + d; } } Arrays.fill(x, false); Arrays.fill(y, false); } } int ans = 0; for (int i = 0; i < N; i++) { ans += (lx[i] + ly[i]); } return ans; } public static boolean dfs(int from, boolean[] x, boolean[] y, int[] lx, int[] ly, int[] match, int[] slack, int[][] map) { int N = map.length; x[from] = true; for (int to = 0; to < N; to++) { if (!y[to]) { int d = lx[from] + ly[to] - map[from][to]; if (d != 0) { slack[to] = Math.min(slack[to], d); } else { y[to] = true; if (match[to] == -1 || dfs(match[to], x, y, lx, ly, match, slack, map)) { match[to] = from; return true; } } } } return false; } // 为了测试 public static int[][] randomValidMatrix(int len) { int[][] ans = new int[len][len]; int all = len * len; for (int i = 1; i <= all; i++) { ans[(int) (Math.random() * len)][(int) (Math.random() * len)]++; } return ans; } public static void main(String[] args) { int len = 4; int testTimes = 1000; System.out.println("测试开始"); for (int i = 0; i < testTimes; i++) { int[][] map = randomValidMatrix(len); int ans1 = minDistance1(map); int ans2 = minDistance2(map); if (ans1 != ans2) { System.out.println("出错了!"); break; } } System.out.println("测试结束"); } }