题解:洛谷 P2324 [SCOI2005] 骑士精神
【题目来源】
洛谷:[P2324 SCOI2005] 骑士精神 - 洛谷
【题目描述】
在一个 \(5\times 5\) 的棋盘上有 \(12\) 个白色的骑士和 \(12\) 个黑色的骑士,且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和他横坐标相差为 \(1\),纵坐标相差为 \(2\) 或者横坐标相差为 \(2\),纵坐标相差为 \(1\) 的格子)移动到空位上。
给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘

为了体现出骑士精神,他们必须以最小的步数完成任务。
【输入】
第一行有一个正整数 \(T\)(\(T \le 10\)),表示一共有 \(T\) 组数据。
接下来有 \(T\) 个 \(5 \times 5\) 的矩阵,0 表示白色骑士,1 表示黑色骑士,* 表示空位。两组数据之间没有空行。
【输出】
对于每组数据都输出一行。如果能在 \(15\) 步以内(包括 \(15\) 步)到达目标状态,则输出步数,否则输出 -1。
【输入样例】
2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100
【输出样例】
7
-1
【算法标签】
《洛谷 P2324 骑士精神》 #搜索# #广度优先搜索BFS# #启发式搜索# #启发式迭代加深搜索IDA# #A算法# #折半搜索meet in the middle# #各省省选# #2005# #四川#
【代码详解】
// 40分版本
#include <bits/stdc++.h>
using namespace std;
map<string, bool> m; // 记录访问过的状态
string r = "111110111100 * 110000100000"; // 目标状态
struct Node
{
string str; // 当前棋盘状态
int step; // 已走步数
int pos; // 空格(*)的位置
} x, y; // x: 当前节点,y: 临时节点
int n; // 测试用例数量
string s; // 临时字符串
void dfs(Node x)
{
queue<Node> q; // 使用队列进行BFS
q.push(x);
// 8个可能的移动方向(相对于5×5棋盘)
int f[8] = {-11, -9, -3, 7, 11, 9, 3, -7};
while (!q.empty())
{
x = y = q.front();
q.pop();
for (int i = 0; i < 8; i++) // 尝试8个方向
{
// 检查新位置是否越界
if (x.pos + f[i] < 0 || x.pos + f[i] > 24)
{
continue;
}
// 检查边界条件,防止非法移动
if (x.pos % 5 == 4 && i > 0 && i < 5) // 最右列
{
continue;
}
if (x.pos % 5 == 0 && (i == 0 || i > 4)) // 最左列
{
continue;
}
if (x.pos % 5 == 3 && i > 1 && i < 4) // 右数第二列
{
continue;
}
if (x.pos % 5 == 1 && i > 5) // 左数第二列
{
continue;
}
// 交换空格和相邻棋子
swap(x.str[x.pos], x.str[x.pos + f[i]]);
if (!m[x.str]) // 如果新状态未被访问
{
// 步数限制检查
if (x.step == 15)
{
cout << -1 << endl;
return;
}
// 检查是否达到目标状态
if (x.str == r)
{
cout << x.step + 1 << endl;
return;
}
// 更新空格位置和步数
x.pos = x.pos + f[i];
x.step++;
m[x.str] = 1; // 标记为已访问
q.push(x);
}
x = y; // 恢复状态,尝试下一个方向
}
}
// 未找到解
cout << -1 << endl;
}
int main()
{
cin >> n; // 读入测试用例数量
for (int i = 0; i < n; i++)
{
x.str = ""; // 清空初始状态
// 读入5×5棋盘
for (int j = 0; j < 5; j++)
{
cin >> s;
x.str += s;
}
x.step = 0; // 初始化步数
x.pos = x.str.find('*'); // 查找空格位置
m.clear(); // 清空访问记录
m[x.str] = 1; // 标记初始状态为已访问
dfs(x); // 开始搜索
}
return 0;
}
// 满分解法
#include <bits/stdc++.h>
using namespace std;
map<string, bool> m; // 记录访问过的状态
string r = "111110111100 * 110000100000"; // 目标状态
struct Node
{
string str; // 当前棋盘状态
int step; // 已走步数
int pos; // 空格(*)的位置
// 重载()运算符,用于优先队列的比较(A*算法启发式函数)
bool operator() (Node a, Node b)
{
int sa = 0, sb = 0;
// 计算与目标状态的曼哈顿距离(启发式函数)
for (int i = 0; i < 25; i++)
{
if (a.str[i] != r[i]) sa++;
if (b.str[i] != r[i]) sb++;
}
// 比较f(n) = g(n) + h(n),g(n)=步数,h(n)=启发式估计
return a.step + sa > b.step + sb;
}
} x, y; // x: 当前节点,y: 临时节点
int n; // 测试用例数量
string s; // 临时字符串
void dfs(Node x)
{
// 使用优先队列实现A*搜索
priority_queue<Node, vector<Node>, Node> q;
q.push(x);
// 8个可能的移动方向(相对于5×5棋盘)
int f[8] = {-11, -9, -3, 7, 11, 9, 3, -7};
while (!q.empty())
{
x = y = q.top();
q.pop();
for (int i = 0; i < 8; i++) // 尝试8个方向
{
// 检查新位置是否越界
if (x.pos + f[i] < 0 || x.pos + f[i] > 24)
{
continue;
}
// 检查边界条件,防止非法移动
if (x.pos % 5 == 4 && i > 0 && i < 5) // 最右列
{
continue;
}
if (x.pos % 5 == 0 && (i == 0 || i > 4)) // 最左列
{
continue;
}
if (x.pos % 5 == 3 && i > 1 && i < 4) // 右数第二列
{
continue;
}
if (x.pos % 5 == 1 && i > 5) // 左数第二列
{
continue;
}
// 交换空格和相邻棋子
swap(x.str[x.pos], x.str[x.pos + f[i]]);
if (!m[x.str]) // 如果新状态未被访问
{
// 步数限制检查
if (x.step == 15)
{
cout << -1 << endl;
return;
}
// 检查是否达到目标状态
if (x.str == r)
{
cout << x.step + 1 << endl;
return;
}
// 更新空格位置和步数
x.pos = x.pos + f[i];
x.step++;
m[x.str] = 1; // 标记为已访问
q.push(x);
}
x = y; // 恢复状态,尝试下一个方向
}
}
// 未找到解
cout << -1 << endl;
}
int main()
{
cin >> n; // 读入测试用例数量
for (int i = 0; i < n; i++)
{
x.str = ""; // 清空初始状态
// 读入5×5棋盘
for (int j = 0; j < 5; j++)
{
cin >> s;
x.str += s;
}
x.step = 0; // 初始化步数
x.pos = x.str.find('*'); // 查找空格位置
m.clear(); // 清空访问记录
m[x.str] = 1; // 标记初始状态为已访问
dfs(x); // 开始搜索
}
return 0;
}
【运行结果】
2
10110
01*11
10111
01001
00000
7
01011
110*1
01110
01010
00100
-1
浙公网安备 33010602011771号