# APCS實作題2024年10月第2題:蒐集寶石
> 日期:2024年10月28日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=o712)
<br />
## 題目
### 問題描述
有一個 $M \times N$ 的地圖,每一格的數字紀錄著寶石的數量,如果數字是 $-1$ 代表牆壁。
有一位機器人一開始位於 $(r, c)$ 的位置上且方向朝右邊,他遵循著以下規則行走。
1. 若機器人位於的格字內寶石數量為 $0$,則機器人程式終止。
2. 機器人維護著一個分數 score,將 score 加上當前格的寶石數量,並且撿起一顆寶石。
3. 若 score 是 $k$ 的倍數,則向右轉 90 度。
4. 若機器人面向的格子是牆壁或是超出邊界,則繼續向右轉 90 度直到面向的格子非牆壁或非超出邊界,並回到第 1 步。
<img style="display: block; margin-left: auto; margin-right: auto" height="70%" width="70%" src="https://zerojudge.tw/ShowImage?id=4320">
<br />
例如機器人一開始在座標 $(2, 1)$ 且 $k=2$,向右走兩步之後分數為 $3+2+3=8$,由於 $8$ 是 $2$ 的倍數所以向右轉 90 度。接下來往下走一步分數變為 $11$,需要向右轉 2 次 90 度才不會面向牆壁或是邊界外的格子。
接下來向前走一步走到座標 $(2, 3)$,由於先前已經拿走一顆寶石,該位置的寶石數量變為 $2$,因此分數變為 $13$,再繼續往上走兩步到 $(0, 3)$ 處分數為 $16$,由於 $16$ 為 $2$ 的倍數所以向右轉 90 度。
向前走一格到 $(0, 4)$ 後需要向右轉兩次 90 度,回到 $(0, 3)$ 後由於寶石數量為 $0$,機器人停止。過程中機器人總共撿了 $8$ 顆寶石。
<br />
### 輸入格式
第一行有 $5$ 個正整數 $M, N, k, r, c$
$$
1 \leq M \leq 100 ~~~~~ 2 \leq N \leq 100 ~~~~~ 1 \leq k \leq 20 ~~~~~ 0 \leq r < M ~~~~~ 0 \leq c < N
$$
保證機器人初始位置不是牆壁。接下來有 $M$ 行,每一行有 $N$ 的數字,代表地圖的資訊。
子題配分
- 60%:$M = 1$
- 40%:無限制
<br />
### 輸出格式
輸出機器人會蒐集幾個寶石。
<br />
### 範例輸入1
```
1 7 3 0 4
1 -1 2 1 2 1 0
```
### 範例輸出1
```
5
```
### 範例輸入2
```
4 5 4 2 1
2 0 1 1 1
2 -1 0 2 -1
0 3 2 3 0
1 1 -1 3 1
```
### 範例輸出2
```
8
```
<br />
## Python 程式碼
費時最久約 0.1 s,使用記憶體最多 3.5 MB,通過測試。
```python=
M, N, k, r, c = map(int, input().split()) # 地圖列數 M、欄數 N,寶石倍數 k,起點位置 (r, c)
grid = [list(map(int, input().split())) for _ in range(M)] # 讀取地圖資料
dirs = ((0, 1), (1, 0), (0, -1), (-1, 0)) # 移動方向依序為右、下、左、上
d = 0 # 移動方向
score = 0 # 分數
pick = 0 # 目前撿起的寶石數量
while grid[r][c] != 0: # 如果此格寶石數量不是0繼續執行
score += grid[r][c] # 分數加上此格的寶石數量
pick += 1 # 檢1個寶石
grid[r][c] -= 1 # 此格寶石數量減1
move = False # 是否能走到下一格
ts = 0 # 因為分數而轉向的次數,最多只會有1次
while not move: # 如果不能走到下一格繼續執行
nr, nc = r + dirs[d][0], c + dirs[d][1] # 下一格為 (nr, nc)
if score%k == 0 and ts == 0: # 如果 score 是 k 的倍數而且還沒有因為分數轉向
d = (d+1)%4; ts += 1 # 轉向,ts 加 1
# 向右且 nc>= N 出界,向下且 nr>=M 出界,向左且 nc<0 出界,向上且 nr<0 出界
elif (d == 0 and nc >= N) or (d == 1 and nr >= M) or (d == 2 and nc < 0) or (d == 3 and nr < 0):
d = (d+1)%4 # 轉向
elif grid[nr][nc] == -1: # 遇到牆壁
d = (d+1)%4 # 轉向
else: # 可以走到下一格
move = True # 重設為 True
r, c = nr, nc # 更新 (r, c)
# end of while loop
print(pick) # 印出撿起的寶石數量
```
<br /><br />
## C++ 程式碼
費時最久約 4 ms,使用記憶體最多 380 kB,通過測試。
```cpp=
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int M, N, k, r, c;
cin >> M >> N >> k >> r >> c; // 地圖列數 M、欄數 N,寶石倍數 k,起點位置 (r, c)
int grid[M][N];
for(int i=0; i<M; i++)
for(int j=0; j<N; j++)
cin >> grid[i][j]; // 讀取地圖資料
int dirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 移動方向依序為右、下、左、上
int d = 0, score = 0, pick = 0; // 移動方向,分數,目前撿起的寶石數量
while(grid[r][c] != 0) { // 如果此格寶石數量不是0繼續執行
score += grid[r][c]; // 分數加上此格的寶石數量
pick++; // 檢1個寶石
grid[r][c]--; // 此格寶石數量減1
bool move = false; // 是否能走到下一格
int ts = 0; // 因為分數而轉向的次數,最多只會有1次
while(!move) { // 如果不能走到下一格繼續執行
int nr = r + dirs[d][0], nc = c + dirs[d][1]; // 下一格為 (nr, nc)
if (score%k == 0 && ts == 0) { // 如果 score 是 k 的倍數而且還沒有因為分數轉向
d = (d+1)%4; ts++; // 轉向,ts 加 1
// 向右且 nc>= N 出界,向下且 nr>=M 出界,向左且 nc<0 出界,向上且 nr<0 出界
} else if ((d == 0 && nc >= N) || (d == 1 && nr >= M) || (d == 2 && nc < 0) || (d == 3 && nr < 0)) {
d = (d+1)%4; // 轉向
} else if (grid[nr][nc] == -1) { // 遇到牆壁
d = (d+1)%4; // 轉向
} else { // 可以走到下一格
move = true; // 重設為 True
r = nr; c = nc; // 更新 (r, c)
}
}
}
cout << pick << "\n"; // 印出撿起的寶石數量
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`