# 演算法課程題解 - 二維陣列
# TOJ 114
## 題目
https://toj.tfcis.org/oj/pro/114/
給一個 $n \times m$ 的陣列,如果其中有連續三個相同的連續元素在同一行或列上,輸出 Yes ,否則輸出 No
## 解法 By Koios1143
### 想法
分別檢查列以及行是否有相同元素即可
### 程式碼
```cpp=
//By Koios1143
#include<iostream>
using namespace std;
int main(){
int arr[5][6];
for(int i=0 ; i<5 ; i++){
for(int j=0 ; j<6 ; j++){
cin>>arr[i][j];
}
}
bool find_ans=false;
// row
for(int i=0,cnt=1 ; i<5 ; i++,cnt=1){
for(int j=1 ; j<6 && !find_ans ; j++){
if(arr[i][j] == arr[i][j-1])
cnt++;
else
cnt=1;
if(cnt==3)
find_ans=true;
}
}
// col
for(int j=0,cnt=1 ; j<6 ; j++,cnt=1){
for(int i=1 ; i<5 && !find_ans ; i++){
if(arr[i][j] == arr[i-1][j])
cnt++;
else
cnt=1;
if(cnt==3)
find_ans=true;
}
}
if(find_ans)
cout<<"Yes\n";
else
cout<<"No\n";
return 0;
}
```
### 複雜度分析
總時間複雜度約為 $O(3nm)$
# UVa 10189
## 題目
http://domen111.github.io/UVa-Easy-Viewer/?10189
有一個地雷區,寬與長分別為 $n, m$ ,地雷以 `*` 表示
輸出每個非地雷的點四周八個方向總共有多少個地雷
## 解法 By Koios1143
### 想法
對於地圖上的每一個非地雷的點,直接檢查八個方向有多少地雷記錄下來即可
這裡有個小技巧,我們將八個方向 $x$ 和 $y$ 分別改變的大小用陣列記錄下來,接下來當我們要檢查八個方向,就只需要用 for 迴圈跑過一遍就可以了!
另外,這裡使用 cango 來判斷一個點是否超出範圍,在程式碼書寫上會變得更淺顯易懂
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int n,m,Case=1,dir[8][2]={{1,0},{-1,0},{0,1},{0,-1},{-1,-1},{1,-1},{-1,1},{1,1}};
char arr[105][105];
bool cango(int x,int y){
if(x<0 || x>=n || y<0 || y>=m) return false;
return true;
}
int main(){
bool out=false;
while(cin>>n>>m && (n||m)){
if(out){
cout<<'\n';
}
else{
out=true;
}
cout<<"Field #"<<Case++<<":\n";
// input
for(int i=0 ; i<n ; i++){
for(int j=0 ; j<m ; j++){
cin>>arr[i][j];
}
}
// solve
for(int i=0 ; i<n ; i++){
for(int j=0 ; j<m ; j++){
if(arr[i][j] == '*'){
cout<<'*';
}
else{
int cnt=0;
for(int k=0 ; k<8 ; k++){
// 枚舉八個方向
int nx=i+dir[k][0];
int ny=j+dir[k][1];
// 點在範圍內
if(cango(nx,ny)){
if(arr[nx][ny] == '*'){
cnt++;
}
}
}
cout<<cnt;
}
}
cout<<'\n';
}
}
return 0;
}
```
### 時間複雜度分析
輸入時間複雜度為 $O(nm)$
最差情況對於每個點都要檢查八個方向,複雜度為 $O(8nm)$
單筆測茲的時間複雜度為 $O(9nm)$ 約為 $O(nm)$
# ZJ b266
## 題目
https://zerojudge.tw/ShowProblem?problemid=b266
給一個經過幾個操作的矩陣,求原本矩陣的長寬以及該矩陣
可行的操作包括
- 翻轉
![](https://i.imgur.com/0hKaQkJ.png)
- 旋轉
![](https://i.imgur.com/1xjUnEG.png)
特別注意到,他要的是原本的矩陣所以所有操作都要反過來做
## 解法 By Koios1143
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int r,c,m,arr[15][15],op[15];
bool out=false;
void rotate(){
int tmp[15][15];
swap(r,c);
for(int i=0 ; i<r ; i++){
for(int j=0 ; j<c ; j++){
tmp[i][j] = arr[j][r-i-1];
}
}
swap(arr,tmp);
}
void mirror(){
int tmp[15][15];
for(int arr_x=r-1, tmp_x=0 ; arr_x>=0 ; arr_x--, tmp_x++){
for(int j=0 ; j<c ; j++){
tmp[tmp_x][j] = arr[arr_x][j];
}
}
swap(arr,tmp);
}
int main(){
while(cin>>r>>c>>m){
if(out){
cout<<"\n";
}
else{
out=true;
}
for(int i=0 ; i<r ; i++){
for(int j=0 ; j<c ; j++){
cin>>arr[i][j];
}
}
for(int i=0 ; i<m ; i++){
cin>>op[i];
}
for(int i=m-1 ; i>=0 ; i--){
if(op[i]==0){
rotate();
}
else{
mirror();
}
}
cout<<r<<" "<<c<<"\n";
for(int i=0 ; i<r ; i++){
for(int j=0 ; j<c ; j++){
if(j) cout<<' ';
cout<<arr[i][j];
}
cout<<"\n";
}
}
return 0;
}
```
### 時間複雜度分析
輸入時間複雜度為 $O(rc+m)$
翻轉與旋轉的時間複雜度皆為 $O(rc)$
每筆測資的時間複雜度為 $O(rc+m+mrc)$ 約為 $O(mrc)$
# UVa 10010
## 題目
http://domen111.github.io/UVa-Easy-Viewer/?10010
給一個 $m \times n$ 的圖,每個格子上都有一個字
接下來會有 $k$ 個字串,要問這個字串在圖中的位置,並且保證一定存在
我們在圖中尋找的方向有8個 : 往左、往右、往上、往下、往左上、往左下、往右上、往右下
並且請忽視大小寫
## 解法 By Koios1143
### 想法
對於每個點,如果和詢問的第一個字相同,就以它為起點向8個方向找,如果找到答案就直接輸出
這裡一樣用到 dir 陣列儲存八個方位的 $x, y$ 座標變化量
並且用 cango 判斷是否還在圖的範圍內
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int t,m,n,k,dir[8][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
char arr[55][55];
string query;
bool out=false;
bool cango(int x,int y){
// 判斷是否超出範圍
if(x<0 || x>=m || y<0 || y>=n) return false;
return true;
}
bool find_ans(int x, int y, int len){
// 向8個方向找答案,並回傳是否找到答案
for(int i=0 ; i<8 ; i++){
bool end=true;
for(int step=0 ; step<len ; step++){
int nx=x+dir[i][0]*step;
int ny=y+dir[i][1]*step;
if(!cango(nx,ny) || arr[nx][ny]!=query[step]){
// 不能走,或是不匹配則必定非為答案
end=false;
break;
}
}
if(end) return true;
}
return false;
}
int main(){
cin>>t;
while(t--){
if(out){
cout<<"\n";
}
else{
out=true;
}
cin>>m>>n;
for(int i=0 ; i<m ; i++){
for(int j=0 ; j<n ; j++){
cin>>arr[i][j];
arr[i][j]=tolower(arr[i][j]);
}
}
cin>>k;
while(k--){
cin>>query;
for(int i=0 ; i<query.size() ; i++){
query[i]=tolower(query[i]);
}
bool end=false;
int ans_x=-1,ans_y=-1;
for(int i=0 ; i<m ; i++){
for(int j=0 ; j<n && !end ; j++){
if(arr[i][j]==query[0] && find_ans(i,j,query.size())){
cout<<i+1<<" "<<j+1<<"\n";
end=true;
}
}
}
}
}
return 0;
}
```
### 時間複雜度分析
輸入時間複雜度為 $O(mn)$
對於8個方位的查詢,時間複雜度可以估計為 $O(1)$
則查詢的時間複雜度約為 $O(mn)$
總時間複雜度為 $O(tmn)$
# UVa 118
## 題目
http://domen111.github.io/UVa-Easy-Viewer/?118
有幾個機器人在 $x \times y$ 大小的地圖裡移動
移動方式包含 `左轉` `右轉` `前進`
當機器人走出地圖邊界,就會掉下去,並且在最後經過的點坐下標記
當其他機器人下次要走出地圖邊界,並且看到這個標記時,會自動忽略掉下去的指令
現在針對每一個機器人,詢問移動結束後的座標位置
## 解法 By Koios1143
### 想法
直接將整個過程模擬過一遍
記得除了記錄現在在哪裡之外,也要記錄當前面向哪個方向
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int n,m,sx,sy,face,arr[55][55];
char c,dir[4]={'N','E','S','W'};
string op;
int get_face(char ch){
if(ch=='N') return 0;
else if(ch=='E') return 1;
else if(ch=='S') return 2;
return 3;
}
int main(){
cin>>n>>m;
while(cin>>sx>>sy>>c){
// 以數字表示方位
face=get_face(c);
cin>>op;
bool lost=false;
// 針對指令進行操作
for(int i=0 ; i<op.size() ; i++){
if(op[i]=='L'){
face--;
if(face<0) face=3;
}
else if(op[i]=='R'){
face++;
if(face>3) face=0;
}
else{
if(face==0){
if(sy+1>m){
if(arr[sx][sy]==0){
arr[sx][sy]=-1;
lost=true;
break;
}
}
else
sy++;
}
else if(face==1){
if(sx+1>n){
if(arr[sx][sy]==0){
arr[sx][sy]=-1;
lost=true;
break;
}
}
else
sx++;
}
else if(face==2){
if(sy-1<0){
if(arr[sx][sy]==0){
arr[sx][sy]=-1;
lost=true;
break;
}
}
else
sy--;
}
else if(face==3){
if(sx-1<0){
if(arr[sx][sy]==0){
arr[sx][sy]=-1;
lost=true;
break;
}
}
else
sx--;
}
}
}
cout<<sx<<" "<<sy<<" "<<dir[face];
if(lost){
cout<<" LOST\n";
}
else{
cout<<'\n';
}
}
return 0;
}
```
### 時間複雜度分析
對於每一個指令操作的時間複雜度為 $O(1)$
操作每個機器人的時間複雜度為 $O(len(op))$
## 解法 By sa072686
### 想法
建表和餘數都是好東西,人人都該學
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int n,m,sx,sy, nx, ny,face,arr[55][55];
char c;
string dir = "NESW";
int tbl[128];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
string op;
int main(){
int i;
for (i=0; i<dir.size(); i++)
{
tbl[dir[i]] = i;
}
cin>>n>>m;
while(cin>>sx>>sy>>c){
// 以數字表示方位
face=tbl[c];
cin>>op;
bool lost=false;
// 針對每個指令進行操作
for(int i=0 ; i<op.size() ; i++){
if(op[i]=='L'){
face = (face+3) % 4;
}
else if(op[i]=='R'){
face = (face+1) % 4;
}
else{
nx = sx + dx[face];
ny = sy + dy[face];
if (nx < 0 || nx > n || ny < 0 || ny > m)
{
if (arr[sx][sy] == 0)
{
arr[sx][sy] = -1;
lost = true;
break;
}
}
else
{
sx = nx;
sy = ny;
}
}
}
cout<<sx<<" "<<sy<<" "<<dir[face];
if(lost){
cout<<" LOST\n";
}
else{
cout<<'\n';
}
}
return 0;
}
```
### 時間複雜度分析
對於每一個指令操作的時間複雜度為 $O(1)$
操作每個機器人的時間複雜度為 $O(len(op))$
# UVa 10500
## 題目
http://domen111.github.io/UVa-Easy-Viewer/?10500
給一張地圖,如果不能走的話會標記 $X$ ,你會從起點 $(x,y)$ 開始走,每次檢查4個方向,並將看到的結果記錄下來
檢查過程當中,依序檢查 北 東 南 西 ,第一次遇到可以走的就走過去
在4個方向都不能走的情況下結束,並輸出紀錄的結果以及走過的步數
## 解法 By Koios1143
### 想法
直接把走的過程模擬過一遍
從起點開始,每次檢查4個方向記錄下來,並且走向第一個可以走的地方
一直重覆到不能走為止,將結果輸出即可
記得方向要從 北->東->南->西
另外,在檢查四個方向的過程中,記得不要往走過的點走,不然可能會永遠不會結束喔w
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int n,m,x,y,dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
char arr[15][15], res[15][15];
bool vis[15][15];
void init(){
// 初始化
for(int i=0 ; i<15 ; i++){
for(int j=0 ; j<15 ; j++){
arr[i][j]='X';
res[i][j]='?';
vis[i][j]=false;
}
}
}
void print_table(){
// 輸出表格的間隔
for(int i=1 ; i<=m ; i++){
cout<<"|---";
}
cout<<"|\n";
}
bool cango(int x, int y){
// 判斷是否在圖的範圍內
if(x<0 || x>n || y<0 || y>m) return false;
return true;
}
int main(){
while(cin>>n>>m && (n||m)){
init();
cin>>x>>y;
for(int i=1 ; i<=n ; i++){
for(int j=1 ; j<=m ; j++){
cin>>arr[i][j];
}
}
res[x][y]='0';
vis[x][y]=true;
int move=0;
while(true){
bool found=false;
int found_x,found_y;
for(int i=0 ; i<4 ; i++){
int nx=x+dir[i][0];
int ny=y+dir[i][1];
res[nx][ny]=arr[nx][ny];
if(cango(nx,ny) && arr[nx][ny]!='X' && !found && !vis[nx][ny]){
found=true;
found_x=nx;
found_y=ny;
vis[found_x][found_y]=true;
}
}
if(found){
x=found_x;
y=found_y;
move++;
}
else{
break;
}
}
cout<<"\n";
for(int i=1 ; i<=n ; i++){
print_table();
for(int j=1 ; j<=m ; j++){
if(j==1) cout<<"|";
cout<<" "<<res[i][j]<<" |";
}
cout<<"\n";
}
print_table();
cout<<"\n";
cout<<"NUMBER OF MOVEMENTS: "<<move<<"\n";
}
return 0;
}
```
### 時間複雜度分析
輸入時間複雜度 $O(nm)$
每次針對4個方向尋找,時間複雜度視為 $O(1)$
最糟的情況需要將棋盤全部遍歷過,時間複雜度為 $O(nm)$
輸出時間複雜度也為 $O(nm)$
每筆測資總時間複雜度為 $O(3nm)$ 約為 $O(nm)$
# UVa 10196
## 題目
http://domen111.github.io/UVa-Easy-Viewer/?10196
給一個西洋棋盤的盤面,以小寫表示黑色,大寫表示白色
棋盤包含幾種棋子
- Pawn(士兵,以p或P表示)
- Knight(騎士,以n或N表示)
- Bishop(主教,以b或B表示)
- Rook(城堡,以r或R表示)
- Queen(皇后,以q或Q表示)
- King(國王,以k或K表示)
現在給你一個盤面,求是否有 白色/黑色 的國王正處於可以被攻擊的狀態,或是沒有這兩種狀態
保證沒有兩者同時處在可被攻擊狀態的情況
## 解法 By Koios1143
### 想法
很有趣的實作題w
基本上我們可以直接針對所有在盤面上的棋子都動動看,如果遇到可以攻擊敵方國王就直接輸出
如果寫得很冗長也許是很正常的
在書寫過程中也許會發現到時常出現重複的問題
這時候就可以好好善用 function 的優勢,讓程式碼變得更加簡潔
並且建議在寫這樣的實作題可以直接在 main 完成,並假定某個 function 能具有什麼功能,等到 main 寫完,再去完成那些 function
這樣寫起來會順暢許多
### 程式碼
```cpp=
#include<iostream>
using namespace std;
int game=1;
int pb[2][2]={{1,-1},{1,1}},pw[2][2]={{-1,-1},{-1,1}};
int knight[8][2]={{-1,-2},{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2}};
char board[10][10];
bool cango(int x,int y){
// 判斷是否超出棋盤
if(x<0 || x>=8 || y<0 || y>=8) return false;
return true;
}
bool rock(int x, int y, int color){
// 判斷城堡是否能 check
char king;
if(color==0)
king='K';
else
king='k';
for(int i=x-1 ; i>=0 ; i--){
//up
if(board[i][y]==king)
return true;
else if(board[i][y]!='.')
break;
}
for(int i=x+1 ; i<8 ; i++){
//down
if(board[i][y]==king)
return true;
else if(board[i][y]!='.')
break;
}
for(int i=y-1 ; i>=0 ; i--){
//left
if(board[x][i]==king)
return true;
else if(board[x][i]!='.')
break;
}
for(int i=y+1 ; i<8 ; i++){
//right
if(board[x][i]==king)
return true;
else if(board[x][i]!='.')
break;
}
return false;
}
bool bishop(int x,int y,int color){
// 判斷主教是否能 check
char king;
if(color==0)
king='K';
else
king='k';
for(int i=x-1, j=y+1 ; i>=0 && j<8 ; i--, j++){
// upper right
if(board[i][j]==king)
return true;
else if(board[i][j]!='.')
break;
}
for(int i=x-1, j=y-1 ; i>=0 && j>=0 ; i--, j--){
// upper left
if(board[i][j]==king)
return true;
else if(board[i][j]!='.')
break;
}
for(int i=x+1, j=y+1 ; i<8 && j<8 ; i++, j++){
// lower right
if(board[i][j]==king)
return true;
else if(board[i][j]!='.')
break;
}
for(int i=x+1, j=y-1 ; i<8 && j>=0 ; i++, j--){
// upper left
if(board[i][j]==king)
return true;
else if(board[i][j]!='.')
break;
}
return false;
}
bool check_over(char chess, int x, int y, int color){
// 確認是否能 check
char ch=tolower(chess);
if(ch=='p'){
// pawn
if(color==0){
// black
for(int i=0 ; i<2 ; i++){
int nx=x+pb[i][0];
int ny=y+pb[i][1];
if(cango(nx,ny) && board[nx][ny]=='K')
return true;
}
}
else{
//white
for(int i=0 ; i<2 ; i++){
int nx=x+pw[i][0];
int ny=y+pw[i][1];
if(cango(nx,ny) && board[nx][ny]=='k')
return true;
}
}
}
else if(ch=='r'){
// rook
return rock(x,y,color);
}
else if(ch=='b'){
// bishop
return bishop(x,y,color);
}
else if(ch=='q'){
// queen
return rock(x,y,color) || bishop(x,y,color);
}
else if(ch=='n'){
// knight
char king;
if(color==0)
king='K';
else
king='k';
for(int i=0 ; i<8 ; i++){
int nx=x+knight[i][0];
int ny=y+knight[i][1];
if(cango(nx,ny) && board[nx][ny]==king)
return true;
}
return false;
}
return false;
}
int main(){
while(true){
bool end=false;
for(int i=0 ; i<8 ; i++){
for(int j=0 ; j<8 ; j++){
cin>>board[i][j];
if(board[i][j]!='.')
end=true;
}
}
if(!end)
break;
bool found=false;
for(int i=0 ; i<8 && !found ; i++){
for(int j=0 ; j<8 && !found ; j++){
if(board[i][j]=='.' || board[i][j]=='k' || board[i][j]=='K')
continue;
if(islower(board[i][j])){
// black
if(check_over(board[i][j], i, j, 0)){
cout<<"Game #"<<game<<": white king is in check.\n";
game++;
found=true;
}
}
else{
// white
if(check_over(board[i][j], i, j, 1)){
cout<<"Game #"<<game<<": black king is in check.\n";
game++;
found=true;
}
}
}
}
if(!found){
cout<<"Game #"<<game<<": no king is in check.\n";
game++;
}
}
return 0;
}
```
### 時間複雜度分析
假設棋盤邊長為 $N$
輸入時間複雜度為 $O(N^2)$
所有棋子針對任意方向的搜尋時間複雜度都視為 $O(1)$
最差情況盤面上每個點都需要搜尋過,時間複雜度為 $O(N^2)$
每筆測資時間複雜度為 $O(2 \times N^2)$ 約為 $O(N^2)$
###### tags: `SCIST 演算法 題解`