1 条题解

  • 0
    @ 2026-10-31 17:58:06

    思路

    把网格看成一张图:

    • 每个非障碍格子是一个点;
    • 上下左右相邻的两个非障碍格子之间连一条边权为 11 的边;
    • 同一字母的两个传送门格子之间连一条边权为 00 的边——站到任意一个传送门上,都可以不花步数走到配对的传送门。传送门格本身和普通格一样可以通行,也可以从传送门格继续上下左右移动。

    于是问题变成从 (1,1)(1,1) 到 (n,m)(n,m) 的最短路。图中边权只有 00 和 11,所以用 0-1 BFS:

    • 用双端队列维护待扩展的点,起点入队,距离为 00;
    • 每次取出队首 (x,y)(x,y),若已经处理过就跳过,否则标记为已处理。每个点第一次被取出时,它的距离就是最短路;
    • 00 权边(传送):若当前格是传送门,把配对格松弛为 dp[x][y]dp[x][y],松弛成功就压入队首;
    • 11 权边(上下左右):松弛成功就压入队尾。

    因为 00 权边压队首、11 权边压队尾,队列中的距离始终最多相差 11,出队顺序按距离非降,所以第一次取出某个点时它的距离已经最优。到达终点时直接输出 dp[n][m]dp[n][m];队列空了还没到达则输出 −1-1。

    注意不能写成普通 FIFO 队列:00 权边会让先入队的点带着更大的距离,出队顺序不再按距离单调,此时"一弹出终点就返回"可能得到偏大的答案。

    实现上先扫描一遍地图,把每种字母的两个格子记下来建立双向的传送映射;vis 保证每个点只按最终距离扩展一次,避免重复入队带来的额外开销。

    时间复杂度 O(nm)O(nm),空间 O(nm)O(nm)。

    代码

    #include<bits/stdc++.h>
    #define int long long
    #define pll pair<int,int>
    using namespace std;
    
    const int N = 1010, inf = 1e18;
    int dx[4] = {1,-1,0,0}, dy[4] = {0,0,1,-1};  // 上下左右四个方向
    char a[N][N];                               // 地图,'#' 表示障碍
    int dp[N][N];                               // 到达每个格子的最少步数
    bool vis[N][N];                             // 该格子是否已按最终距离扩展过
    
    int n,m;
    void go(){
        // 初始化:地图外一律视为障碍,距离设为无穷大
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
                a[i][j] = '#';
                dp[i][j] = inf;
                vis[i][j] = false;
            }
        }
        vector<vector<pll>>coor(100);  // coor[c] 存放字母 c 对应的传送门坐标
    
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>a[i][j];
    
                // 小写字母是传送门,按字母归类
                if('a' <= a[i][j] and a[i][j] <= 'z'){
                    coor[a[i][j]-'a'].push_back({i, j});
                }
            }
        }
        // 建立传送门映射:同字母的两个格子互相传送
        map<pll, pll>door;
        for(vector<pll> v:coor){
            if(v.size()==2){
                door[v[0]] = v[1];
                door[v[1]] = v[0];
            }
        }
    
        struct node
        {
            int x;
            int y;
        };
    
        // 0-1 BFS:0 权边压入队首,1 权边压入队尾
        deque<node>q;
        q.push_back({1, 1});
        dp[1][1] = 0;                 // 起点步数为 0
    
        while (q.size()) {
            auto [x, y] = q.front(); q.pop_front();
    
            // 每个点只按最终的最短距离扩展一次
            if (vis[x][y]) continue;
            vis[x][y] = true;
    
            // 第一次弹出终点就是最短路
            if (x == n && y == m) {
                cout << dp[x][y] << endl;
                return;
            }
    
            // 逻辑 1:处理传送(0 权边)
            if ('a' <= a[x][y] && a[x][y] <= 'z') {
                auto [mx, my] = door[{x, y}];
                // 传送不耗时,所以步数和当前一样
                if (dp[mx][my] > dp[x][y]) {
                    dp[mx][my] = dp[x][y];
                    q.push_front({mx, my});   // 0 权边放队首
                }
            }
    
            // 逻辑 2:处理常规上下左右移动(1 权边)
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                // 越界或是障碍就不能走
                if (nx < 1 || nx > n || ny < 1 || ny > m || a[nx][ny] == '#') continue;
    
                if (dp[nx][ny] > dp[x][y] + 1) {
                    dp[nx][ny] = dp[x][y] + 1;
                    q.push_back({nx, ny});    // 1 权边放队尾
                }
            }
        }
    
        // 队列为空仍未到达终点,说明无路可走
        cout<<-1<<endl;
    }
    
    signed main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);cout.tie(nullptr);
        int _=1;
        // 本题只有一组数据,不需要读入测试组数
        while(_--){
            go();
        }
        return 0;
    }
    
    • 1

    信息

    ID
    15
    时间
    1000ms
    内存
    256MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者