# APCS實作題2017年3月第3題:數字龍捲風
> 第1版:2023年2月17日
> 第2版:2023年6月13日,加上 C++ 程式碼
> 第3版:2023年10月19日,將 Python 程式碼改成較精簡的版本
> 作者:王一哲
> 題目來源:[106年3月4日實作題第3題](https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2018/12/1060304APCSImplementation.pdf)
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=c292)
<br />
## 題目
### 問題描述
給定一個 $N \times N$ 的二維陣列,其中 $N$ 是奇數,我們可以從正中間的位置開始,以順時針旋轉的方式走訪每個陣列元素恰好一次。對於給定的陣列內容與起始方向,請輸出走訪順序之內容。下面的例子顯示了 $N=5$ 且第一步往左的走訪順序:
<img height="50%" width="50%" src="https://i.imgur.com/CPXwwBc.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
依此順序輸出陣列內容則可以得到「9123857324243421496834621」。
類似地,如果是第一步向上,則走訪順序如下:
<img height="50%" width="50%" src="https://i.imgur.com/5DghH4H.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
依此順序輸出陣列內容則可以得到「9385732124214968346214243」。
<br />
### 輸入格式
輸入第一行是整數 $N$,$N$ 為奇數且不小於 3。第二行是一個 0 ~ 3 的整數代表起始方向,其中 0 代表左、1 代表上、2 代表右、3 代表下。第三行開始 $N$ 行是陣列內容,順序是由上而下,由左至右,陣列的內容為 0 ~ 9 的整數,同一行數字中間以一個空白間隔。
<br />
### 輸出格式
請輸出走訪順序的陣列內容,該答案會是一連串的數字,數字之間**不要輸出空白**,結尾有換行符號。
<br />
### 範例一:輸入
```
5
0
3 4 2 1 4
4 2 3 8 9
2 1 9 5 6
4 2 3 7 8
1 2 6 4 3
```
### 範例一:正確輸出
```
9123857324243421496834621
```
<br />
### 範例二:輸入
```
3
1
4 1 2
3 0 5
6 7 8
```
### 範例二:正確輸出
```
012587634
```
<br />
### 評分說明
輸入包含若干筆測試資料,每一筆測試資料的執行時間限制(time limit)均為 1 秒,依正確通過測資筆數給分。其中:
- 第 1 子題組 20 分,$3 \leq N \leq 5$,且起始方向均為向左。
- 第 2 子題組 80 分,$3 \leq N \leq 49$,起始方向無限定。
<br />
### 提示
本題有多種處理方式,其中之一是觀察每次轉向與走的步數。例如,起始方向是向左時,前幾步的走法是:左 1、上 1、右 2、下 2、左 3、上 3、……一直到出界為止。
<br />
## Python 程式碼
```python=
N = int(input()) # 讀取矩陣的列數、欄數 N
d = int(input()) # 讀取起始方向
data = [] # 儲存矩陣的二維串列
for _ in range(N): # 讀取矩陣內容
data.append(list(map(int, input().split())))
directions = ((0, -1), (-1, 0), (0, 1), (1, 0))
direction = directions[d]
def changeDir(dir1):
if dir1 == (0, -1): return directions[1]
elif dir1 == (-1, 0): return directions[2]
elif dir1 == (0, 1): return directions[3]
else: return directions[0]
x = y = int((N-1)/2)
print(data[x][y], end="")
count = 1
step = 1
while count < N*N:
for _ in range(step):
count += 1
if count <= N*N:
x += direction[0]
y += direction[1]
if count == N*N: print(data[x][y])
else: print(data[x][y], end="")
else: break
direction = changeDir(direction)
for _ in range(step):
count += 1
if count <= N*N:
x += direction[0]
y += direction[1]
print(data[x][y], end="")
else: break
direction = changeDir(direction)
step += 1
```
<br />
1. 第1 ~ 5行:從標準輸入讀取資料,矩陣的維度儲存到N,起始方向儲存到d,矩陣內容儲存到 data。
2. 第7、8行:用 tuple 格式的 directons 儲存4個方向,再依照d的值取出對應的方向儲存到 direction。
3. 第10 ~ 14行:自訂函式 changeDir,由原來的方向決定改變後的方向,方向依序為**左、上、右、下**。
4. 第16、17行:計算正中央的元素位置索引值,再印出此元素值。
5. 第20 ~ 38行:在印出第 N<sup>2</sup> 個元素之前,繼續執行 while 迴圈。由於沿著某個方向移動的步數 step 依序為1、1、2、2、3、3、……,在 while 迴圈之中有兩個 for 迴圈,當一個 for 迴圈執行完畢時用自訂函式 changeDir 改變前進方向,當兩個 for 迴圈都執行完畢時將移動步數 step 加1。最後一個印出的元素一定是在第1個 for 迴圈裡面,所以第33行印出最後一個元素時要換行。
6. 於 ZeroJudge 測試結果,第12筆測資費時最久,約 24 ms,使用記憶體 3.8 MB,通過測試。
<br /><br />
## C++ 程式碼
```cpp=
#include <iostream>
using namespace std;
int changeDir(int); // 改變方向用的自訂函式
int dirs[][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; // 儲存方向的二維陣列
int main(int argc, char* argv[]) {
// 由標準輸入讀取矩陣維度 N、起始方向 D、矩陣資料 data
int N, D;
cin >> N >> D;
int data[N][N];
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++) {
cin >> data[i][j];
}
}
// 起始方向
int* dir = dirs[D];
// 計算矩陣中央的位置並印出元素
int x = (N-1)/2;
int y = x;
cout << data[x][y];
// 已印出元素的次數 count、移動格數 step
int count = 1, step = 1;
// 順時鐘方向旋轉印出元素值
while(count < N*N) {
for(int i=0; i<step; i++) { // 往一個方向移動 step 格並印出元素
count++;
if (count <= N*N) {
x += dir[0];
y += dir[1];
if (count == N*N) { // 印出最後一個元素時加上換行
cout << data[x][y] << endl;
} else { // 不是最後一個元素
cout << data[x][y];
}
} else {
break; // 超出邊界終止迴圈
}
}
D = changeDir(D); // 改變方向
dir = dirs[D];
for(int i=0; i<step; i++) { // 往一個方向移動 step 格並印出元素
count++;
if (count <= N*N) { // 不會遇到最後一個元素
x += dir[0];
y += dir[1];
cout << data[x][y];
}
}
D = changeDir(D); // 改變方向
dir = dirs[D];
step++; // 增加移動格數
}
return 0;
}
// 改變方向用的自訂函式
int changeDir(int d) {
if (d == 0) return 1;
else if (d == 1) return 2;
else if (d == 2) return 3;
else return 0;
}
```
<br />
1. 第4、63 ~ 68行:改變方向用的自訂函式 changeDir,由原來的方向決定改變後的方向,方向依序為**左、上、右、下**。。
2. 第5行:用2維陣列 dirs 儲存4個方向。
3. 第9 ~ 16行:由標準輸入讀取矩陣維度 N、起始方向 D、矩陣資料 data。
4. 第19行:定義方向 dir。
5. 第22 ~ 24行:計算矩陣中央的位置並印出元素。
7. 第30 ~ 58行:在印出第 N<sup>2</sup> 個元素之前,繼續執行 while 迴圈。由於沿著某個方向移動的步數 step 依序為1、1、2、2、3、3、……,在 while 迴圈之中有兩個 for 迴圈,當一個 for 迴圈執行完畢時用自訂函式 changeDir 及 dir = dirs[D] 改變前進方向,當兩個 for 迴圈都執行完畢時將移動步數 step 加1。最後一個印出的元素一定是在第1個 for 迴圈裡面,所以第37行印出最後一個元素時要換行。
8. 於 ZeroJudge 測試結果,花費時間為 3 ms,使用記憶體 348 kB,通過測試。
9. 由於讀取元素的方向一定是順時鐘方向,表示方向的變數 D 數值變化時依序為 0、1、2、3,可以不需要定義 changeDir,第45、55行改成
```cpp
D = (D+1)%4;
```
於 ZeroJudge 測試結果,花費時間為 3 ms,使用記憶體 332 kB,通過測試。
<br /><br />
---
###### tags:`APCS`、`C++`、`Python`