13.6 枚舉鄰格與範圍
射線是「一直走」,鄰格是「只看旁邊」。把方向表整圈跑一遍,每個方向只看一格,就是枚舉所有鄰格;每個鄰格都要過 inside 這一關,因為邊角格的鄰格會出界。
for (int d = 0; d < 4; d++) { // 四鄰格;換成 8 就是八鄰格
int nr = r + dr[d], nc = c + dc[d];
if (!inside(nr, nc)) continue; // 出界的不是鄰格
// 處理 (nr, nc)
}
¶例題:機器人的路徑(APCS 2019 年 6 月中級)
題目:n \times m 的地圖(1 \le n, m \le 100),每格一個整數(0 \le a_{r,c} < 10^6,所有數值互不相同)。機器人:① 從全圖最小值的格子出發,標記已走過、計入總和 ② 只看上下左右四個鄰格,排除出界與已走過的 ③ 有候選格就走到其中數值最小的一格,標記、加總,回到 ② ④ 沒有候選格就停止。輸出走過的所有格子數值總和。
四鄰格取最小、走過就標記——「找候選再挑最好」的固定寫法是用一個 bestValue 從最大值往下壓,最後看有沒有找到:
#include <bits/stdc++.h>
using namespace std;
// 四鄰格的方向表:上、下、左、右(只跑一圈、不轉向,所以不必順時針排)
const int dr[4] = {-1, 1, 0, 0};
const int dc[4] = {0, 0, -1, 1};
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
int row = 0, col = 0; // 起點=全圖最小值的位置
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
cin >> grid[r][c];
if (grid[r][c] < grid[row][col]) {
row = r;
col = c;
}
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
visited[row][col] = true;
long long answer = grid[row][col];
while (true) {
int nextRow = -1, nextCol = -1;
int bestValue = INT_MAX;
for (int d = 0; d < 4; d++) { // 枚舉四個鄰格
int nr = row + dr[d];
int nc = col + dc[d];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue; // 出界
if (visited[nr][nc]) continue; // 走過
if (grid[nr][nc] < bestValue) {
bestValue = grid[nr][nc];
nextRow = nr;
nextCol = nc;
}
}
if (nextRow == -1) break; // 沒有候選格:停止
row = nextRow;
col = nextCol;
visited[row][col] = true;
answer += grid[row][col];
}
cout << answer << '\n';
return 0;
}
總和用 long long:最多 10^4 格、每格不到 10^6,加起來可到 10^{10},int 裝不下(上冊 2.9)。這一題的方向表沒有順時針排,因為它只需要「跑一圈」、不需要「右轉」——表要怎麼排,看題目要不要轉向。
¶範圍枚舉:曼哈頓距離
有時候要看的不是四個鄰格,而是「距離不超過 x 的所有格子」。兩格 (i, j) 與 (s, t) 的曼哈頓距離是 |i - s| + |j - t|——只走上下左右要走幾步。要枚舉這個菱形範圍,最直接的寫法是把包住它的正方形跑一遍、每一格檢查距離,出界的跳過:
for (int di = -x; di <= x; di++)
for (int dj = -x; dj <= x; dj++) {
if (abs(di) + abs(dj) > x) continue; // 不在菱形裡
int r = i + di, c = j + dj;
if (!inside(r, c)) continue; // 出界的格子不計算
// 處理 (r, c)
}
特殊位置(APCS 2023 年 6 月中級):n \times m 的正整數陣列(1 \le n, m \le 50,每格小於 10)。對元素 A[i][j] = x,若與它距離不超過 x 的所有元素(含自己、出界不算)總和除以 10 的餘數等於 x 除以 10 的餘數,(i, j) 就是特殊位置。輸出特殊位置的個數,再依列、行由小到大列出。
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<vector<int>> a;
bool inside(int r, int c) {
return 0 <= r && r < n && 0 <= c && c < m;
}
int main() {
cin >> n >> m;
a.assign(n, vector<int>(m));
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
vector<pair<int, int>> answer;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int x = a[i][j];
int total = 0;
for (int di = -x; di <= x; di++) { // 枚舉「範圍」裡的每一格
for (int dj = -x; dj <= x; dj++) {
if (abs(di) + abs(dj) > x) continue; // 曼哈頓距離超過 x:不在範圍內
int r = i + di, c = j + dj;
if (!inside(r, c)) continue; // 出界的格子不計算
total += a[r][c];
}
}
if (total % 10 == x % 10) answer.push_back({i, j});
}
}
cout << answer.size() << '\n';
for (auto [r, c] : answer) cout << r << ' ' << c << '\n';
return 0;
}
答案「依列、行由小到大」正好就是雙層迴圈的順序,照著 push_back 就已經排好,不用另外排序。
同一招再做一題就熟了:電子畫布(APCS 2024 年 6 月中級)——H \times W 的畫布(1 \le H, W \le 20)起初全 0,N 次操作(N \le 100)各給 (r, c, t, x),把所有與 (r, c) 曼哈頓距離不超過 t 的格子加上 x,最後輸出畫布。畫布很小,每次操作乾脆整張掃一遍、逐格判斷距離:
for (int op = 0; op < N; op++) {
int r, c, t, x;
cin >> r >> c >> t >> x;
for (int i = 0; i < H; i++) // 畫布很小,整張掃過去
for (int j = 0; j < W; j++)
if (abs(i - r) + abs(j - c) <= t) // 曼哈頓距離不超過 t 的格子
canvas[i][j] += x;
}
「整張掃過去、用條件過濾」和「只枚舉範圍、用 inside 過濾」是同一件事的兩種寫法:地圖小就掃全圖(連 inside 都省了),地圖大、範圍小就只枚舉範圍。先估一下運算量(10.1)再選。