题解:洛谷 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
【核心思想】
-
问题分析:给定 FJ 初始位置 \(x\) 和牛的位置 \(y\)(牛不动),FJ 每次可移动到 \(p+1\)、\(p-1\) 或 \(2p\)。求抓住牛的最少移动次数。这是一个一维 BFS 最短路问题,关键在于将三种移动方式建模为边权为 \(1\) 的边,利用 BFS 的层序特性求最短路;同时需处理 \(x \ge y\) 时的特殊情况。
-
算法选择:
- BFS(广度优先搜索):三种移动方式代价均为 \(1\),BFS 天然适合求无权图最短路
- 特判优化:若 \(x = y\),答案为 \(0\);若 \(x > y\),只能一步步后退,答案为 \(x - y\)
- 距离数组:
dis[p]记录到达位置 \(p\) 的最少步数,初始化为 \(-1\) 表示未访问
-
关键步骤:
- 读取输入:\(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]\) 并结束
- 输出答案
-
时间/空间复杂度:
- 时间复杂度:\(O(T \cdot \max(x, y))\),每个位置最多入队一次
- 空间复杂度:\(O(\max(x, y))\),距离数组和队列
-
BFS 一维搜索的核心思想:
- 状态空间离散化:将连续数轴上的位置离散为节点,三种移动为无向边(除 \(2p\) 为单向)
- 层序扩展保证最优:BFS 按步数分层扩展,首次到达 \(y\) 时即为最少步数
- 剪枝与边界:\(x > y\) 时直接后退最优,避免无意义搜索;\(2p\) 可能越界需严格限制在 \([0, 10^5]\) 内
- 适用于"一维坐标上的最短步数"类问题,核心是建立状态转移图并用 BFS 求最短路
【解题思路】

【算法标签】
普及- #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
浙公网安备 33010602011771号