在一个n*n的棋盘上有n枚棋子。每次可以把一枚棋子往上、下、左、右方向之一移动一格,最后排成一行、一列或者主、副对角线上(因此一共有2n+2条可能的目标状态),要求移动次数最小。
棋盘上有一些位置是障碍,棋子在任何时候都不能经过。棋子的初始位置保证不在障碍物上。任两枚棋子不能在同时到达 同一个格子。
【输入文件】
输入文件move.in第一行包含两个整数n, m,表示棋子的个数(它也是棋盘的边长)和障碍的个数。以下n行,每行两个整数(x, y),表示第i个棋子的坐标(1<=x, y<=n),以下m行,每行给出一个障碍物的坐标。假设这n+m个坐标两两不重合。
【输出文件】
输出文件仅包含一个整数,表示最小移动步数。如果无解,输出-1。
【样例】
move.in
5 1
1 2
2 4
3 4
5 1
5 3
1 1
move.out
6
【限制】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;
}