# APCS 2023/6 題解
## ==路徑偵測==
### <font color="0E81A6">敘述</font>
給一個二維平面,座標如同數學的二維座標(Y正為北,X正為東)。
起始位置在 (0, 0),接下來會有n個座標,你需要按照這些座標點的順序移動,保證僅會垂直或水平方向上移動,不會斜向移動,且第一個點保證一定是X軸正的位置(初始方向向右)。
請輸出這條路徑中,左轉、右轉、迴轉的個數分別為多少。
### <font color="0E81A6">輸入說明</font>
第一行輸入一個正整數n,接下來有n行,每一行都有兩個正整數x,y。
保證的是相鄰兩個點的座標差值不超過100。
### <font color="0E81A6">輸出說明</font>
輸出三個正整數,分別代表左轉、右轉、迴轉的次數。
### <font color="E3A506">核心</font>
陣列、外積、條件判斷
### <font color="E3A506">思路</font>
會外積這題就會輕鬆很多。
dir是z軸向量的方向。
```c++=
#include <bits/stdc++.h>
using namespace std;
struct point
{
int x=0,y=0;
}p[105];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y);
int dir,left=0,right=0,turn=0;
int x1,x2,y1,y2;
for(int i=2;i<=n;i++)
{
x1=p[i-1].x-p[i-2].x,y1=p[i-1].y-p[i-2].y;
x2=p[i].x-p[i-1].x,y2=p[i].y-p[i-1].y;
dir=x1*y2-x2*y1;
if(dir>0) left++;
else if(dir<0) right++;
else
{
if(x1*x2<0||y1*y2<0) turn++;
}
}
printf("%d %d %d\n",left,right,turn);
}
```
## ==特殊位置==
### <font color="0E81A6">敘述</font>
給定一個n x m的二維矩陣a,設 x = a[i][j],離(i,j)曼哈頓距離為 x 內的點數值總和 % 10 恰為 x 的稱之為特殊位置。定義兩個點 (a, b) 和 (c, d) 的曼哈頓距離為 |a - c| + |b - d|。
請寫一個程式,輸出共有幾個特殊位置,並按照字典序由小到大輸出這些位置的座標。
### <font color="0E81A6">輸入說明</font>
第一行輸入兩個正整數 n , m (1<= n , m <= 50) ,接下來有 n 行,每行有 m 個數字,每一個數字介於 0 到 9 。
### <font color="0E81A6">輸出說明</font>
第一行輸出共有幾個特殊位置,接下來輸出 k 行,每一行輸出兩個正整數代表作標點位。特殊位置請按照字典順序由小到大輸出。
### <font color="E3A506">核心</font>
二維陣列、模擬
### <font color="E3A506">思路</font>
只需注意檢查邊界。
```c++=
#include<bits/stdc++.h>
using namespace std;
int M[55][55];
int X[55*55],Y[55*55];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) for(int j=0;j<m;j++) scanf("%d",&M[i][j]);
int num=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
int sum=0;
for(int x=max(0,i-M[i][j]);x<min(n,i+M[i][j]+1);x++)
{
for(int y=max(0,j-M[i][j]);y<min(m,j+M[i][j]+1);y++)
{
if(abs(x-i)+abs(y-j)<=M[i][j]) sum+=M[x][y];
}
}
sum%=10;
if(sum==M[i][j]) X[num]=i,Y[num]=j,num++;
}
}
printf("%d\n",num);
for(int i=0;i<num;i++) printf("%d %d\n",X[i],Y[i]);
}
```
## ==磁軌移動序列==
### <font color="0E81A6">敘述</font>
你拿到一個磁帶和一串指令。磁帶上的指針初始位置為 10,我們將其表示為 T10。指令是一個由多個 T 和 loop 指令組成的字串,每個指令都會影響指針的移動。
T 指令的格式為 Txx,其中 xx 是兩位數的整數(10~99),代表指針從當前位置移動到 xx 所指示的位置。
除了 T 指令外,還有一個 loop 指令結構,其格式為 Lx...E,其中 x 是一位數的整數(1~9)。loop 指令允許重複執行一系列指令。loop 指令的開始標記為 Lx,結束標記為 E,指令序列位於這兩個標記之間。loop 指令可以嵌套,也就是說,一個 loop 指令的內部可以包含其他的 loop 指令。保證所有 loop 指令內一定會有至少一個 T 指令。
請寫一個程式,根據給定的指令串,計算指針總共移動的距離。
範例: 給定指令串:T10T15T23T23T22T22T44 指針總共移動的距離為:5 + 8 + 0 + 1 + 0 + 22 = 36
字串長度不超過10^5^,總距離不超過2^60^
### <font color="0E81A6">輸入說明</font>
輸入一個字串,為該磁帶指針的控制指令。
### <font color="0E81A6">輸出說明</font>
輸出一個正整數,代表指針執行完指令後所移動的總路徑長。
### <font color="E3A506">核心</font>
queue、遞迴
### <font color="E3A506">思路</font>
有點像有括號的四則運算,由最裡層的嵌套往外計算。
last記錄每層嵌套的最左端,注意檢查和更新!
before為now往後探勘後的當前位置,now檢查到個位數,表示遇到一層嵌套,進行遞歸,若不是則計算距離並右移before。
若此嵌套需執行times次,則嵌套內距離將乘上times,注意嵌套最左端位置和最右端位置的距離只需乘times - 1次。接著將此嵌套的總距離返回給外層嵌套,內層就無需再理會了,很好理解,若外層執行k次,則外層的總距離為 ( 內層總距離 + 外層進入內層)* k + 外層左右端位置距離* (k-1),移此類推。
每層嵌套會返回它的最左端位置(給外層進入)、最右端位置以及總距離,分別是last、before、total_distance。
last要注意檢查!!!若<10則此嵌套開頭為內層嵌套,需更新為內層嵌套的最左端位置。
before的更新很好懂,不多做解釋。
```c++=
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define piii pair<pair<int,int>,int>
#define ff first.first
#define fs first.second
#define s second
queue<int> Q;
piii caculate()
{
int before = Q.front();
int last = Q.front();
int total_distance = 0;
while (Q.front()!=-1)
{
int now = Q.front();
Q.pop();
if (now >= 10)
{
total_distance += abs(now - before);
before = now;
}
else
{
piii in = caculate();
total_distance += (in.s) * now;
total_distance += abs(in.fs - in.ff) * (now - 1);
if(last<10) last=in.ff;
else total_distance+=abs(before-in.ff);
before=in.fs;
}
}Q.pop();
return {{last, before}, total_distance};
}
signed main()
{
char ch;
int num;
while (scanf("%c", &ch) != EOF)
{
if (ch == '\n') break;
else if (ch == 'T') scanf("%d", &num), Q.push(num); // num >= 10
else if (ch == 'L') scanf("%d", &num), Q.push(num); // num < 10
else if (ch == 'E') Q.push(-1);
}
Q.push(-1);
piii ans = caculate();
printf("%lld\n", ans.s+abs(10-ans.ff));
}
```
## ==開啟寶盒==
### <font color="0E81A6">敘述</font>
已知有 n 個寶盒編號 0~n-1 以及 m 種鑰匙編號 0~m-1。一開始你有 t
種鑰匙分別為x1,...,xt。
每一個寶盒要打開都需要同時擁有 k 種鑰匙,第 i 個寶盒分別需要 ri1,...,rik 種類的鑰匙。每個寶盒打開後都會得到 k 種鑰匙,第 i 個寶盒打開後分別會得到 si1,...,sik 種類的鑰匙,當拿到新的鑰匙之後可以繼續開啟新的寶盒。保證寶盒內的鑰匙不會重複,並且每種鑰匙可以開啟的寶盒數量不超過60。
請輸出最多可以開啟多少個寶盒。
### <font color="0E81A6">輸入說明</font>
第一行輸入三個正整數 n,m,k,代表有 n 個寶盒,m 種鑰匙以及每個寶盒需要的鑰匙數量 k。
接下來輸入一行,第一個數字 t 代表一開始有的鑰匙種類數量,後面有 t 個數字代表這些鑰匙分別的種類。
接下來有 n 行,每一行有 k 個數字,代表要開啟第 i 個寶盒的第 j 種鑰匙為 rij。
最後有 n 行,每一行有 k 個數字,代表開啟第 i 個寶盒後得到的第 j 種鑰匙為 sij 。
### <font color="E3A506">核心</font>
圖論、BFS
### <font color="E3A506">思路</font>
暴力解肯定超時,此題必須在O(n)內。
試想一下,已知每個寶盒需要的鑰匙,換句話說,已知哪種鑰匙能打開此寶盒。
我們記錄which鑰匙能打開which種寶盒為keysOpen陣列,which寶盒能給which種鑰匙為boxsGive陣列。
boxsCount記錄每個寶盒剩餘所需鑰匙數。
答案呼之欲出了!!
我們只需走訪所獲得的鑰匙就好了,看它能打開哪種寶盒並獲得哪種鑰匙。
以queue記錄手頭上的鑰匙,每拿一把,就開它能開的寶盒i,然後丟掉這把鑰匙,
boxsCount[i]- -,if boxsCount[i]==0,寶盒打開,欣賞它的鑰匙並搶走,推入queue。但很可惜的是,已經看過的鑰匙就不用拿了,別氣憤,因為時間不允許。
```c++=
#include<bits/stdc++.h>
using namespace std;
#define N 100005
vector<int> boxsGive[N];
vector<int> keysOpen[N];
int boxsCount[N];
queue<int> own;
bool keysHave[N]={false};
int main()
{
int n,m,k,t;
scanf("%d%d%d%d",&n,&m,&k,&t);
int key;
for(int i=0;i<t;i++) scanf("%d",&key),keysHave[key]=true,own.push(key);
for(int i=0;i<n;i++)
{
for(int j=0;j<k;j++) scanf("%d",&key),keysOpen[key].push_back(i);
}
for(int i=0;i<n;i++)
{
for(int j=0;j<k;j++) scanf("%d",&key),boxsGive[i].push_back(key);
}
for(int i=0;i<n;i++) boxsCount[i]=k;
int ans=0;
while(!own.empty())
{
key=own.front();
own.pop();
for(int num:keysOpen[key])
{
boxsCount[num]--;
if(boxsCount[num]==0)
{
ans++;
for(int get:boxsGive[num])
{
if(keysHave[get]==false) keysHave[get]=true,own.push(get);
}
}
}
}
printf("%d\n",ans);
}
```