語法書 / AA 競程語法書 下冊 / 第十三單元 / 一步一步走:模擬移動與轉向

13.4 一步一步走:模擬移動與轉向

有了 inside 和方向表,「一個角色在地圖上聽規則行動」就只是把規則排好、一條一條翻譯。角色的狀態是三個變數:位置 rc 與面向 dir;每一步固定的節奏是先算下一格、檢查、再移動——不要先 r += dr[dir] 再檢查,撞牆時人已經站在牆裡了。

int nr = r + dr[dir], nc = c + dc[dir];   // 先算「下一格」,不動 r、c
if (inside(nr, nc) && grid[nr][nc] != '#') {   // 檢查
    r = nr;  c = nc;                            // 通過才踏上去
}

例題:蒐集寶石(APCS 2024 年 10 月中級)

題目就是「想像一下」那五條規則。1 \le M \le 1002 \le N \le 1002 \le K \le 20,寶石數介於 0 \sim K-1,起點不是牆且四周不會全無路可走,保證機器人一定會停。輸出的是拿到的寶石數,不是得分。

規則已經幫你排好順序了,程式的主迴圈就照 1~5 寫,一條規則一段註解:

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

const int MAX_SIZE = 100;

// 方向照「右轉一次」的順序排:東 → 南 → 西 → 北
// 右轉就是 dir 加 1(繞一圈回到東)
const int dr[4] = {0, 1, 0, -1};
const int dc[4] = {1, 0, -1, 0};

int main() {
    int M, N, K, r, c;
    cin >> M >> N >> K >> r >> c;

    int grid[MAX_SIZE][MAX_SIZE];
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            cin >> grid[i][j];   // -1 是牆,其餘是寶石數
        }
    }

    int dir = 0;     // 0 = 東、1 = 南、2 = 西、3 = 北
    int score = 0;   // 總得分
    int gems = 0;    // 拿到的寶石數(答案)

    while (grid[r][c] != 0) {      // 規則 1:這一格沒寶石就停止
        score += grid[r][c];       // 規則 2:得分加上這一格目前的寶石數,
        grid[r][c] -= 1;           //         並拿走一顆
        gems += 1;

        if (score % K == 0) {      // 規則 3:得分是 K 的倍數就右轉
            dir = (dir + 1) % 4;
        }

        while (true) {             // 規則 4:前方出界或是牆,就一直右轉
            int nr = r + dr[dir];
            int nc = c + dc[dir];
            if (nr >= 0 && nr < M && nc >= 0 && nc < N && grid[nr][nc] != -1) {
                break;             // 前方走得進去,右轉到此為止
            }
            dir = (dir + 1) % 4;
        }

        r += dr[dir];              // 規則 5:走進前方的格子
        c += dc[dir];
    }

    cout << gems << endl;
    return 0;
}

幾個值得停下來看的地方:

  • 方向表從「東」開始排(題目說初始面向東),但一樣順時針,右轉一樣 + 1
  • 規則 4 的 while (true) 一定會停:機器人是從某個方向走進來的,往回走的那個方向一定通。
  • 會不會跑不完?每一輪都拿走一顆寶石,寶石總數有限(不超過 100 \times 100 \times 19),所以迴圈次數有上界。