题解:洛谷 P2873 Mud Puddles
【题目来源】
洛谷:P2873 [USACO07DEC] Mud Puddles S - 洛谷
【题目描述】
农夫约翰在早上 \(6\) 点准时离开家去挤奶贝茜。然而,前一天晚上下了大雨,田地里非常泥泞。约翰从坐标平面上的点 \((0,0)\) 出发,前往位于 \((X,Y)\) 的贝茜(\(-500\le X\le 500\);\(-500\le Y\le 500\))。他可以看到田地上所有 \(N(1\le N\le 10,000)\) 个泥坑,位于点 \((A_i,B_i)\)(\(-500\le A_i\le 500\);\(-500\le B_i\le 500\))。每个泥坑只占据它所在的点。
刚买了新靴子的农夫约翰绝对不想弄脏他的靴子,但他也想尽快到达贝茜。他已经迟到了,因为他不得不数清所有的泥坑。如果农夫约翰只能平行于坐标轴行走,并且只能在整数坐标点处转弯,那么他到达贝茜并保持靴子干净的最短距离是多少?总会有一条没有泥的路径可以让农夫约翰到达贝茜。
【输入】
- 第 \(1\) 行:三个用空格分隔的整数:\(X,Y,N\)。
- 第 \(2\) 行到第 \(N+1\) 行:第 \(i+1\) 行包含两个用空格分隔的整数:\(A_i\) 和 \(B_i\)。
【输出】
- 第 \(1\) 行:农夫约翰到达贝茜而不踩到泥的最短距离。
【输入样例】
1 2 7
0 2
-1 3
3 1
1 1
4 2
-1 1
2 2
【输出样例】
11
【核心思想】
-
问题分析:给定起点 \((0,0)\)、终点 \((X,Y)\) 和 \(N\) 个泥坑坐标,FJ 只能沿坐标轴方向在整数点移动,不能踩泥坑。求到达终点的最短距离。这是一个BFS最短路/网格避障问题,关键在于将坐标偏移处理(避免负数下标),用BFS在无障碍网格上求最短路。
-
算法选择:
- BFS(广度优先搜索):四方向移动,边权均为 \(1\),BFS天然适合求无权图最短路
- 坐标偏移:所有坐标加 \(500\),将 \([-500, 500]\) 映射到 \([0, 1000]\),避免负数数组下标
- 边界剪枝:根据泥坑和起终点的坐标范围,限制BFS搜索边界,避免无限扩展
-
关键步骤:
- 读取输入:\(X, Y, N\) 和 \(N\) 个泥坑坐标 \((A_i, B_i)\)
- 坐标偏移与标记:所有坐标加 \(500\),泥坑位置 \(a[A_i+500][B_i+500] = 1\)
- 确定搜索边界:
- \(minx = \min(\text{所有泥坑}x, 500)\),\(maxx = \max(\text{所有泥坑}x, 500)\)
- \(miny = \min(\text{所有泥坑}y, 500)\),\(maxy = \max(\text{所有泥坑}y, 500)\)
- 搜索范围扩展 \(1\) 格边界:\([minx-1, maxx+1] \times [miny-1, maxy+1]\)
- BFS过程:
- 起点 \((500, 500)\) 入队,步数 \(step = 0\)
- 取出队首,四方向扩展:
- 若越界:跳过
- 若到达终点 \((X+500, Y+500)\):输出 \(step + 1\) 并结束
- 若不是泥坑且未访问:标记访问,入队
- 输出答案
-
时间/空间复杂度:
- 时间复杂度:\(O((\text{搜索范围宽度}) \times (\text{搜索范围高度}))\),最坏 \(O(1002 \times 1002)\)
- 空间复杂度:\(O(1005 \times 1005)\),访问标记和泥坑标记数组
-
BFS网格最短路的核忈思想:
- 坐标偏移处理负数:通过统一加偏移量,将可能为负的坐标映射到非负数组下标
- 层序扩展保证最优:BFS按步数分层,首次到达终点时即为最短路径(曼哈顿距离意义下)
- 边界限制优化:根据输入数据的实际范围确定搜索边界,避免向无穷远处扩展
- 适用于"网格避障最短路径"类问题,核心是正确处理坐标和边界
【解题思路】

【算法标签】
普及- #BFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, sx, sy, minx=1e9, miny=1e9, maxx=-1e9, maxy=-1e9, dx[4]={-1,1,0,0}, dy[4]={0,0,-1,1}, tx, ty, tstep;
int a[1005][1005]={0}, vis[1005][1005]={0};
struct node {
int x, y, step, visit;
};
int main()
{
cin >> sx >> sy >> n; // 输入结束坐标及n
for (int i=1; i<=n; i++) { // 遍历n个泥塘坐标
int tmp1, tmp2;
cin >> tmp1 >> tmp2; // 输入坐标
minx = min(minx, tmp1+500); // 计算x坐标最小值
maxx = max(maxx, tmp1+500); // 计算x坐标最大值
miny = min(miny, tmp2+500); // 计算y坐标最小值
maxy = max(maxy, tmp2+500); // 计算y坐标最大值
a[tmp1+500][tmp2+500]=1; // 因为存在负数,x和y均加上500,对于泥塘的坐标,a数组置为1
}
minx = min(minx, 500); // 再与(0,0)(即(500,500))进行比较
maxx = max(maxx, 500);
miny = min(miny, 500);
maxy = max(maxy, 500);
queue<node> q; // 定义队列
node tp = {500, 500, 0}; // 队头数据
q.push(tp); // 压入队列
while (!q.empty()) { // bfs模板
tp = q.front();
q.pop();
for (int i=0; i<4; i++) { // 进行上下左右位置移动
tx = tp.x + dx[i];
ty = tp.y + dy[i];
tstep = tp.step+1; // 每移动一个格子,step加1
if (tx<minx-1||tx>maxx+1||ty<miny-1||ty>maxy+1) continue; // 当移动后的坐标越界时,继续循环
if (tx==sx+500 && ty==sy+500) { // 当遇到终点时,打印step,退出循环
cout << tstep << endl;
return 0;
}
if (a[tx][ty]==0 && vis[tx][ty]==0) { // 将要移动的坐标不是泥塘,且没有访问过
vis[tx][ty]=1; // 更新vis数组
node t = {tx, ty, tstep}; // 初始化t节点
q.push(t); // 将其压入队列中
}
}
}
return 0;
}
【运行结果】
1 2 7
0 2
-1 3
3 1
1 1
4 2
-1 1
2 2
11
浙公网安备 33010602011771号