洛谷 P1825 Corn Maze S:BFS 迷宫最短路径完全解析

题目分析

问题描述

在一个 N×M 的玉米迷宫网格中(2 ≤ N, M ≤ 300),奶牛需要从起点 @ 出发到达终点 =。迷宫包含以下元素:

  • 墙壁# 表示,不可通行
  • 草地. 表示,可通行,移动耗时1单位
  • 传送装置:大写字母 A-Z 表示,双向传送且耗时0
  • 起点@
  • 终点=

核心挑战:正确处理传送装置的瞬时传送机制,在BFS框架下保证找到最短路径。

算法核心:BFS与传送装置处理

算法选择依据

BFS(广度优先搜索)是解决无权图最短路径问题的经典算法。在本问题中,迷宫网格可视为图结构,节点为网格点,边为移动或传送关系。BFS按距离起点步数递增的顺序遍历节点,确保首次到达终点即为最短路径

关键难点:传送装置处理

传送装置的特殊性在于:

  1. 双向性:从任一端可瞬间到达另一端
  2. 零耗时:传送不增加步数
  3. 强制使用:如果奶牛踩到滑梯的任一端,她必须使用滑梯

完整AC代码解析

#include <bits/stdc++.h>
#include <queue>
using namespace std;

struct pos
{
    int x,y,step;    //step指到达这个点所需的最少步数 
    pos()
    {
        x=y=-1;        //初始化成为一个非法坐标以便后续的赋值 
        step=0;        //开始前是0步 
    }
};

struct spt        // spread_point传送点 
{
    pos p1,p2;    //两个配对的传送点    
};

pos s,f;    //起点和终点的坐标 
int n,m;    //地图长和宽 
char a[305][305];    //地图 
bool v[305][305];    //标记数组 
int dx[] = {0,1,0,-1};    
int dy[] = {1,0,-1,0};    //四个方向的移动 
spt spts[200];        //传送点 
queue<pos> q;        //bfs队列 

bool check(int x,int y)
{
    if(x<0||x>n||y<0||y>m||a[x][y]=='#'||v[x][y]==1)
        return 0;
    return 1;
}        

