题解:洛谷 P1588 Catch That Cow

【题目来源】

洛谷:P1588 [USACO07OPEN] Catch That Cow S - 洛谷

【题目描述】

FJ 丢了一头牛,决定将其找回。FJ 和牛位于数轴上,初始位置分别为 \(x\)\(y\),牛保持不动。每次移动时,若 FJ 处于位置 \(p\),他可移动至 \(p + 1\)\(p - 1\)\(2 \times p\)。计算 FJ 抓住牛所需的最少移动次数。

【输入】

第一行为一个整数 \(t\ ( 1\le t\le 10)\),表示数据组数;

接下来每行包含一个两个正整数 \(x,y\ (0<x,y \le 10^5)\),分别表示 FJ 和牛的坐标。

【输出】

对于每组数据,输出最少步数,每组数据间用换行隔开。

【输入样例】

1
5 17

【输出样例】

4

【核心思想】

  1. 问题分析:给定 FJ 初始位置 \(x\) 和牛的位置 \(y\)(牛不动),FJ 每次可移动到 \(p+1\)\(p-1\)\(2p\)。求抓住牛的最少移动次数。这是一个一维 BFS 最短路问题,关键在于将三种移动方式建模为边权为 \(1\) 的边,利用 BFS 的层序特性求最短路;同时需处理 \(x \ge y\) 时的特殊情况。

  2. 算法选择

    • BFS(广度优先搜索):三种移动方式代价均为 \(1\),BFS 天然适合求无权图最短路
    • 特判优化:若 \(x = y\),答案为 \(0\);若 \(x > y\),只能一步步后退,答案为 \(x - y\)
    • 距离数组dis[p] 记录到达位置 \(p\) 的最少步数,初始化为 \(-1\) 表示未访问
  3. 关键步骤

    • 读取输入\(T\) 组数据,每组 \(x, y\)
    • 特判
      • \(x = y\):输出 \(0\)
      • \(x > y\):输出 \(x - y\)
    • BFS 过程\(x < y\) 时):
      • 初始化队列,\(dis[x] = 0\)\(x\) 入队
      • 取出队首 \(p\),检查三种扩展:
        • \(p+1\):若未越界且未访问,\(dis[p+1] = dis[p] + 1\),入队
        • \(p-1\):若未越界且未访问,\(dis[p-1] = dis[p] + 1\),入队
        • \(2p\):若未越界且未访问,\(dis[2p] = dis[p] + 1\),入队
      • 若扩展到达 \(y\),输出 \(dis[y]\) 并结束
    • 输出答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot \max(x, y))\),每个位置最多入队一次
    • 空间复杂度:\(O(\max(x, y))\),距离数组和队列
  5. BFS 一维搜索的核心思想

    • 状态空间离散化:将连续数轴上的位置离散为节点,三种移动为无向边(除 \(2p\) 为单向)
    • 层序扩展保证最优:BFS 按步数分层扩展,首次到达 \(y\) 时即为最少步数
    • 剪枝与边界\(x > y\) 时直接后退最优,避免无意义搜索;\(2p\) 可能越界需严格限制在 \([0, 10^5]\)
    • 适用于"一维坐标上的最短步数"类问题,核心是建立状态转移图并用 BFS 求最短路

【解题思路】

img/Pasted image 20260519130019.png

【算法标签】

普及- #BFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int t, x, y;
int vis[100005]={0}, d[100005]={0};  // vis数组用来标记该点是否来过,d数组用来表示到达该点的步数
int main()
{
    cin >> t;  // 输入t组
    while (t--) {  // 依次接受t组数据输入并处理
        cin >> x >> y;  // 输入x和y
        queue<int> q;  // 定义队列
        memset(vis, 0, sizeof(vis));  // 初始化vis和d数组
        memset(d, 0, sizeof(d));
        if (x==y) {  // 如果x与y重合
            cout << 0 << endl;  // 说明牛和FJ在同一个位置,输出为0
        }
        if (x>y) {  // 如果x大于y
            cout << x-y << endl;  // 则只能向后退,步数为x-y
        }
        if (x < y) {  // 如果x小于y,则使用广搜
            int tp = x;  // 定义tp为x坐标
            vis[tp] = 1;  // 标记该点已经访问过
            q.push(tp);  // 压入队列中
            while (!q.empty()) {  // 如果队列不为空
                int tp = q.front();  // 取出队头
                q.pop();
                if (tp==y) {  // 广搜退出条件,即tp与y重合
                    cout << d[tp] << endl;  // 打印d[tp],即到达tp这个点的步数
                    break;  // 退出循环,无需继续搜索
                }
                if (vis[tp+1]!=1 && tp+1>=0 && tp+1<=100000) {  // 对前进一步的位置进行边界判断及访问情况判断,符合条件再继续
                    q.push(tp+1);  // 前进一步位置压入队列
                    vis[tp+1] = 1;  // 标记为访问过
                    d[tp+1] = d[tp] + 1;  // 更新到达前进一步的步数
                }
                if (vis[tp-1]!=1 && tp-1>=0 && tp-1<=100000) {  // 对后退一步的位置进行边界判断及访问情况判断
                    q.push(tp-1);  // 后退一步位置压入队列
                    vis[tp-1] = 1;  // 标记为访问过
                    d[tp-1] = d[tp] + 1;  // 更新到达后退一步的步数
                }
                if (vis[tp*2]!=1 && tp*2>=0 && tp*2<=100000) {  // 对2*x的位置进行边界判断及访问情况判断
                    q.push(tp*2);  // 2*x位置压入队列
                    vis[tp*2] = 1;  // 标记为访问过
                    d[tp*2] = d[tp] + 1;  // 更新2*x位置的步数
                }
            }
        }
    }
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int T, x, y, dis[N];  // T: 测试用例数, (x,y): 起点终点, dis: 距离数组

// 广度优先搜索
void bfs()
{
    memset(dis, -1, sizeof(dis));  // 初始化距离为-1
    dis[x] = 0;  // 起点距离为0
    queue<int> q;
    q.push(x);
    while (q.size())
    {
        int x = q.front();
        q.pop();
        if (x + 1 < N && dis[x + 1] == -1)  // 前进一步
        {
            dis[x + 1] = dis[x] + 1;
            q.push(x + 1);
        }
        if (x - 1 > 0 && dis[x - 1] == -1)  // 后退一步
        {
            dis[x - 1] = dis[x] + 1;
            q.push(x - 1);
        }
        if (x * 2 < N && dis[x * 2] == -1)  // 走到2x位置
        {
            dis[x * 2] = dis[x] + 1;
            q.push(x * 2);
        }
        if (x == y)  // 到达终点
        {
            cout << dis[y] << endl;
            return;
        }
    }
}
int main()
{
    cin >> T;  // 输入测试用例数
    while (T--)
    {
        cin >> x >> y;  // 输入起点和终点
        bfs();
    }
    return 0;
}

【运行结果】

1
5 17
4
posted @ 2026-08-27 11:06  团爸讲算法  阅读(3)  评论(0)    收藏  举报