# APCS實作題2016年3月第2題:矩陣轉換
> 第1版:2023年2月10日
> 第2版:2023年6月9日,加上 C++ 程式碼
> 作者:王一哲
> 題目來源:[105年3月5日實作題第2題](https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2020/10/APCS-%E5%AF%A6%E4%BD%9C%E9%A1%8C-2016.03.05.pdf)
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=b266)
<br />
## 題目
### 問題描述
矩陣是將一群元素整齊的排列成一個矩形,在矩陣中的橫排稱為列 (row),直排稱為行 (column),其中以 $X_{ij}$ 來表示矩陣 $X$ 中的第 $i$ 列第 $j$ 行的元素。如圖一中,$X_{32} = 6$。
我們可以對矩陣定義兩種操作如下:
- 翻轉:即第一列與最後一列交換、第二列與倒數第二列交換、……依此類推。
- 旋轉:將矩陣以順時針方向轉 90 度。
例如:矩陣 $X$ 翻轉後可得到 $Y$,將矩陣 $Y$ 再旋轉後可得到 $Z$。
<img height="60%" width="60%" src="https://i.imgur.com/Yt1SpYc.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
一個矩陣 $A$ 可以經過一連串的旋轉與翻轉操作後,轉換成新矩陣 $B$。如圖二中,$A$
經過翻轉與兩次旋轉後,可以得到 $B$。給定矩陣 $B$ 和一連串的操作,請算出原始的
矩陣 $A$。
<img height="80%" width="80%" src="https://i.imgur.com/b9lKZEu.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
### 輸入格式
第一行有三個介於 1 與 10 之間的正整數 $R$, $C$, $M$。接下來有 $R$ 行(line)是矩陣 $B$ 的內容,每一行(line)都包含 $C$ 個正整數,其中的第 $i$ 行第 $j$ 個數字代表矩陣 $B_{ij}$ 的值。在矩陣內容後的一行有 $M$ 個整數,表示對矩陣 $A$ 進行的操作。第 $k$ 個整數 $m_k$ 代表第 $k$ 個操作,如果 $m_k = 0$ 則代表旋轉,$m_k = 1$ 代表翻轉。同一行的數字之間都是以一個空白間格,且矩陣內容為 0~9 的整數。
<br />
### 輸出格式
輸出包含兩個部分。第一個部分有一行,包含兩個正整數 $R'$ 和 $C'$ ,以一個空白隔開,分別代表矩陣 $A$ 的列數和行數。接下來有 $R'$ 行,每一行都包含 $C'$ 個正整數,且每一行的整數之間以一個空白隔開,其中第 $i$ 行的第 $j$ 個數字代表矩陣 $A_{ij}$ 的值。每一行的最後一個數字後並無空白。
<br />
### 範例一:輸入
```
3 2 3
1 1
3 1
1 2
1 0 0
```
### 範例一:正確輸出
```
3 2
1 1
1 3
2 1
```
(說明)如圖二所示
<br />
### 範例二:輸入
```
3 2 2
3 3
2 1
1 2
0 1
```
### 範例二:正確輸出
```
2 3
2 1 3
1 2 3
```
(說明)
<img height="40%" width="40%" src="https://i.imgur.com/0Wt3m1X.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
### 評分說明
輸入包含若干筆測資,每一筆測資的執行時間限制(time limit)均為 2 秒,依
正確通過測資筆數給分。其中:
第 1 子題組 30 分,其每個操作都是翻轉。
第 2 子題組 70 分,操作有翻轉也有旋轉。
<br /><br />
## 分析
若矩陣A為維度為 $M \times N$(M rows, N columns)
$$
A =
\begin{bmatrix}
A_{00} & A_{01} & A_{02} & A_{03} \\
A_{10} & A_{11} & A_{12} & A_{13} \\
A_{20} & A_{21} & A_{22} & A_{23}
\end{bmatrix}
$$
將矩陣A順時鐘方向旋轉後儲存為矩陣B
$$
B =
\begin{bmatrix}
B_{00} & B_{01} & B_{02} \\
B_{10} & B_{11} & B_{12} \\
B_{20} & B_{21} & B_{22} \\
B_{30} & B_{31} & B_{32}
\end{bmatrix}
{} =
\begin{bmatrix}
A_{20} & A_{10} & A_{00} \\
A_{21} & A_{11} & A_{01} \\
A_{22} & A_{12} & A_{02} \\
A_{23} & A_{13} & A_{03}
\end{bmatrix}
$$
因此矩陣B與旋轉前的矩陣A索引值對應的關係為 $(i, j) = (M - j -1, i)$。
<br />
將矩陣A上下翻轉後儲存為矩陣B
$$
B =
\begin{bmatrix}
B_{00} & B_{01} & B_{02} & B_{03}\\
B_{10} & B_{11} & B_{12} & B_{13}\\
B_{20} & B_{21} & B_{22} & B_{23}
\end{bmatrix}
{} =
\begin{bmatrix}
A_{20} & A_{21} & A_{22} & A_{23}\\
A_{10} & A_{11} & A_{12} & A_{13}\\
A_{00} & A_{01} & A_{02} & A_{03}
\end{bmatrix}
$$
因此矩陣B與上下翻轉前的矩陣A索引值對應的關係為 $(i, j) = (M - i - 1, j)$。
最後要特別注意一點,**本題是要從操作後的矩陣逆推回原來的矩陣,所有的操作步驟順序相反,矩陣向逆時鐘方向轉回去**。
<br /><br />
## Python 程式碼
### 原版的題目
```python=
"""
APCS 105年3月實作題2:矩陣轉換
日期:2023/2/10
作者:王一哲
題目來源:https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2020/10/APCS-%E5%AF%A6%E4%BD%9C%E9%A1%8C-2016.03.05.pdf
"""
# 逆時鐘方向轉回來
def rotate(matrix):
rows = len(matrix[0])
columns = len(matrix)
matrix2 = [[None]*columns for _ in range(rows)]
for i in range(columns):
for j in range(rows):
matrix2[rows - j - 1][i] = matrix[i][j]
return matrix2
# 上下翻轉
def updown(matrix):
rows = len(matrix)
columns = len(matrix[0])
matrix2 = [[None]*columns for _ in range(rows)]
for i in range(rows):
for j in range(columns):
matrix2[i][j] = matrix[rows - i - 1][j]
return matrix2
# 印出矩陣
def printMatrix(matrix):
rows = len(matrix)
columns = len(matrix[0])
for i in range(rows):
for j in range(columns):
if j == columns - 1: print("{:d}".format(matrix[i][j]))
else: print("{:d}".format(matrix[i][j]), end=" ")
# 輸入R、C、M,輸入矩陣列數R、每列元素數量C、操作次數M,並用半型空格分隔
R, C, M = map(int, input().split())
# 輸入陣列B資料,輸入矩陣每列的內容,並用半型空格分隔
B = []
for _ in range(R):
B.append(list(map(int, input().split())))
# 輸入操作 operates,輸入操作,0代表順時鐘方向旋轉,1代表上下翻轉,並用半型空格分隔,操作順序要反過來
operates = list(map(int, input().split()))
operates.reverse()
# 反過來操作,得到原來的矩陣
A = B.copy()
for operate in operates:
if operate == 0:
A = rotate(A)
elif operate == 1:
A = updown(A)
# 輸出結果
print("{:d} {:d}".format(len(A), len(A[0])))
printMatrix(A)
```
<br />
1. 第8 ~ 15行:自訂函式 rotate,將矩陣逆時鐘方向轉回去。假設輸入的矩陣(旋轉後的矩陣B)維度為 $N \times M$,先産生一個所有元素皆為 None、維度為 $M \times N$ 的矩陣,用來接收對應索引值的元素,最後輸出為原來的矩陣(旋轉前的矩陣A)。
2. 第18 ~ 25行:自訂函式 updown,將矩陣上下翻轉。由於翻轉前後的矩陣維度相同,若輸入的矩陣(翻轉後的矩陣B)維度為 $M \times N$,先産生一個所有元素皆為 None、維度為 $M \times N$ 的矩陣,用來接收對應索引值的元素,最後輸出為原來的矩陣(翻轉前的矩陣A)。
3. 第28 ~ 34行:自訂函式 printMatrix,依序印出各個元素,相鄰兩個元素之間用半型空格分開,特別注意**每一列的元素之後沒有空格、直接換行**。其中第33、34行也可以寫成這樣
```python
print(matrix[i][j], end="\n" if j == columns-1 else " ")
```
5. 第37行:由鍵盤輸入資料,分別儲存到變數R、C、M。
6. 第40 ~ 42行:由鍵盤輸入R行的資料,用 list 格式儲存到操作後的矩陣B。
7. 第45 ~ 46行:由鍵盤輸入操作步驟,用 list 格式儲存到 operates,但是題目要反序操作變回原來的矩陣,要再用 reverse() 將 operates 中的資料順序顛倒。
8. 第49 ~ 54行:先將矩陣B的資料複製到矩陣A,由 operates 依序讀取操作步驟,將矩陣A變為操作前的樣子。
9. 第57行:印出原來的矩陣維度。
10. 第58行:用自訂函式 printMatrix 印出矩陣的元素。
11. ZeroJudge 測試結果:花費時間約為 18 ms,使用記憶體 3.4 MB,通過測試。
<br /><br />
## 加強版:測資有多個矩陣
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=b965)
<br />
這是上一題的加強版,測資中包含多個矩陣,以下是我的寫法。
<br />
```python=
"""
APCS 105年3月實作題2:矩陣轉換,每筆測試資料中有多筆矩陣及操作的資料
日期:2023/2/10
作者:王一哲
題目來源:https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2020/10/APCS-%E5%AF%A6%E4%BD%9C%E9%A1%8C-2016.03.05.pdf
"""
import sys
# 逆時鐘方向轉回來
def rotate(matrix):
rows = len(matrix[0])
columns = len(matrix)
matrix2 = [[None]*columns for _ in range(rows)]
for i in range(columns):
for j in range(rows):
matrix2[rows - j - 1][i] = matrix[i][j]
return matrix2
# 上下翻轉矩陣
def updown(matrix):
rows = len(matrix)
columns = len(matrix[0])
matrix2 = [[None]*columns for _ in range(rows)]
for i in range(rows):
for j in range(columns):
matrix2[i][j] = matrix[rows - i - 1][j]
return matrix2
# 印出矩陣,每一列最後一個數字之後沒有空格
def printMatrix(matrix):
rows = len(matrix)
columns = len(matrix[0])
for i in range(rows):
for j in range(columns):
if j < columns - 1: print("{:d}".format(matrix[i][j]), end=" ")
else: print("{:d}".format(matrix[i][j]))
def action(data):
# 輸入R、C、M,輸入矩陣列數R、每列元素數量C、操作次數M,並用半型空格分隔
R, C, M = data[0][0], data[0][1], data[0][2]
# 輸入陣列B資料,輸入矩陣每列的內容,並用半型空格分隔
B = []
for i in range(1, R+1):
B.append(data[i])
# 輸入操作 operates,輸入操作,0代表順時鐘方向旋轉,1代表上下翻轉,並用半型空格分隔
operates = data[R+1]
operates.reverse()
# 操作矩陣
A = B.copy()
for operate in operates:
if operate == 0:
A = rotate(A)
elif operate == 1:
A = updown(A)
# 輸出結果
print("{:d} {:d}".format(len(A), len(A[0])))
printMatrix(A)
# 輸入資料
data, matrices = [], []
while True:
try:
line = list(map(int, sys.stdin.readline().split()))
if not line: break
data.append(line)
except EOFError:
pass
# 分割每個矩陣的資料
i = 0
while i < len(data):
n = data[i][0]
matrices.append(data[i:i+n+2])
i = i+n+2
# 操作矩陣
for matrix in matrices: action(matrix)
```
<br />
1. 自訂函式 rotate、updown、printMatrix 都與上一題的程式碼相同,直接沿用。
2. 第37 ~ 56行:自訂函式 action,包含讀取輸矩陣列數R、每列元素數量C、操作次數M、矩陣內容、操作步驟、反操作、印出結果。
3. 第59 ~ 66行:由標準輸入讀取資料,原來的寫法是用 input,但是在 ZeroJudge 上測試時會發生錯誤,因此在程式一開始 **import sys**,第62行後半段改寫成 **sys.stdin.readlin().split()**,並且將讀取資料的部分包到 try 裡面,發生 **EOFError** 時執行 pass,程式才不會卡住。
4. 第69 ~ 73行:分割讀進來的資料 data,每個矩陣的資料,包含R、C、M、元素、操作,存成一個一維串列,再包到二維串列 matrices 之中。
5. 第76行:由 matrices依序讀取每個矩陣的資料,引用自訂函式 action,變回原來的矩陣並印出維度、元素。
6. ZeroJudge 測試結果:第一筆測資花費時間約為 36 ms,使用記憶體 4.2 MB;第二筆測資花費時間約為 0.2 s,使用記憶體 5.9 MB,通過測試。
<br /><br />
## C++ 程式碼
### 原版的題目
```cpp=
#include <iostream>
using namespace std;
void swap(int&, int&); // 交換數值
void rotate(); // 逆時鐘方向轉回來
void updown(); // 上下翻轉
void printMatrix(); // 印出矩陣
int R, C, M;
int **matrix;
int main(int argc, char* argv[]) {
// 輸入R、C、M,輸入矩陣列數R、每列元素數量C、操作次數M,並用半型空格分隔
cin >> R >> C >> M;
// 輸入陣列B資料,輸入矩陣每列的內容,並用半型空格分隔
matrix = new int*[R];
for(int i=0; i<R; i++) {
matrix[i] = new int[C];
for(int j=0; j<C; j++) {
cin >> matrix[i][j];
}
}
// 輸入操作 operates,輸入操作,0代表順時鐘方向旋轉,1代表上下翻轉,並用半型空格分隔,操作順序要反過來
int operates[M];
for(int i=0; i<M; i++) {
cin >> operates[M-i-1];
}
// 還原矩陣
for(int i=0; i<M; i++) {
if (operates[i] == 0) rotate();
else if (operates[i] == 1) updown();
}
// 輸出題目要求的內容
cout << R << " " << C << endl;
printMatrix();
return 0;
}
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
void rotate() {
int** newMatrix = new int*[C];
for(int i=0; i<C; i++) {
newMatrix[i] = new int[R];
for(int j=0; j<R; j++) {
newMatrix[i][j] = matrix[j][C-i-1];
}
}
matrix = newMatrix;
swap(R, C);
}
void updown() {
for(int i=0; i<R/2; i++) {
for(int j=0; j<C; j++) {
swap(matrix[i][j], matrix[R-i-1][j]);
}
}
}
void printMatrix() {
for(int i=0; i<R; i++) {
for(int j=0; j<C; j++) {
if (j < C-1)
cout << matrix[i][j] << " ";
else
cout << matrix[i][j] << endl;
}
}
}
```
<br />
1. 第4、39 ~ 43行:自訂函式 swap,用來交換數值。
2. 第5、45 ~ 55行:自訂函式 rotate,將矩陣逆時鐘方向轉回去。輸入的資料是為旋轉後的矩陣 mateix,維度為 $N \times M$;函式中先産生一個維度為 $M \times N$ 的矩陣 newMatrix,用來接收旋轉前的矩陣資料;再將 newMatrix 的資料傳給 matrix。
3. 第6、57 ~ 63行:自訂函式 updown,利用自訂函式 swap 將矩陣上下翻轉,。
4. 第7、65 ~ 74行:自訂函式 printMatrix,依序印出各個元素,相鄰兩個元素之間用半型空格分開,特別注意**每一列的元素之後沒有空格、直接換行**。
5. 第9行:定義全域變數 R、C、M。
6. 第10行:定義全域變數 matrix,為二維陣列。
7. 第14行:由鍵盤輸入資料,分別儲存到變數R、C、M。
8. 第16 ~ 22行:由鍵盤輸入資料操作後的矩陣 matrix。
10. 第24 ~ 27行:由鍵盤輸入操作步驟,儲存到一維陣列 operates,但是題目要反序操作變回原來的矩陣,operates 中的資料與輸入資料順序相反。
11. 第29 ~ 32行:由 operates 依序讀取操作步驟,將矩陣 matrix 變為操作前的樣子。
12. 第34行:印出原來的矩陣維度。
13. 第35行:用自訂函式 printMatrix 印出矩陣的元素。
14. ZeroJudge 測試結果:花費時間約為 2 ms,使用記憶體 324 kB,通過測試。
<br /><br />
---
###### tags:`APCS`、`Python`