# APCS 2020年01月題解 ## 第一題 猜拳 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=h026 ### 解套 N/A ### 題目分析 直接按照題述進行相關操作 先輸入所有妹妹準備出的拳,接著從頭遍歷 每一輪先輸出哥哥要出的拳,接著判斷勝負,如果分出勝負就輸出並直接return 0結束程式 否則就照題述決定下一輪哥哥要出的拳,不過要注意如果是第一輪的話因為妹妹不可能"連續兩輪出一樣的拳",所以不能判斷這一條(陣列會吃到邊界外) 最後如果能把所有迴圈跑完(沒有被結束)代表平手,輸出相關內容 時間複雜度$O(N)$ ### 解題流程 輸入$→$遍歷、輸出 ### 解題過程 #### 變數宣告 ```cpp=3 int f,n,y[10]; ``` 意義與題述同 #### 輸入 ```cpp=5 cin>>f>>n; for(int i=0;i<n;i++)cin>>y[i]; ``` #### 迴圈遍歷 ```cpp=7 for(int i=0;i<n;i++){ cout<<f<<" "; if(f==0&&y[i]==2||f==2&&y[i]==5||f==5&&y[i]==0){ cout<<": Won at round "<<i+1; return 0; } else if(y[i]==0&&f==2||y[i]==2&&f==5||y[i]==5&&f==0){ cout<<": Lost at round "<<i+1; return 0; } else{ if(i>=1&&y[i-1]==y[i]){ if(y[i]==0)f=5; else if(y[i]==2)f=0; else f=2; } else f=y[i]; } } ``` 先輸出要出的拳(第8行) 接著判斷有沒有贏(第9-12行)以及有沒有輸(第13-16行) 其中第12、15行直接return 0結束main函式避免跑到後面的平手部分 最後按照規則決定下一輪要出的拳(第17-24行) #### 平手的輸出 ```cpp=26 cout<<": Drew at round "<<n; ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> using namespace std; int f,n,y[10]; int main(){ cin>>f>>n; for(int i=0;i<n;i++)cin>>y[i]; for(int i=0;i<n;i++){ cout<<f<<" "; if(f==0&&y[i]==2||f==2&&y[i]==5||f==5&&y[i]==0){ cout<<": Won at round "<<i+1; return 0; } else if(y[i]==0&&f==2||y[i]==2&&f==5||y[i]==5&&f==0){ cout<<": Lost at round "<<i+1; return 0; } else{ if(i>=1&&y[i-1]==y[i]){ if(y[i]==0)f=5; else if(y[i]==2)f=0; else f=2; } else f=y[i]; } } cout<<": Drew at round "<<n; } ``` ## 第二題 矩陣總和 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=h027 ### 解套 定義兩相同小矩陣的「距離」為相同位置但數字不同的位置數量 求一大矩陣所有子矩陣中與小矩陣距離不超過$r$的子矩陣個數,以及其中總和與小矩陣相差最小的值 (若無輸出-1) ### 題目分析 因為矩陣大小最多只到$10×100$,所以可以考慮暴力一點的作法 直接遍歷大矩陣中所有與小矩陣大小相同的子矩陣,計算距離與總和即可 實作上我是枚舉子矩陣的左上角(這樣就能透過小矩陣長寬推得邊界) 並額外寫一個計算距離與總和的函式calc,並用calc回傳的值更新答案 時間複雜度$O(nmst)$ ### 解題流程 輸入$→$枚舉子矩陣、計算答案$→$輸出 ### 解題過程 #### 變數宣告 ```cpp=3 int s,t,n,m,r; int small[10][100],big[10][100],ssum=0,cnt=0,ans=INT_MAX; ``` small存小矩陣,big存大矩陣,ssum存小矩陣元素和,cnt存要多少子矩陣距離不超過$r$,ans存最小距離差,其餘與題述同 #### calc函式 ```cpp=5 pair<int,int> calc(int x1,int y1){ int cnt=0,ans=0; for(int i=x1;i<=x1+s-1;i++)for(int j=y1;j<=y1+t-1;j++){ ans+=big[i][j]; if(big[i][j]!=small[i-x1][j-y1])cnt++; } return {cnt,ans}; } ``` 參數x1,x2是子矩陣的左上角座標(第5行) 一開始宣告cnt,ans存距離與元素和(第6行) 接者遍歷子矩陣,計算cnt和ans(第7-10行) 最後以pair形式回傳cnt與ans(第11行) #### 輸入 ```cpp=14 cin>>s>>t>>n>>m>>r; for(int i=0;i<s;i++)for(int j=0;j<t;j++){ cin>>small[i][j]; ssum+=small[i][j]; } for(int i=0;i<n;i++)for(int j=0;j<m;j++)cin>>big[i][j]; ``` 第17行算小矩陣元素和 #### 枚舉子矩陣 ```cpp=20 for(int i=0;i<=n-s;i++)for(int j=0;j<=m-t;j++){ auto num=calc(i,j); if(num.first<=r){ cnt++; ans=min(abs(num.second-ssum),ans); } } ``` 先用num存calc函式計算的值(第21行) 接著判斷距離是否不超過$r$,若是就更新cnt與ans(第22-25行) #### 輸出 ```cpp=27 cout<<cnt<<"\n"; if(cnt!=0)cout<<ans; else cout<<-1; ``` 輸出cnt與ans,其中若沒有符合條件的子矩陣就不輸出ans改輸出-1 ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> using namespace std; int s,t,n,m,r; int small[10][100],big[10][100],ssum=0,cnt=0,ans=INT_MAX; pair<int,int> calc(int x1,int y1){ int cnt=0,ans=0; for(int i=x1;i<=x1+s-1;i++)for(int j=y1;j<=y1+t-1;j++){ ans+=big[i][j]; if(big[i][j]!=small[i-x1][j-y1])cnt++; } return {cnt,ans}; } int main(){ cin>>s>>t>>n>>m>>r; for(int i=0;i<s;i++)for(int j=0;j<t;j++){ cin>>small[i][j]; ssum+=small[i][j]; } for(int i=0;i<n;i++)for(int j=0;j<m;j++)cin>>big[i][j]; for(int i=0;i<=n-s;i++)for(int j=0;j<=m-t;j++){ auto num=calc(i,j); if(num.first<=r){ cnt++; ans=min(abs(num.second-ssum),ans); } } cout<<cnt<<"\n"; if(cnt!=0)cout<<ans; else cout<<-1; } ``` ## 第三題 砍樹 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=h028 ### 解套 N/A ### 題目分析 首先要先了解到一個重要的性質 > 無論砍樹的順序為何,最後能砍除的樹木是相同的 這個性質讓我們不必花時間思考「能砍掉最多樹木的方法」之類的, 只要最後有把能砍的樹木都砍完就行了 至於為什麼會這樣呢?因為題目有寫 知道順序不影響後,接下來就只要把該砍的樹都砍完就行了 這題的操作主要可以分成兩種: 判斷一棵樹可不可以砍,以及找到某棵樹的前一棵以及後一棵樹 判斷一棵樹能不能砍的部分比較簡單,只要一開始存好每棵樹的位置與高度,找到前後的樹再算一下就好了,所以本題的重點還是在於找到前後的樹 很明顯地一棵樹的前後會因為有樹被砍掉而有所改變,所以不能直接用原始輸入的順序判斷 我原本的想法是用set維護現存的樹已達到$O(logn)$查詢、刪除,在一年前也過了,但最近重寫發現時間限制被壓緊,所以只好想新的解法 後來發現可以直接用陣列紀錄一棵樹前後樹的編號,這樣就能$O(1)$查詢, 而且刪除的操作也算簡單,只要把「前一棵樹的下一棵樹」改成「自己的下一棵樹」、「下一棵樹的前一棵樹」改成「自己的前一棵樹」就能解決,如此就能避免真的把樹刪掉,節省時間 **前:** | 樹編號 | 前一棵樹 | 後一棵樹 | | -------- | -------- | -------- | | 2 | 1 | 3 | | 3 | 2 | 4 | | 4 | 3 | 5 | **後:** | 樹編號 | 前一棵樹 | 後一棵樹 | | -------- | -------- | -------- | | 2 | 1 | 4 | | 3 | 2 | 4 | | 4 | 2 | 5 | 實作上開一個二維陣列$loc[N][2]$,其中$loc[i][0]$、$loc[i][1]$分別代表編號為$i$的樹前一棵與後一棵的編號 於是就得到我們的解法:從左往右掃依次判斷每棵樹能不能砍(用$loc$加速),如果砍成功就更新答案、$loc$,並**跳回到前一棵樹**,因為如果這棵樹砍了,空間就變大了,可能讓前一棵樹變得可以砍 最終複雜度應該是$O(n)$,因為每顆樹最多砍掉一次,且就總數來看頂多處理$2n$次 ### 解題流程 輸入$→$初始化$→$砍樹(遍歷+回頭檢查)$→$輸出 ### 解題過程 #### define ```cpp=2 #define pos first #define h second ``` 依照存的東西更改命名 #### 變數宣告 ```cpp=5 int n,l,cnt=0,mx=0,loc[100000+10][2]; pair<int,int>arr[100000+10]; ``` cnt砍了幾棵樹,mx存最高的能砍的樹,arr存每棵樹的位置與高度,loc同前述,n,l同題述 #### 輸入優化 ```cpp=8 ios::sync_with_stdio(0); cin.tie(0); ``` 不加也會過 #### 輸入 ```cpp=10 cin>>n>>l; for(int i=1;i<=n;i++)cin>>arr[i].pos; for(int i=1;i<=n;i++)cin>>arr[i].h; ``` #### 初始化 ```cpp=13 arr[0]={1,0}; arr[n+1]={l,0}; for(int i=0;i<=n+1;i++){ loc[i][0]=i-1; loc[i][1]=i+1; } ``` 為了方便操作在arr前後加上邊界(這題設$0~l$或$1~l$都會過) #### 砍樹 ```cpp=19 int it=1; while(1){ if(arr[it].pos-arr[it].h>=arr[loc[it][0]].pos||arr[it].pos+arr[it].h<=arr[loc[it][1]].pos){ cnt++; mx=max(mx,arr[it].h); loc[loc[it][0]][1]=loc[it][1]; loc[loc[it][1]][0]=loc[it][0]; if(loc[it][0]!=0)it=loc[it][0]; else it=loc[it][1]; } else it=loc[it][1]; if(it>n)break; } ``` 一開始初始化位置為1(第19行) 接著不斷重複以下操作: 先判斷一棵樹能不能砍(第21行) 如果可以的話就更新答案(第22.23行),然後更新前後樹木的位置關係,最後更新it 需要注意的是如果it已經是第一棵樹了就不用往前,不然都要往前推一棵 如果不行就直接跳到下一棵樹 最後判斷如果已經到底了就終止迴圈 #### 輸出 ```cpp=32 cout<<cnt<<"\n"<<mx; ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> #define pos first #define h second using namespace std; int n,l,cnt=0,mx=0,loc[100000+10][2]; pair<int,int>arr[100000+10]; int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n>>l; for(int i=1;i<=n;i++)cin>>arr[i].pos; for(int i=1;i<=n;i++)cin>>arr[i].h; arr[0]={1,0}; arr[n+1]={l,0}; for(int i=0;i<=n+1;i++){ loc[i][0]=i-1; loc[i][1]=i+1; } int it=1; while(1){ if(arr[it].pos-arr[it].h>=arr[loc[it][0]].pos||arr[it].pos+arr[it].h<=arr[loc[it][1]].pos){ cnt++; mx=max(mx,arr[it].h); loc[loc[it][0]][1]=loc[it][1]; loc[loc[it][1]][0]=loc[it][0]; if(loc[it][0]!=0)it=loc[it][0]; else it=loc[it][1]; } else it=loc[it][1]; if(it>n)break; } cout<<cnt<<"\n"<<mx; } ``` ## 第四題 貨物分配 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=h029 https://zerojudge.tw/ShowProblem?problemid=f163 (f163的測資範圍較大,要開多一點記憶體,接下來開的大小是根據h029,要丟f163麻煩自行調整) ### 解套 構造一個二元樹狀結構,從根往下加值,每次把數值加到值比較小的子樹上, 並輸出最後到達的葉節點編號 ### 題目分析 說實話我不太確定這題究竟想要考什麼? 感覺應該是想考圖論中的二元樹,不過好像又只有用到存圖跟存點權的技巧,所以... 我的做法是用叫arr的pair陣列存每個節點的左右節點,並用叫w的int陣列存每個節點的重量 初始化重量時由下往上,若是貨艙就直接回傳原本的重量,否則把自身重量設為左右兩節點的值加起來再回傳 每次放貨物就從根節點往下遍歷,把重量加到比較輕的子樹上並更新自身重量,重覆遞迴到遇到葉節點為止(貨箱),最後輸出到達的貨箱 為此需要兩個函式,分別是初始化重量的init函式以及放置貨物的put函式 另外題目有保證入口編號一定是1,所以不須判斷 時間複雜度$O(mn)$(如果是一條鍊的話) ### 解題流程 輸入、存圖$→$初始化二元樹$→$放貨物、輸出 ### 解題過程 #### define ```cpp=2 #define L first #define R second ``` 讓自己寫起來比較順 #### 變數宣告 ```cpp=5 int n,m,w[200000],goods[100],p,s,t; pair<int,int>arr[100000]; ``` n,m,p,s,t同題述,w,arr如前述,goods存每個貨物重量 #### init函式 ```cpp=7 int init(int id){ if(id>=n)return w[id]; w[id]=init(arr[id].L)+init(arr[id].R); return w[id]; } ``` 若編號大於n代表是貨艙,直接回傳重量(第8行) 不然把重量設為左右節點的重量和(要先各自初始化完)再回傳自身重量(第9-10行) #### put函式 ```cpp=12 void put(int good,int id){ w[id]+=good; if(id>=n){ cout<<id<<" "; return; } if(w[arr[id].L]<=w[arr[id].R])put(good,arr[id].L); else put(good,arr[id].R); return; } ``` 先更新自身重量(第13行) 若已抵達貨倉就直接輸出並結束(第14-17行) 不然就依照左右重量決定貨物放到哪裡,並回傳(第18-20行) #### 輸入優化 ```cpp=23 ios::sync_with_stdio(0); cin.tie(0); ``` 不加也會過 #### 輸入、存圖 ```cpp=25 cin>>n>>m; for(int i=n;i<2*n;i++)cin>>w[i]; for(int i=0;i<m;i++)cin>>goods[i]; for(int i=1;i<n;i++){ cin>>p>>s>>t; arr[p]={s,t}; } ``` #### 初始化 ```cpp=32 int a=init(1); ``` 因為是int函式所以還是要把值存到某個地方去 #### 放貨物 ```cpp=33 for(int i=0;i<m;i++)put(goods[i],1); ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> #define L first #define R second using namespace std; int n,m,w[200000],goods[100],p,s,t; pair<int,int>arr[100000]; int init(int id){ if(id>=n)return w[id]; w[id]=init(arr[id].L)+init(arr[id].R); return w[id]; } void put(int good,int id){ w[id]+=good; if(id>=n){ cout<<id<<" "; return; } if(w[arr[id].L]<=w[arr[id].R])put(good,arr[id].L); else put(good,arr[id].R); return; } int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n>>m; for(int i=n;i<2*n;i++)cin>>w[i]; for(int i=0;i<m;i++)cin>>goods[i]; for(int i=1;i<n;i++){ cin>>p>>s>>t; arr[p]={s,t}; } int a=init(1); for(int i=0;i<m;i++)put(goods[i],1); } ```