题解:洛谷 P1747 好奇怪的游戏

【题目来源】

洛谷:P1747 好奇怪的游戏 - 洛谷

【题目描述】

爱与愁大神坐在公交车上无聊,于是玩起了手机。一款奇怪的游戏进入了爱与愁大神的眼帘:***(游戏名被打上了马赛克)。这个游戏类似象棋,但是只有黑白马各一匹,在点 \(x_1,y_1\)\(x_2,y_2\) 上。它们得从点 \(x_1,y_1\)\(x_2,y_2\) 走到 \((1,1)\)。这个游戏与普通象棋不同的地方是:马可以走“日”,也可以像象走“田”。现在爱与愁大神想知道两匹马到 \((1,1)\) 的最少步数,你能帮他解决这个问题么?

注意不能走到 \(x\)\(y\) 坐标 \(\le 0\) 的位置。满足此限制的 \((x,y)\) 均可到达。

【输入】

第一行两个整数 \(x_1,y_1\)

第二行两个整数 \(x_2,y_2\)

【输出】

第一行一个整数,表示黑马到 \((1,1)\) 的步数。

第二行一个整数,表示白马到 \((1,1)\) 的步数。

【输入样例】

12 16
18 10

【输出样例】

8
9

【核心思想】

  1. 问题分析:给定两匹马的初始位置 \((x_1, y_1)\)\((x_2, y_2)\),每匹马可以走"日"(8种)或"田"(4种),共12种移动方式,求各自到 \((1,1)\) 的最少步数。不能走到坐标 \(\leq 0\) 的位置。这是一个多源BFS(两次独立BFS)问题,关键在于将特殊移动规则建模为12个方向向量,用BFS求最短路。

  2. 算法选择

    • BFS(广度优先搜索):无权图最短路,每步代价为1,BFS保证第一次到达目标时步数最少
    • 12方向扩展:将"日"字走法(8种)和"田"字走法(4种)统一为12个方向向量
  3. 关键步骤

    • 方向数组定义
      • "日"字:\((\pm 2, \pm 1), (\pm 1, \pm 2)\) — 8种
      • "田"字:\((\pm 2, \pm 2)\) — 4种
      • 共12种:dx[12] = {-2,-2,-1,-1,1,1,2,2,-2,-2,2,2}dy[12] = {-1,1,-2,2,-2,2,-1,1,-2,2,-2,2}
    • BFS函数(参数为起始坐标 \((x, y)\)):
      • 初始化 dist\(-1\)(未访问),dist[x][y] = 0
      • 队列扩展12个方向,边界检查(\(1 \leq nx, ny \leq 20\))和访问检查
      • 到达 \((1,1)\) 立即返回 dist[nx][ny]
    • 两次调用:分别对两匹马的初始位置调用 bfs
  4. 时间/空间复杂度

    • 时间复杂度:\(O(2 \times XY \times 12) = O(XY)\)\(X, Y\) 为棋盘范围(本题中 \(20 \times 20\)
    • 空间复杂度:\(O(XY)\)dist 数组
  5. BFS多方向扩展的核心思想

    • 移动规则统一:将象棋中马的"日"和象的"田"统一为12个方向向量,BFS扩展时无差别处理
    • 棋盘边界限制:坐标必须 \(\geq 1\),且题目保证在合理范围内可达,设置上界为 \(20\) 即可覆盖
    • BFS最优性保证:无权图中BFS按层扩展,第一次到达目标点的层数即为最少步数
    • 独立两次搜索:两匹马互不影响,分别独立运行BFS,无状态共享
    • 适用于"网格特殊移动规则最短路"问题,核心在于准确建模所有可能移动方向

【算法标签】

普及 #BFS-二维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;  // 定义坐标对类型
const int N = 25;  // 定义棋盘大小常量
int dist[N][N];  // 距离数组,存储从起点到每个点的最少步数
// 马的12种移动方式:日字移动和"目"字移动
int dx[12] = {-2, -2, -1, -1, 1, 1, 2, 2, -2, -2, 2, 2};
int dy[12] = {-1, 1, -2, 2, -2, 2, -1, 1, -2, 2, -2, 2};

// 广度优先搜索函数,计算从(x,y)到(1,1)的最少步数
int bfs(int x, int y)
{
    memset(dist, -1, sizeof(dist));  // 初始化距离数组为-1,表示未访问
    queue<PII> q;  // 定义队列用于BFS
    dist[x][y] = 0;  // 起点到自身的距离为0
    q.push({x, y});  // 将起点加入队列
    while (!q.empty())  // 当队列不为空时循环
    {
        int xx = q.front().first, yy = q.front().second;  // 取出队首坐标
        q.pop();  // 弹出队首
        for (int i = 0; i < 12; i++)  // 遍历12种移动方式
        {
            int nx = xx + dx[i], ny = yy + dy[i];  // 计算移动后的新坐标
            // 检查新坐标是否越界或已被访问
            if (nx < 1 || nx > 20 || ny < 1 || ny > 20 || dist[nx][ny] != -1)
                continue;
            dist[nx][ny] = dist[xx][yy] + 1;  // 更新新坐标的距离
            if (nx == 1 && ny == 1)  // 如果到达目标点(1,1)
                return dist[nx][ny];  // 返回最短距离
            q.push({nx, ny});  // 将新坐标加入队列
        }
    }
}
int main()
{
    int x1, x2, y1, y2;  // 定义两个起始坐标
    cin >> x1 >> y1 >> x2 >> y2;  // 输入两个起始坐标
    cout << bfs(x1, y1) << endl;  // 计算并输出从第一个起点到(1,1)的最少步数
    cout << bfs(x2, y2) << endl;  // 计算并输出从第二个起点到(1,1)的最少步数
    return 0;
}

【运行结果】

12 16
18 10
8
9
posted @ 2026-08-10 12:27  团爸讲算法  阅读(16)  评论(0)    收藏  举报