题解:洛谷 P2324 [SCOI2005] 骑士精神

【题目来源】

洛谷:[P2324 SCOI2005] 骑士精神 - 洛谷

【题目描述】

在一个 \(5\times 5\) 的棋盘上有 \(12\) 个白色的骑士和 \(12\) 个黑色的骑士,且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和他横坐标相差为 \(1\),纵坐标相差为 \(2\) 或者横坐标相差为 \(2\),纵坐标相差为 \(1\) 的格子)移动到空位上。

给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘

image

为了体现出骑士精神,他们必须以最小的步数完成任务。

【输入】

第一行有一个正整数 \(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
posted @ 2026-02-19 14:42  团爸讲算法  阅读(1)  评论(0)    收藏  举报