题解:洛谷 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

【核心思想】

  1. 问题分析:给定起点 \((0,0)\)、终点 \((X,Y)\)\(N\) 个泥坑坐标,FJ 只能沿坐标轴方向在整数点移动,不能踩泥坑。求到达终点的最短距离。这是一个BFS最短路/网格避障问题,关键在于将坐标偏移处理(避免负数下标),用BFS在无障碍网格上求最短路。

  2. 算法选择

    • BFS(广度优先搜索):四方向移动,边权均为 \(1\),BFS天然适合求无权图最短路
    • 坐标偏移:所有坐标加 \(500\),将 \([-500, 500]\) 映射到 \([0, 1000]\),避免负数数组下标
    • 边界剪枝:根据泥坑和起终点的坐标范围,限制BFS搜索边界,避免无限扩展
  3. 关键步骤

    • 读取输入\(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\) 并结束
        • 若不是泥坑且未访问:标记访问,入队
    • 输出答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O((\text{搜索范围宽度}) \times (\text{搜索范围高度}))\),最坏 \(O(1002 \times 1002)\)
    • 空间复杂度:\(O(1005 \times 1005)\),访问标记和泥坑标记数组
  5. 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
posted @ 2026-08-28 15:55  团爸讲算法  阅读(2)  评论(0)    收藏  举报