[USACO11OPEN] Corn Maze S

GitHub跳转原题关系图返回列表

预处理每个传送门端点的另一端,BFS 进入字母格时立即跳转并只增加一次移动时间。

OJ: luogu

题目 ID: P1825

难度:普及/提高-

标签:BFS网格最短路python

日期: 2026-07-16 18:01

题意

在玉米迷宫中从 @=。移动到相邻格花一单位时间;踏上成对大写字母后必须立即免费传送到另一个同字母端点。

思路

先扫描迷宫,按字母把大写传送门坐标存进 vector(每字母恰好两个位置)。

BFS 尝试走入相邻格:若不是墙且未被访问过,就进一步判断——如果是大写字母,则从 vector 中找到同字母的另一端跳转过去,再入队;否则直接入队。无论是否触发传送,步数都只加一。

因为步数代价统一为 1,普通 BFS 就能求出最短路。

Python 知识

  • defaultdict(list) 按字母收集两个端点。
  • 字典推导式配合 enumerate(pair) 同时建立两个方向的映射。
  • cell.isupper() 直接识别大写字母传送门。
  • 坐标元组可同时作为字典键、集合元素和队列状态。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/collections_toolkit.mddefaultdict、集合与 deque
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/bfs_shortest.md:网格最短路状态。

代码

python
from collections import defaultdict, deque


n, m = map(int, input().split())
maze = [input().strip() for _ in range(n)]
portals = defaultdict(list)

for x, row in enumerate(maze):
    for y, cell in enumerate(row):
        if cell == "@":
            start = x, y
        elif cell.isupper():
            portals[cell].append((x, y))

other_end = {
    point: pair[1 - index]
    for pair in portals.values()
    for index, point in enumerate(pair)
}

queue = deque([(*start, 0)])
visited = {start}
answer = -1

while queue:
    x, y, distance = queue.popleft()
    if maze[x][y] == "=":
        answer = distance
        break

    for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
        nxt = x + dx, y + dy
        if not (0 <= nxt[0] < n and 0 <= nxt[1] < m):
            continue
        if maze[nxt[0]][nxt[1]] == "#":
            continue
        if nxt in other_end:
            nxt = other_end[nxt]
        if nxt not in visited:
            visited.add(nxt)
            queue.append((*nxt, distance + 1))

print(answer)
cpp
/* author: Rainboy email: rainboylvx@qq.com  time: 2022年 02月 13日 星期日 19:37:03 CST */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3+5,maxe = 1e3+5; //点与边的数量

/* 顺时针-4个方向 */
int fx[][2] = { {-1,0}, {0,1}, {1,0}, {0,-1} };
int n,m;

// 判断 (x,y) 是否在迷宫范围内
bool in_mg(int x,int y){
    return x >= 1 && x <=n && y >=0  && y<m;
}

// BFS 队列节点:x,y 为坐标,s 为起点到该点的最少步数
struct node {
    int x,y,s;
};
bool vis[maxn][maxn];   // 访问标记

string mg[maxn];        // 迷宫,每行一个字符串
int sx,sy,tx,ty;        // 起点 @,终点 =

vector<node> v[30];     // v[c] 存储所有大写字母 c 对应的传送门坐标(每字母恰好两个)


void init(){
    std::cin >> n >> m;
    for(int i=1;i<=n;++i){
        std::cin >> mg[i];
        for(int j=0;j<=m-1;++j){
            if( mg[i][j] == '='){
                tx = i;
                ty = j;
            }
            else if(mg[i][j] == '@'){
                sx = i;
                sy = j;
            }
            else if(std::isupper(mg[i][j])){
                v[mg[i][j] - 'A'].push_back({i,j,0}); // 记录传送门位置
            }
        }
    }
}

// BFS 求最短路,遇到传送门立即跳到另一端
int bfs(){
    queue<node> q;
    q.push({sx,sy,0});
    vis[sx][sy] = 1;

    while ( !q.empty() ) {
        node h = q.front();
        q.pop();
        if( h.x == tx && h.y == ty ) return h.s; // 到达终点

        for(int i=0;i<=3;++i){
            int nx = h.x + fx[i][0];
            int ny = h.y + fx[i][1];

            // 越界、撞墙、已访问 则跳过
            if( !in_mg(nx, ny) || mg[nx][ny] == '#' || vis[nx][ny] ) continue;

            if( isupper(mg[nx][ny])){ // 踩到传送门
                int t = mg[nx][ny] - 'A';
                vis[nx][ny] = 1;
                // 找到同一字母的另一个传送门,跳转过去
                for(int k = 0 ;k < v[t].size() ; ++k){
                    if( v[t][k].x != nx ||  v[t][k].y != ny  ){
                        nx = v[t][k].x;
                        ny = v[t][k].y;
                        break;
                    }
                }
                q.push({nx,ny,h.s+1});
            }
            else { // 普通格子
                q.push({nx,ny,h.s+1});
                vis[nx][ny] = 1;
            }
        }
    }
    return -1; // 无法到达
}

int main(int argc,char * argv[]){
    init();
    int ans = bfs();
    std::cout << ans << std::endl;
    return 0;
}

复杂度

预处理和 BFS 都至多扫描常数次网格,时间与空间复杂度均为 O(nm)O(nm)

总结

免费传送不需要额外入队一层:在生成邻居时直接把端点规范成另一端,就能继续使用普通 BFS 的层数作为时间。