{ }codelper
深度优先搜索栈P0190

边捡钥匙边摸索的洞穴探险

给出一张 R 行 C 列的洞穴地图。
'.' 是空格,'#' 是岩石,'S' 是探险家起初所在的空格,英文小写字母是放着该字母钥匙的空格,除 'S' 以外的英文大写字母是门。
只有已经持有同一字母的小写钥匙时,才能进入大写字母的门(例如 'B' 门需要 'b' 钥匙)。
钥匙在第一次进入那个格子时捡起,捡到的钥匙一直带在身上。
探险家把起始格标记为已到过,然后重复下面的过程。
从当前格按上、右、下、左的顺序查看相邻格,移动一步到第一个在地图内、不是岩石、还没到过、而且能进入的格子,并把那个格子标记为已到过。
如果没有这样的相邻格,就退回一步,回到第一次进入当前格时离开的那个格子。
在退回到的格子上,也重新从上方开始查看。
在起始格没有可去的相邻格时,探险结束。
前进和退回都算移动一次。
第一行输出移动次数 T 和到过的格子数(包括起始格),用空格分开。
第二行按捡到的顺序连起来输出新捡到的钥匙字母(已经有的字母的钥匙再次捡到时不写)。
一个都没有就输出 '-'。
接着对每个询问 t,在一行中输出刚好移动 t 次后探险家所在格子的行号和列号(左上角是 1 1)。
移动 0 次后是起始格;t 大于 T 时,是探险结束的格子(起始格)。

输入
第一行给出 R 和 C,接下来 R 行是长度为 C 的地图字符串,下一行给出询问数 Q,再下一行给出 Q 个询问 t,用空格分开。
1 ≤ R, C ≤ 50,'S' 恰好一个,1 ≤ Q ≤ 100,0 ≤ t ≤ 10,000。
输出
第一行是 T 和到过的格子数,第二行是捡到的钥匙字母或 '-',接下来 Q 行每行 '行 列'
样例 1 · stdin
3 4
S.A.
.#b#
a...
4
0 3 10 100
stdout
18 10
ab
1 1
2 1
1 3
1 1
样例 2 · stdin
3 3
#A#
#Sa
###
6
0 1 2 3 4 5
stdout
4 3
a
2 2
2 3
2 2
1 2
2 2
2 2
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

沿着墙摸索、走不通就退回的探险就是深度优先搜索(DFS),把"到目前为止走进来的路"当作栈带着走就行:前进时压栈(push),走不通时弹栈(pop)并回到新的栈顶格子。
栈里只剩起始格又无处可去时就结束。
最常见的陷阱是直接用普通的递归 DFS(for 每个方向递归调用):返回后从下一个方向接着看,所以不会再检查这期间捡到的钥匙打开的前面方向的门(例 2 中永远打不开上方的 'A' 门,T 变成 2)。
本题规定每次站到一个格子上都从上方重新查看,所以每次在栈顶格子从头查看四个方向即可(有已到过的标记,一个格子重新查看最多也就四个方向,很快)。
第二个陷阱是深度:在空旷的 50×50 洞穴里路径会深达 2,500 格,用递归解会超过 Python 默认的递归上限(1,000),Craykon 也可能在几千层时停下——所以自己做的栈才安全。
另外:退回的步子也要算移动;t 大于 T 时回答起始格(不要越过数组末尾去读);行号、列号从 1 开始。
各语言:Python 用列表 append/pop 和 for … else(没有 break 地结束时执行 else),Java 用 ArrayDeque<int[]> 的 push/pop/peek,C# 用 Stack<(int y, int x)> 和 Array.FindIndex,HashSet.Add 只有真正加入时才返回 true,能马上知道钥匙是不是第一次捡到。
C++ 把 std::vector 当栈用(push_back/pop_back/back)配合 std::find_if,C 用格子编号数组加栈顶下标变量,JavaScript、TypeScript 用数组 push/pop 和 STEPS.find(…)(找不到是 undefined)。
Craykon 把格子展开成一个数 y * c + x,对列表 path 用 添加/按位置移除(数量 - 1),用 ch.小写() 找门的钥匙。
Craykon 读取列表范围之外会出运行错误,所以用 数学.较小值(t, moves) 挡住末端。

