owned this note
owned this note
Published
Linked with GitHub
# APCS 2023 10月 心得和簡易程式
### **觀念題心得**
上午觀念題的題目比起之前比較少有超出範圍的感覺,但還是有一些莫名其妙的程式追蹤題,就是只能慢慢推也抓不出考點的題目,不知道是不是故意出出來拖時間的,整體計算量也偏多,還有一題變形到超奇怪的stack題沒抓到考點也推不出來Q,很多題目都是倒著問的,像是輸出為X程式碼為?,程式碼為X什麼測資會有bug?,這類題目不好推只能靠平常累積經驗,不寫程式也絕對吃虧。
## 實作題
### [P1機械鼠](https://zerojudge.tw/ShowProblem?problemid=m370)
將輸入位置分成左邊和右邊,會求最大最小值就能拿分。
```=C++
#include <bits/stdc++.h>
using namespace std;
int main()
{
int x, n; cin >> x >> n;
int mx = -101;
int mn = 101;
int l = 0, r = 0;
int p;
for(int i=0;i<n;i++){
cin >> p;
if(p > x){
r++;
mx = max(mx, p);
}else{
l++;
mn = min(mn, p);
}
}
if(l > r) cout << l << " " << mn;
else cout << r << " " << mx;
return 0;
}
```
### [P2卡牌遊戲](https://zerojudge.tw/ShowProblem?problemid=m371) 二維陣列
APCS的第二題大多是二維陣列模擬類型的題目,這題也不例外,算是其中較為簡單的,思考方式是先比較列再比較行,能夠配對(combo)就刪除(改為-1)然後繼續,看了一下測試資料的大小稍微計算時間複雜度就知道這題不會超時,再思考一下更簡單聰明的方法也不是不行,但既然都不用管時間了,當然就用能穩穩拿分,想法簡單不容易出錯的方式。檢查時發現同一行或列有兩組以上同時combo時會有bug,所以就加上break了(一列配對一組就離開),反正不管時間。
```=C++
#include <bits/stdc++.h>
using namespace std;
int p[22][42];
int main()
{
int n, m; cin >> n >> m;
int ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin >> p[i][j];
}
}
while(1){
bool combo = 0;
int pre;
//check row
for(int i=1;i<=n;i++){
pre = 0;
p[i][0] = -2;
for(int j=1;j<=m;j++){
if(p[i][j] == p[i][pre]){
ans += p[i][j];
p[i][j] = -1;
p[i][pre] = -1;
combo = 1;
break;
}else if(p[i][j] != -1){
pre = j;
}
}
}
//check column
for(int i=1;i<=m;i++){
pre = 0;
p[0][i] = -2;
for(int j=1;j<=n;j++){
if(p[j][i] == p[pre][i]){
ans += p[j][i];
p[j][i] = -1;
p[pre][i] = -1;
combo = 1;
break;
}else if(p[j][i] != -1){
pre = j;
}
}
}
if(combo == 0) break;
}
cout << ans;
return 0;
}
```
### [P3搬家](https://zerojudge.tw/ShowProblem?problemid=m372) DFS
看題目時邊看邊笑,笑的原因是這題出的很有趣,而且馬上就能想到是圖遍歷+條件判斷的題目,之後就開始動手實作,考試時的程式也幾乎是直接爆破,不像現在有稍微簡化過,沒用BFS是因為DFS對我來說較為直觀,BFS也沒有明顯的優點,但在Zerojudge上就有一條測試資料沒過:(,希望到時候成績出來,有人能解答這題用DFS能否拿到滿分?我自己是沒辦法驗證了,原因在後記。
```=C++
#include <bits/stdc++.h>
using namespace std;
int n, m;
char p[505][505];
bool vis[505][505];
int dis, ans;
void dfs(int x, int y){
dis++;
int nx, ny;
// have way top
if(p[x][y] == 'X' || p[x][y] == 'I' || p[x][y] == 'L' || p[x][y] == 'J'){
nx = x - 1;
ny = y;
if(nx >= 0 && !vis[nx][ny]){
// down open
if(p[nx][ny] == 'X' || p[nx][ny] == 'I' || p[nx][ny] == '7' || p[nx][ny] == 'F'){
vis[nx][ny] = 1;
dfs(nx, ny);
}
}
}
// have way down
if(p[x][y] == 'X' || p[x][y] == 'I' || p[x][y] == '7' || p[x][y] == 'F'){
nx = x + 1;
ny = y;
if(nx < n && !vis[nx][ny]){
// top open
if(p[nx][ny] == 'X' || p[nx][ny] == 'I' || p[nx][ny] == 'L' || p[nx][ny] == 'J'){
vis[nx][ny] = 1;
dfs(nx, ny);
}
}
}
// have way left
if(p[x][y] == 'X' || p[x][y] == 'H' || p[x][y] == '7' || p[x][y] == 'J'){
nx = x;
ny = y - 1;
if(ny >= 0 && !vis[nx][ny]){
// right open
if(p[nx][ny] == 'X' || p[nx][ny] == 'H' || p[nx][ny] == 'L' || p[nx][ny] == 'F'){
vis[nx][ny] = 1;
dfs(nx, ny);
}
}
}
// have way right
if(p[x][y] == 'X' || p[x][y] == 'H' || p[x][y] == 'L' || p[x][y] == 'F'){
nx = x;
ny = y + 1;
if(ny < m && !vis[nx][ny]){
// left open
if(p[nx][ny] == 'X' || p[nx][ny] == 'H' || p[nx][ny] == '7' || p[nx][ny] == 'J'){
vis[nx][ny] = 1;
dfs(nx, ny);
}
}
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin >> p[i][j];
}
}
ans = 0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(p[i][j] != '0' && !vis[i][j]){
dis = 0;
memset(vis, 0, 505*505);
vis[i][j] = 1;
dfs(i, j);
}
ans = max(ans, dis);
}
}
cout << ans;
return 0;
}
```
### [P4投資遊戲](https://zerojudge.tw/ShowProblem?problemid=m373) 動態規劃
大概帶著80分鐘進入第四題,第四題也是以往思考上最花時間的一題(對於我這菜鳥來說),看完題目看了一下子題配分,ok 40+60,直接考慮滿分作法沒有在客氣,畢竟寫完前三題了,思考了五分鐘大概確定就是DP題開始推轉移式,從最簡易的DP類型開始,就是設dp[i][j],為第i天使用了j個金牌的最大總收益,對於第i天來說只有兩種可能,就是用金牌和沒用金牌,假設用了並且是第k個,那麼前一天一定只能用k-1個,如果沒用前一天維持k個,兩者取最大值,轉移式推完後丟到寫好的二維迴圈裡嘗試跑測資,咦,好像對了,之後由於不太相信又多嘗試幾個自訂測資,試到想不出其它極端情況才放心,回去檢查前三題。
```=C++
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, k; cin >> n >> k;
vector<vector<int>> dp = vector<vector<int>>(n+1, vector<int>(k+1));
int ans = 0;
int p;
for(int i=1;i<=n;i++){
cin >> p;
dp[i][0] = 0;
for(int j=0;j<=k;j++){
dp[i][j] = max(dp[i-1][j-1], dp[i-1][j] + p);
ans = max(dp[i][j], ans);
}
}
cout << ans;
return 0;
}
```
如果k=0的話就是最大連續區間合了,可以參考[這裡](https://hackmd.io/@CLKO/rkhigGlTG?type=view#%E7%B6%93%E5%85%B8%E5%95%8F%E9%A1%8C%EF%BC%9A%E6%9C%80%E5%A4%A7%E9%80%A3%E7%BA%8C%E5%85%83%E7%B4%A0%E5%92%8C),我從中獲益許多。
### 後記
這次考APCS是感覺與五級分最接近的一次,回去檢查時發現第三題有一個bug,而且多數情況下不會出現,還記得那時用來測試的圖形是
```=C++
F 7 I
L X 7
I L J
```
時間越來越接近,好在有找到錯的地方,其中一個方向的程式碼沒有加上!vis,改完程式測試成功後還剩下兩分鐘,但還要去複製程式碼和提交,結果提交後測資結果錯誤,我超級緊張,只能選擇原本的程式再提交一次,等判定時,時間就結束了,回到家才驚覺,我把第三題的程式拿去交第四題xd。
### 心得和建議
給在奮鬥的高中生,學習程式的阻礙和挫折滿大的,你會寫一題題目花了半天時間,去看解答還看不懂,或是這題題目的想法竟然是這樣,給我一天我都想不到,然後覺得自己好爛,這裡提供四個建議,1.持續練習,花半天寫程式可能沒什麼感覺,甚至覺得這半天沒學到什麼,浪費了,但持續練習,有一天你會發現,"X我好強",然後繼續循環。2.考古題刷爛,這應該是取得成績最快的方式,說真的,不可能每次考試都生一個新的考點出來,能考的就那些,換湯不換藥,考前針對目標把考古題都做過絕對是CP值最高的方式。 3.針對練習,基礎語法不熟,好好把一套教程上完,二維迴圈不熟,就多做考古題第二題,像這次考試前也是狂練了動態規劃題,機會就剛好來了:)。4. 120%的準備,平常練習時打不出全壘打,那正式上場時更不可能,也算是我從這次考試學到的經驗,20%準備留給容錯,考APCS不可能不檢查,除非你有100%的自信,尤其測資都只給一兩個,zerojudge上的題目都還給多了,很多邊界、例外情況都會少處理。