在一个n*n的棋盘上有n枚棋子。每次可以把一枚棋子往上、下、左、右方向之一移动一格,最后排成一行、一列或者主、副对角线上(因此一共有2n+2条可能的目标状态),要求移动次数最小。棋盘上有一些位置是障碍,棋子在任何时候都不能经过。棋子的初始位置保证不在障碍物上。任两枚棋子不能在同时到达同一个格子。
【输入文件】 输入文件move.in第一行包含两个整数n, m,表示棋子的个数(它也是棋盘的边长)和障碍的个数。以下n行,每行两个整数(x, y),表示第i个棋子的坐标(1<=x, y<=n),以下m行,每行给出一个障碍物的坐标。假设这n+m个坐标两两不重合。【输出文件】 输出文件仅包含一个整数,表示最小移动步数。如果无解,输出-1。【样例】move.in5 11 22 43 45 15 31 1move.out6【限制】50%的数据满足:2<=n<=15,m=0100%的数据满足:2<=n<=50, 0<=m<=100
此题考察KM算法的应用。首先以所有棋子为起点,分别求一次单源最短路(由于图非常稀疏,所以用广搜接就行了。然后,枚举终点(按行,列,以及两条对角线枚举),分别做一次最小匹配(用KM算法),最后在所有的匹配值中取一个最小值即可。Accode:#include <cstdio>#include <cstdlib>#include <algorithm>#include <string>#include <cstring>const char fi[] = "move.in", fo[] = "move.out";const int maxN = 60, SIZE = 0xffff;const int MAX = 0x3f3f3f3f, MIN = ~MAX;const int dx[] = {0, 0, 1, -1};const int dy[] = {1, -1, 0, 0};struct Node{ int x, y; Node() {} Node(int x, int y): x(x), y(y) {}} q[SIZE + 1], chess[maxN], target[maxN];bool _chess[maxN], _target[maxN];bool mp[maxN][maxN], vis[maxN][maxN];int lx[maxN], ly[maxN], dist[maxN][maxN][maxN];int Link[maxN], n, m, f, r, ans = MAX;inline int getint(){ int res = 0; char tmp; while (!isdigit(tmp = getchar())); do res = (res << 3) + (res << 1) + tmp - '0'; while (isdigit(tmp = getchar())); return res;}void init(){ freopen(fi, "r", stdin); freopen(fo, "w", stdout); n = getint(); m = getint(); for (int i = 0; i < n; ++i) { int x = getint(), y = getint(); chess[i] = Node(x - 1, y - 1); } memset(mp, 1, sizeof mp); for (int i = 0; i < m; ++i) { int u = getint(), v = getint(); mp[u - 1][v - 1] = 0; } return;}void Bfs(int S){ memset(vis, 0, sizeof vis); dist[S][chess[S].x][chess[S].y] = 0; vis[chess[S].x][chess[S].y] = 1; q[(r = f = 0)++] = chess[S]; while (f < r) { Node Now = q[f++]; int x = Now.x, y = Now.y; for (int j = 0; j < 4; ++j) { int u = x + dx[j], v = y + dy[j]; if (u < 0 || u >= n || v < 0 || v >= n) continue; if (mp[u][v] && !vis[u][v]) { vis[u][v] = 1; dist[S][u][v] = dist[S][x][y] + 1; q[r++] = Node(u, v); } } } return;}bool Find(int i){ _chess[i] = 1; for (int j = 0; j < n; ++j) if (!_target[j] && lx[i] + ly[j] == dist[i][target[j].x][target[j].y]) { _target[j] = 1; if (Link[j] == -1 || Find(Link[j])) {Link[j] = i; return 1;} } return 0;}int KM(){ for (int i = 0; i < n; ++i) { lx[i] = MAX; ly[i] = 0; Link[i] = -1; for (int j = 0; j < n; ++j) lx[i] = std::min(lx[i], dist[i][target[j].x][target[j].y]); } for (int k = 0; k < n; ++k)//这里循环变量一定不能用i。 while (1) { memset(_chess, 0, sizeof _chess); memset(_target, 0, sizeof _target); if (Find(k)) break; int Max = MIN; for (int i = 0; i < n; ++i) if (_chess[i]) for (int j = 0; j < n; ++j) if (!_target[j]) Max = std::max(Max, lx[i] + ly[j] - dist[i][target[j].x] [target[j].y]); for (int i = 0; i < n; ++i) { if (_chess[i]) lx[i] -= Max; if (_target[i]) ly[i] += Max; } } //注意求最小匹配和最大匹配略有不同。 int ans = 0; for (int i = 0; i < n; ++i) ans += dist[Link[i]][target[i].x][target[i].y]; return ans;}void work(){ memset(dist, 0x3f, sizeof dist); for (int i = 0; i < n; ++i) Bfs(i);//一定要以每个棋子作为起点求最短路,否则浪费。 for (int i = 0; i < n; ++i) { bool ok = 1; for (int j = 0; j < n; ++j) if (!mp[i][j]) {ok = 0; break;} if (!ok) continue; for (int j = 0; j < n; ++j) target[j] = Node(i, j); ans = std::min(ans, KM()); } for (int j = 0; j < n; ++j) { bool ok = 1; for (int i = 0; i < n; ++i) if (!mp[i][j]) {ok = 0; break;} if (!ok) continue; for (int i = 0; i < n; ++i) target[i] = Node(i, j); ans = std::min(ans, KM()); } bool ok = 1; for (int i = 0; i < n; ++i) if (!mp[i][i]) {ok = 0; break;} if (ok) { for (int i = 0; i < n; ++i) target[i] = Node(i, i); ans = std::min(ans, KM()); } ok = 1; for (int i = 0; i < n; ++i) if (!mp[i][n - i - 1]) {ok = 0; break;} if (ok) { for (int i = 0; i < n; ++i) target[i] = Node(i, n - i - 1); ans = std::min(ans, KM()); } printf("%d\n", ans < MAX ? ans : -1); return;}int main(){ init(); work(); return 0;}