Pythonmain.py · 54 行
import sys data = sys.stdin.read().split("\n")r, c = map(int, data[0].split())grid = [data[1 + y].strip() for y in range(r)]q = int(data[1 + r])queries = list(map(int, data[2 + r].split())) STEPS = ((-1, 0), (0, 1), (1, 0), (0, -1))  # 위, 오른쪽, 아래, 왼쪽start = next((y, x) for y in range(r) for x in range(c) if grid[y][x] == "S")visited = [[False] * c for _ in range(r)]visited[start[0]][start[1]] = Truekeys = set()picked = []          # 새로 얻은 열쇠(얻은 차례)path = [start]       # 지금까지 들어온 길(스택): 맨 위가 지금 자리log = [start]        # log[t] = t번 움직인 뒤의 자리  def can_enter(y, x):    if not (0 <= y < r and 0 <= x < c) or visited[y][x]:        return False    ch = grid[y][x]    if ch == "#":        return False    if ch.isupper():        return ch.lower() in keys  # 문은 맞는 열쇠가 있어야 들어간다    return True  while True:    y, x = path[-1]    # 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다    for dy, dx in STEPS:        ny, nx = y + dy, x + dx        if can_enter(ny, nx):            visited[ny][nx] = True            ch = grid[ny][nx]            if ch.islower() and ch not in keys:                keys.add(ch)                picked.append(ch)            path.append((ny, nx))            break    else:        if len(path) == 1:            break  # 처음 자리에서 더 갈 곳이 없으면 멈춘다        path.pop()  # 왔던 칸으로 한 칸 되돌아간다    log.append(path[-1]) moves = len(log) - 1out = [f"{moves} {sum(map(sum, visited))}", "".join(picked) or "-"]for t in queries:    y, x = log[min(t, moves)]  # 멈춘 뒤에는 처음 자리에 그대로 있다    out.append(f"{y + 1} {x + 1}")print("\n".join(out))
JavaScriptmain.js · 54 行
const data = require("fs").readFileSync(0, "utf8").split("\n");const [r, c] = data[0].trim().split(/\s+/).map(Number);const grid = [];for (let y = 0; y < r; y++) grid.push(data[1 + y].trim());const queries = data[2 + r].trim().split(/\s+/).map(Number); const STEPS = [[-1, 0], [0, 1], [1, 0], [0, -1]]; // 위, 오른쪽, 아래, 왼쪽let start = null;for (let y = 0; y < r; y++) for (let x = 0; x < c; x++) if (grid[y][x] === "S") start = [y, x];const visited = Array.from({ length: r }, () => new Array(c).fill(false));visited[start[0]][start[1]] = true;const keys = new Set();let picked = "";      // 새로 얻은 열쇠(얻은 차례)const path = [start]; // 지금까지 들어온 길(스택): 맨 끝이 지금 자리const log = [start];  // log[t] = t번 움직인 뒤의 자리 const isUpper = (ch) => ch >= "A" && ch <= "Z";const isLower = (ch) => ch >= "a" && ch <= "z";function canEnter(y, x) {  if (y < 0 || y >= r || x < 0 || x >= c || visited[y][x]) return false;  const ch = grid[y][x];  if (ch === "#") return false;  if (isUpper(ch)) return keys.has(ch.toLowerCase()); // 문은 맞는 열쇠가 있어야 들어간다  return true;} while (true) {  const [y, x] = path[path.length - 1];  // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다  const step = STEPS.find(([dy, dx]) => canEnter(y + dy, x + dx));  if (step !== undefined) {    const ny = y + step[0], nx = x + step[1];    visited[ny][nx] = true;    const ch = grid[ny][nx];    if (isLower(ch) && !keys.has(ch)) {      keys.add(ch);      picked += ch;    }    path.push([ny, nx]);  } else {    if (path.length === 1) break; // 처음 자리에서 더 갈 곳이 없으면 멈춘다    path.pop(); // 왔던 칸으로 한 칸 되돌아간다  }  log.push(path[path.length - 1]);} const moves = log.length - 1;const seen = visited.flat().filter(Boolean).length;const out = [`${moves} ${seen}`, picked || "-"];for (const t of queries) {  const [y, x] = log[Math.min(t, moves)]; // 멈춘 뒤에는 처음 자리에 그대로 있다  out.push(`${y + 1} ${x + 1}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 57 行
import { readFileSync } from "node:fs"; type Cell = [number, number];const data: string[] = readFileSync(0, "utf8").split("\n");const [r, c] = data[0].trim().split(/\s+/).map(Number);const grid: string[] = [];for (let y = 0; y < r; y++) grid.push(data[1 + y].trim());const queries = data[2 + r].trim().split(/\s+/).map(Number); const STEPS: Cell[] = [[-1, 0], [0, 1], [1, 0], [0, -1]]; // 위, 오른쪽, 아래, 왼쪽let start: Cell = [0, 0];for (let y = 0; y < r; y++) for (let x = 0; x < c; x++) if (grid[y][x] === "S") start = [y, x];const visited: boolean[][] = Array.from({ length: r }, () => new Array<boolean>(c).fill(false));visited[start[0]][start[1]] = true;const keys = new Set<string>();let picked = "";      // 새로 얻은 열쇠(얻은 차례)const path: Cell[] = [start]; // 지금까지 들어온 길(스택): 맨 끝이 지금 자리const log: Cell[] = [start]; // log[t] = t번 움직인 뒤의 자리 const isUpper = (ch: string): boolean => ch >= "A" && ch <= "Z";const isLower = (ch: string): boolean => ch >= "a" && ch <= "z";function canEnter(y: number, x: number): boolean {  if (y < 0 || y >= r || x < 0 || x >= c || visited[y][x]) return false;  const ch = grid[y][x];  if (ch === "#") return false;  if (isUpper(ch)) return keys.has(ch.toLowerCase()); // 문은 맞는 열쇠가 있어야 들어간다  return true;} while (true) {  const [y, x] = path[path.length - 1];  // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다  const step = STEPS.find(([dy, dx]) => canEnter(y + dy, x + dx));  if (step !== undefined) {    const ny = y + step[0], nx = x + step[1];    visited[ny][nx] = true;    const ch = grid[ny][nx];    if (isLower(ch) && !keys.has(ch)) {      keys.add(ch);      picked += ch;    }    path.push([ny, nx]);  } else {    if (path.length === 1) break; // 처음 자리에서 더 갈 곳이 없으면 멈춘다    path.pop(); // 왔던 칸으로 한 칸 되돌아간다  }  log.push(path[path.length - 1]);} const moves = log.length - 1;const seen = visited.flat().filter(Boolean).length;const out = [`${moves} ${seen}`, picked || "-"];for (const t of queries) {  const [y, x] = log[Math.min(t, moves)]; // 멈춘 뒤에는 처음 자리에 그대로 있다  out.push(`${y + 1} ${x + 1}`);}console.log(out.join("\n"));
JavaMain.java · 79 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.List;import java.util.StringTokenizer; public class Main {    static int r, c;    static char[][] grid;    static boolean[][] visited;    static boolean[] keys = new boolean[26];     static boolean canEnter(int y, int x) {        if (y < 0 || y >= r || x < 0 || x >= c || visited[y][x]) return false;        char ch = grid[y][x];        if (ch == '#') return false;        if (Character.isUpperCase(ch)) return keys[ch - 'A']; // 문은 맞는 열쇠가 있어야 들어간다        return true;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        r = Integer.parseInt(st.nextToken());        c = Integer.parseInt(st.nextToken());        grid = new char[r][];        int sy = 0, sx = 0;        for (int y = 0; y < r; y++) {            grid[y] = in.readLine().trim().toCharArray();            for (int x = 0; x < c; x++)                if (grid[y][x] == 'S') { sy = y; sx = x; }        }        int q = Integer.parseInt(in.readLine().trim());        st = new StringTokenizer(in.readLine());         int[] dy = {-1, 0, 1, 0}, dx = {0, 1, 0, -1}; // 위, 오른쪽, 아래, 왼쪽        visited = new boolean[r][c];        visited[sy][sx] = true;        StringBuilder picked = new StringBuilder(); // 새로 얻은 열쇠(얻은 차례)        ArrayDeque<int[]> path = new ArrayDeque<>(); // 지금까지 들어온 길(스택): 맨 위가 지금 자리        path.push(new int[] {sy, sx});        List<int[]> log = new ArrayList<>();         // log.get(t) = t번 움직인 뒤의 자리        log.add(path.peek());        int seen = 1;        while (true) {            int[] cur = path.peek();            // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다            int d = 0;            while (d < 4 && !canEnter(cur[0] + dy[d], cur[1] + dx[d])) d++;            if (d < 4) {                int ny = cur[0] + dy[d], nx = cur[1] + dx[d];                visited[ny][nx] = true;                seen++;                char ch = grid[ny][nx];                if (Character.isLowerCase(ch) && !keys[ch - 'a']) {                    keys[ch - 'a'] = true;                    picked.append(ch);                }                path.push(new int[] {ny, nx});            } else {                if (path.size() == 1) break; // 처음 자리에서 더 갈 곳이 없으면 멈춘다                path.pop();                   // 왔던 칸으로 한 칸 되돌아간다            }            log.add(path.peek());        }         int moves = log.size() - 1;        StringBuilder out = new StringBuilder();        out.append(moves).append(' ').append(seen).append('\n');        out.append(picked.length() > 0 ? picked : "-").append('\n');        for (int i = 0; i < q; i++) {            int[] at = log.get(Math.min(Integer.parseInt(st.nextToken()), moves)); // 멈춘 뒤에는 처음 자리에 그대로 있다            out.append(at[0] + 1).append(' ').append(at[1] + 1).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 75 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    static void Main()    {        int[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        int r = first[0], c = first[1];        var grid = new string[r];        (int y, int x) start = (0, 0);        for (int y = 0; y < r; y++)        {            grid[y] = Console.ReadLine().Trim();            int x = grid[y].IndexOf('S');            if (x >= 0) start = (y, x);        }        int q = int.Parse(Console.ReadLine().Trim());        int[] queries = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();         (int dy, int dx)[] steps = { (-1, 0), (0, 1), (1, 0), (0, -1) }; // 위, 오른쪽, 아래, 왼쪽        var visited = new bool[r, c];        visited[start.y, start.x] = true;        var keys = new HashSet<char>();        var picked = new StringBuilder();          // 새로 얻은 열쇠(얻은 차례)        var path = new Stack<(int y, int x)>();    // 지금까지 들어온 길(스택): 맨 위가 지금 자리        path.Push(start);        var log = new List<(int y, int x)> { start }; // log[t] = t번 움직인 뒤의 자리        int seen = 1;         bool CanEnter(int y, int x)        {            if (y < 0 || y >= r || x < 0 || x >= c || visited[y, x]) return false;            char ch = grid[y][x];            if (ch == '#') return false;            if (char.IsUpper(ch)) return keys.Contains(char.ToLower(ch)); // 문은 맞는 열쇠가 있어야 들어간다            return true;        }         while (true)        {            var (y, x) = path.Peek();            // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다            int d = Array.FindIndex(steps, s => CanEnter(y + s.dy, x + s.dx));            if (d >= 0)            {                int ny = y + steps[d].dy, nx = x + steps[d].dx;                visited[ny, nx] = true;                seen++;                char ch = grid[ny][nx];                if (char.IsLower(ch) && keys.Add(ch)) picked.Append(ch); // Add는 새로 넣었을 때만 true                path.Push((ny, nx));            }            else            {                if (path.Count == 1) break; // 처음 자리에서 더 갈 곳이 없으면 멈춘다                path.Pop();                 // 왔던 칸으로 한 칸 되돌아간다            }            log.Add(path.Peek());        }         int moves = log.Count - 1;        var out_ = new StringBuilder();        out_.Append(moves).Append(' ').Append(seen).Append('\n');        out_.Append(picked.Length > 0 ? picked.ToString() : "-").Append('\n');        foreach (int t in queries)        {            var (y, x) = log[Math.Min(t, moves)]; // 멈춘 뒤에는 처음 자리에 그대로 있다            out_.Append(y + 1).Append(' ').Append(x + 1).Append('\n');        }        Console.Write(out_);    }}
C++main.cpp · 66 行
#include <algorithm>#include <array>#include <cctype>#include <iostream>#include <string>#include <utility>#include <vector> int main() {    int r, c;    std::cin >> r >> c;    std::vector<std::string> grid(r);    std::pair<int, int> start{0, 0};    for (int y = 0; y < r; y++) {        std::cin >> grid[y];        if (auto x = grid[y].find('S'); x != std::string::npos) start = {y, static_cast<int>(x)};    }    const std::array<std::pair<int, int>, 4> STEPS{{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}};  // 위, 오른쪽, 아래, 왼쪽    std::vector<std::vector<bool>> visited(r, std::vector<bool>(c, false));    std::array<bool, 26> keys{};    std::string picked;                               // 새로 얻은 열쇠(얻은 차례)    std::vector<std::pair<int, int>> path{start};     // 지금까지 들어온 길(스택): 맨 끝이 지금 자리    std::vector<std::pair<int, int>> log{start};      // log[t] = t번 움직인 뒤의 자리    visited[start.first][start.second] = true;    int seen = 1;     auto canEnter = [&](int y, int x) {        if (y < 0 || y >= r || x < 0 || x >= c || visited[y][x]) return false;        char ch = grid[y][x];        if (ch == '#') return false;        if (std::isupper(static_cast<unsigned char>(ch))) return keys[ch - 'A'];  // 문은 맞는 열쇠가 있어야 들어간다        return true;    };     while (true) {        auto [y, x] = path.back();        // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다        auto it = std::find_if(STEPS.begin(), STEPS.end(), [&](const auto& s) { return canEnter(y + s.first, x + s.second); });        if (it != STEPS.end()) {            int ny = y + it->first, nx = x + it->second;            visited[ny][nx] = true;            seen++;            char ch = grid[ny][nx];            if (std::islower(static_cast<unsigned char>(ch)) && !keys[ch - 'a']) {                keys[ch - 'a'] = true;                picked += ch;            }            path.emplace_back(ny, nx);        } else {            if (path.size() == 1) break;  // 처음 자리에서 더 갈 곳이 없으면 멈춘다            path.pop_back();              // 왔던 칸으로 한 칸 되돌아간다        }        log.push_back(path.back());    }     int moves = static_cast<int>(log.size()) - 1;    std::cout << moves << " " << seen << "\n" << (picked.empty() ? "-" : picked) << "\n";    int q;    std::cin >> q;    for (int i = 0; i < q; i++) {        int t;        std::cin >> t;        auto [y, x] = log[std::min(t, moves)];  // 멈춘 뒤에는 처음 자리에 그대로 있다        std::cout << y + 1 << " " << x + 1 << "\n";    }}
Cmain.c · 66 行
#include <ctype.h>#include <stdio.h> #define MAXN 50 static char grid[MAXN][MAXN + 2];static int visited[MAXN][MAXN];static int keys[26];static int r, c;static int path[MAXN * MAXN];           /* 지금까지 들어온 길(스택): 칸 번호 y * c + x */static int logCell[2 * MAXN * MAXN];    /* logCell[t] = t번 움직인 뒤의 자리 */ static int canEnter(int y, int x) {    if (y < 0 || y >= r || x < 0 || x >= c || visited[y][x]) return 0;    char ch = grid[y][x];    if (ch == '#') return 0;    if (isupper((unsigned char)ch)) return keys[ch - 'A']; /* 문은 맞는 열쇠가 있어야 들어간다 */    return 1;} int main(void) {    int q, start = 0;    scanf("%d %d", &r, &c);    for (int y = 0; y < r; y++) {        scanf("%51s", grid[y]);        for (int x = 0; x < c; x++)            if (grid[y][x] == 'S') start = y * c + x;    }    const int DY[4] = {-1, 0, 1, 0}, DX[4] = {0, 1, 0, -1}; /* 위, 오른쪽, 아래, 왼쪽 */    char picked[27];    int npicked = 0, top = 0, moves = 0, seen = 1;    visited[start / c][start % c] = 1;    path[top++] = start;    logCell[0] = start;    for (;;) {        int y = path[top - 1] / c, x = path[top - 1] % c;        /* 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다 */        int d = 0;        while (d < 4 && !canEnter(y + DY[d], x + DX[d])) d++;        if (d < 4) {            int ny = y + DY[d], nx = x + DX[d];            visited[ny][nx] = 1;            seen++;            char ch = grid[ny][nx];            if (islower((unsigned char)ch) && !keys[ch - 'a']) {                keys[ch - 'a'] = 1;                picked[npicked++] = ch;            }            path[top++] = ny * c + nx;        } else {            if (top == 1) break; /* 처음 자리에서 더 갈 곳이 없으면 멈춘다 */            top--;               /* 왔던 칸으로 한 칸 되돌아간다 */        }        logCell[++moves] = path[top - 1];    }    picked[npicked] = '\0';    printf("%d %d\n%s\n", moves, seen, npicked > 0 ? picked : "-");    scanf("%d", &q);    for (int i = 0; i < q; i++) {        int t;        scanf("%d", &t);        int at = logCell[t < moves ? t : moves]; /* 멈춘 뒤에는 처음 자리에 그대로 있다 */        printf("%d %d\n", at / c + 1, at % c + 1);    }    return 0;}
Craykonmain.cray · 104 行
첫줄 = 控制台.读数字();r = 첫줄[0];c = 첫줄[1];grid = [];start = 0; // 칸은 수 하나(y * c + x)로 다룬다循环 (y = 0; y < r; y++){    줄 = 控制台.读行().去空白();    grid.添加(줄);    x = 줄.查找位置("S");    如果 (x >= 0)    {        start = y * c + x;    }}q = 控制台.读行().去空白().转数字();queries = 控制台.读数字(); DY = [-1, 0, 1, 0]; // 위, 오른쪽, 아래, 왼쪽DX = [0, 1, 0, -1];visited = 新列表(r * c, 假);visited[start] = 真;keys = 字典();picked = ""; // 새로 얻은 열쇠(얻은 차례) 대문자인가(ch){    返回 ch >= "A" && ch <= "Z";} 소문자인가(ch){    返回 ch >= "a" && ch <= "z";} 들어갈수있나(y, x){    如果 (y < 0 || y >= r || x < 0 || x >= c)    {        返回 假;    }    如果 (visited[y * c + x])    {        返回 假;    }    ch = grid[y][x];    如果 (ch == "#")    {        返回 假;    }    如果 (대문자인가(ch))    {        返回 keys.包含(ch.小写()); // 문은 맞는 열쇠가 있어야 들어간다    }    返回 真;} path = [start]; // 지금까지 들어온 길(스택): 맨 끝이 지금 자리log = [start]; // log[t] = t번 움직인 뒤의 자리seen = 1;当 (真){    cell = path[path.数量 - 1];    y = 数学.商(cell, c);    x = cell % c;    // 자리에 설 때마다(돌아왔을 때도) 위쪽부터 다시 살핀다 — 그사이 얻은 열쇠로 열리는 문이 있을 수 있다    d = 0;    当 (d < 4 && !들어갈수있나(y + DY[d], x + DX[d]))    {        d += 1;    }    如果 (d < 4)    {        ny = y + DY[d];        nx = x + DX[d];        visited[ny * c + nx] = 真;        seen += 1;        ch = grid[ny][nx];        如果 (소문자인가(ch) && !keys.包含(ch))        {            keys[ch] = 真;            picked = picked + ch;        }        path.添加(ny * c + nx);    }    否则    {        如果 (path.数量 == 1)        {            跳出; // 처음 자리에서 더 갈 곳이 없으면 멈춘다        }        path.按位置移除(path.数量 - 1); // 왔던 칸으로 한 칸 되돌아간다    }    log.添加(path[path.数量 - 1]);} moves = log.数量 - 1;控制台.输出(moves + " " + seen);控制台.输出(picked.长度 > 0 ? picked : "-");遍历 (t 在 queries){    at = log[数学.较小值(t, moves)]; // 멈춘 뒤에는 처음 자리에 그대로 있다    控制台.输出(数学.商(at, c) + 1 + " " + (at % c + 1));}