//合法判断函数 
void bfs()
{
    v[s.x][s.y]=1;
    pos t;
    t.x=s.x,t.y=s.y,t.step=0;
    q.push(t);
    while(!q.empty())
    {
        pos tmp=q.front();
        q.pop();
        int x=tmp.x,y=tmp.y,step=tmp.step; 
        
        if(tmp.x==f.x&&tmp.y==f.y)
        {
            cout<<tmp.step;
            return;
        }
        
        for(int i=0; i<4; i++)
        {
            int xx=x+dx[i];
            int yy=y+dy[i];
            if(check(xx,yy))
            {
                pos val;
                if(a[xx][yy]<'A'||a[xx][yy]>'Z')
                {
                    val.x=xx,val.y=yy,val.step=step+1;
                    v[xx][yy]=1;
                }
                else
                {
                    if(xx==spts[a[xx][yy]-'A'].p1.x&&yy==spts[a[xx][yy]-'A'].p1.y)
                    {
                        val.x=spts[a[xx][yy]-'A'].p2.x;
                        val.y=spts[a[xx][yy]-'A'].p2.y;
                        val.step=step+1;
                    }
                    else
                    {
                        val.x=spts[a[xx][yy]-'A'].p1.x;
                        val.y=spts[a[xx][yy]-'A'].p1.y;
                        val.step=step+1;
                    }
                }
                q.push(val);
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<m; j++)
        {
            cin>>a[i][j];
            if(a[i][j]=='@')
            {
                s.x=i;
                s.y=j;
            }
            if(a[i][j]=='=')
            {
                f.x=i;
                f.y=j;
            }
            if(a[i][j]>='A'&&a[i][j]<='Z')
            {
                int c=a[i][j]-'A';
                if(spts[c].p1.x==-1&&spts[c].p1.y==-1)
                {
                    spts[c].p1.x=i;        
                    spts[c].p1.y=j;
                }
                else
                {
                    spts[c].p2.x=i;
                    spts[c].p2.y=j;
                }
            }
        }
    }
    bfs();
}

代码详解

1. 数据结构设计

struct pos {
    int x, y, step;
};

struct spt {
    pos p1, p2;
};

spt spts[200]; // 传送点存储
  • 位置结构体:存储坐标(x,y)和到达该点的步数
  • 传送点结构体:为每个字母存储一对坐标,表示传送装置的两个端点
  • 访问标记数组v[x][y] 避免重复处理同一位置

2. BFS核心逻辑

void bfs() {
    // 初始化队列和标记
    while (!q.empty()) {
        // 终止条件判断
        if (到达终点) {
            cout << 步数;
            return;
        }
        
        // 四个方向移动探索
        for (四个方向) {
            if (检查合法性) {
                if (是传送装置) {
                    // 处理传送逻辑
                } else {
                    // 常规移动处理
                }
            }
        }
    }
}

3. 关键处理细节

传送装置处理

if (a[xx][yy] >= 'A' && a[xx][yy] <= 'Z') {
    if (当前点是传送装置的第一端点) {
        val.x = 第二端点x;
        val.y = 第二端点y;
    } else {
        val.x = 第一端点x;
        val.y = 第一端点y;
    }
    val.step = step + 1;
}
  • 端点识别:通过比较坐标确定当前是传送装置的哪个端点
  • 正确传送:传送到对应的另一端
  • 步数处理:传送操作不增加额外步数,保持与移动到传送点相同的步数

算法正确性分析

BFS最优性保证

广度优先搜索按距离起点步数递增的顺序遍历节点。由于:

  1. 所有移动操作耗时1
  2. 所有传送操作耗时0
  3. 队列始终按步数排序

因此首次到达终点时记录的步数即为最短时间

传送处理完备性

对于每个传送装置:

  1. 双向处理:从任一端可到达另一端
  2. 即时性:传送在BFS同一层内完成
  3. 强制使用:符合题目要求,踩到传送点必须使用

复杂度分析

时间复杂度

  • BFS遍历:每个节点最多被处理一次,O(N×M)
  • 传送处理:每个传送装置最多处理常数次
  • 总体复杂度:O(N×M),满足题目约束(N,M ≤ 300)

空间复杂度

  • 迷宫存储:O(N×M)
  • 距离数组:O(N×M)
  • 队列空间:最坏情况O(N×M)
  • 传送映射:O(26×2) 存储传送端点

测试用例验证

样例输入1

5 6
###=##
#.W.##
#.@W##
#.####
#.####

预期输出:考虑传送装置的最短路径

边界情况测试

  1. 无传送装置:纯迷宫最短路径
  2. 起点即终点:直接返回0
  3. 多重传送:连续经过多个传送装置
  4. 无法到达:输出-1(但题目保证有解)

常见错误排查

1. WA(Wrong Answer)常见原因

  • 传送处理不当
    • 未正确处理传送的强制性和双向性
    • 传送后错误地继续执行常规移动
    • 步数计算错误(传送应保持步数不变)
  • 访问控制问题
    • 未正确标记已访问节点导致重复处理
    • 边界条件处理不完整

2. 关键调试点

  1. 传送逻辑验证:确保踩到传送点必须传送且正确选择对应端点
  2. 步数验证:移动步数+1,传送步数不变
  3. 队列状态:检查BFS扩展顺序是否符合预期

总结

本题通过BFS算法结合传送装置的特殊处理机制,解决了玉米迷宫中的最短路径问题。关键改进点在于:

  1. 强制传送处理:踩到传送装置必须使用,且正确传送到对应端点
  2. 步数精确控制:严格区分移动和传送的步数计算
  3. 状态有效管理:通过访问标记避免重复处理

该代码已通过洛谷测试,保证了正确性和效率。掌握这种带特殊规则的BFS解决方法,对于解决复杂迷宫类问题和图论变种问题具有重要意义。

  🔥 关注我,解锁CSP-J/S竞赛全攻略 🔥

(每日更新高频考点 + 精选真题解析,助你轻松备赛!)
👇 点击关注立即提升竞赛战力 👇
[https://blog.csdn.net/stillwatersss]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨小码不BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值