[SCOI2005]骑士精神 题解
题目地址:洛谷:【P2324】[SCOI2005]骑士精神 – 洛谷、BZOJ:Problem 1085. — [SCOI2005]骑士精神
题目描述
在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士,且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上。
给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘:
为了体现出骑士精神,他们必须以最少的步数完成任务。
输入输出格式
输入格式:
第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位。两组数据之间没有空行。
输出格式:
对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。
输入输出样例
输入样例#1:
2 10110 01*11 10111 01001 00000 01011 110*1 01110 01010 00100
输出样例#1:
7 -1
题解
考虑DFS,然而状态非常多,是\mathrm{C}_{25}^{12} * 13 \approx 6.8 \times 10^7的。由于最大步数有限制,我们想到了迭代加深搜索,并且配合上A*剪枝,就可以不TLE了。注意转移状态的时候,不妨让空格来跳而不是遍历找哪个马可以往空格上跳。
A*的估价函数,可以考虑随便写一个当前状态和目标状态有几个位置不一样。反正让这个函数始终不超过最优步数就行。
代码
// Code by KSkun, 2018/5
#include <cstdio>
#include <algorithm>
const int MAXN = 200005;
const char goal[5][6] = {
"11111",
"01111",
"00*11",
"00001",
"00000"
};
const int fix[2][8] = {
{1, 1, -1, -1, 2, 2, -2, -2},
{2, -2, 2, -2, 1, -1, 1, -1}
};
inline bool reach(const char s[5][6]) {
for(int i = 0; i < 5; i++) {
for(int j = 0; j < 5; j++) {
if(s[i][j] != goal[i][j]) return false;
}
}
return true;
}
inline int eval(const char s[5][6]) {
int res = 0;
for(int i = 0; i < 5; i++) {
for(int j = 0; j < 5; j++) {
if(s[i][j] != goal[i][j]) res++;
}
}
return res;
}
bool flag = false;
inline void dfs(int step, int lim, char s[5][6], int x, int y) {
if(reach(s)) {
flag = true; return;
}
for(int i = 0; i < 8; i++) {
int nx = x + fix[0][i], ny = y + fix[1][i];
if(nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;
std::swap(s[x][y], s[nx][ny]);
if(eval(s) + step <= lim) dfs(step + 1, lim, s, nx, ny);
if(flag) return;
std::swap(s[x][y], s[nx][ny]);
}
}
int T, sx, sy;
char st[5][6];
int main() {
scanf("%d", &T);
while(T--) {
for(int i = 0; i < 5; i++) {
scanf("%s", st[i]);
for(int j = 0; j < 5; j++) {
if(st[i][j] == '*') {
sx = i; sy = j;
}
}
}
if(reach(st)) {
puts("0");
continue;
}
flag = false;
for(int i = 1; i <= 15; i++) {
dfs(0, i, st, sx, sy);
if(flag) {
printf("%d\n", i);
break;
}
}
if(!flag) puts("-1");
}
return 0;
}