# APCS 2017年10月題解 ## 第一題 邏輯運算子 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=c461 ### 解套 N/A ### 題目分析 首先因為AND操作、OR操作和XOR操作都有對應的位運算符號,所以輸入a,b後把非0數字都轉成1會比較方便,這樣就能直接用內建的位元運算得到答案 逐一判斷,如果符合就輸出對應的字串,最後如果都不合就輸出"IMPOSSIBLE" 時間複雜度$O(1)$ ### 解題流程 輸入$→$轉換$→$判斷、輸出 ### 解題過程 #### 變數宣告 ```cpp=3 int a,b; bool c; ``` 變數名同題目 #### 輸入、轉換 ```cpp=6 cin>>a>>b>>c; a=(a!=0?1:0),b=(b!=0?1:0); ``` #### 判斷、輸出 ```cpp=8 bool cmp=0; if((a&&b)==c){ cmp=1; cout<<"AND\n"; } if((a||b)==c){ cmp=1; cout<<"OR\n"; } if((a^b)==c){ cmp=1; cout<<"XOR\n"; } if(cmp==0)cout<<"IMPOSSIBLE"; ``` cmp紀錄有沒有符合的 ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> using namespace std; int a,b; bool c; int main(){ cin>>a>>b>>c; a=(a!=0?1:0),b=(b!=0?1:0); bool cmp=0; if((a&&b)==c){ cmp=1; cout<<"AND\n"; } if((a||b)==c){ cmp=1; cout<<"OR\n"; } if((a^b)==c){ cmp=1; cout<<"XOR\n"; } if(cmp==0)cout<<"IMPOSSIBLE"; } ``` ## 第二題 交錯字串 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=c462 ### 解套 N/A ### 題目分析 首先因為題目關心的是「連續大(小)寫的交錯」,所以我們只需要知道連續大(小)寫區間的長度就好 一開始先$O(n)$掃過字串,把連續的大(小)寫子字串長度存進陣列segments 接著就是遍歷segments以獲得答案(以ans紀錄) 我的作法是開一個變數$tmp$紀錄第一個遇到長度為k的子字串的位置, 然後持續遍歷直到遇到長度不是$k$的子字串 假設該位置是$i$,代表總共有$i-tmp$個子字串,不過這時還要考慮前一個$(tmp-1)$以及後一個$(i)$子字串長度是否大於k,因為這樣也能對答案有貢獻 (例:$k=1$,$s=aa B a BB$,答案會是$aBaB$而非$Ba$) 再和ans取較大值即可 需要注意的是每處理完一段就要把$tmp$變回-1,避免重複計算 最後輸出$ans×k$,因為ans紀錄的是子字串數量 時間複雜度$O(n)$ ### 解題流程 輸入$→$生成segments$→$計算答案$→$輸出 ### 解題過程 #### 變數宣告 ```cpp=3 int k,ans=0; string s; vector<int>segments; ``` 變數名同題述、前述 #### 輸入優化 ```cpp=7 ios::sync_with_stdio(0); cin.tie(0); ``` 不加也會過 #### 輸入 ```cpp=9 cin>>k>>s; ``` #### 產生segments ```cpp=10 int tmp=1; for(int i=1;i<s.size();i++){ if(isupper(s[i])==isupper(s[i-1]))tmp++; else{ segments.push_back(tmp); tmp=1; } } if(tmp)segments.push_back(tmp); ``` tmp存當前子字串長度 #### 更新答案 ```cpp=19 tmp=-1; for(int i=0;i<segments.size();i++){ if(segments[i]==k){ if(tmp==-1)tmp=i; else continue; } else{ if(tmp==-1)continue; else{ int num=i-tmp; if(segments[i]>k)num++; if(tmp!=0&&segments[tmp-1]>k)num++; ans=max(ans,num); tmp=-1; } } } if(tmp!=-1){ int num=segments.size()-tmp; if(tmp!=0&&segments[tmp-1]>k)num++; ans=max(ans,num); } ``` 處理方法同前述,只是最後要再確認一遍(這邊因為沒有「後一個」所以不用判斷) #### 輸出 ```cpp=41 cout<<ans*k; ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> using namespace std; int k,ans=0; string s; vector<int>segments; int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>k>>s; int tmp=1; for(int i=1;i<s.size();i++){ if(isupper(s[i])==isupper(s[i-1]))tmp++; else{ segments.push_back(tmp); tmp=1; } } if(tmp)segments.push_back(tmp); tmp=-1; for(int i=0;i<segments.size();i++){ if(segments[i]==k){ if(tmp==-1)tmp=i; else continue; } else{ if(tmp==-1)continue; else{ int num=i-tmp; if(segments[i]>k)num++; if(tmp!=0&&segments[tmp-1]>k)num++; ans=max(ans,num); tmp=-1; } } } if(tmp!=-1){ int num=segments.size()-tmp; if(tmp!=0&&segments[tmp-1]>k)num++; ans=max(ans,num); } cout<<ans*k; } ``` ## 第三題 樹狀圖分析 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=c463 ### 解套 dfs ### 題目分析 這明顯是一題dfs/bfs的題目,只是題目沒有給根節點,需要自己找,然後他求的答案有點神奇,是「某節點和最遠子節點的距離」的總和 (我是用dfs,然後存圖是用鄰接串列) 根節點的部分我是用unordered_set來求,一開始先把從1到n的數字都放進去,在存圖時遇到是別人子節點的就把他從unordered_set裡拿掉,最後剩下來的就是根節點 dfs部分在函式裡我用了兩個參數id和dep,紀錄處理的節點編號與深度,另外再開一個far陣列紀錄每個節點「最遠子節點的深度」,而實際在dfs時分成兩種狀況: 1. 葉節點(用子節點陣列大小是0判斷):這時為了方便就把葉節點視為自己,所以他的far值就是深度 2. 非葉節點:往下遍歷,並在遍歷過程中不斷更新far的值(自己和葉節點的far值取max) 最後把答案加上「far值檢調自己的深度」,全部跑完後就會是答案 喔對然後答案會爆int$(10^{10}>2^{31})$ 時間複雜度$O(n)$ ### 解題流程 輸入、存圖$→$dfs$→$輸出 ### 解題過程 #### 變數宣告 ```cpp=3 int n,a,b; long long ans=0; vector<vector<int>>v; unordered_set<int>st; vector<int>far; ``` n同題述,a,b只是輸入用,其他同前述 #### dfs函式 ```cpp=8 void dfs(int id,int dep){ if(v[id].size()==0){ far[id]=dep; return; } for(int i:v[id]){ dfs(i,dep+1); far[id]=max(far[id],far[i]); } ans+=far[id]-dep; return; } ``` 原理同前述 #### 輸入優化 ```cpp=21 ios::sync_with_stdio(0); cin.tie(0); ``` 不加也行 #### 輸入、初始化、存圖 ```cpp=23 cin>>n; v.assign(n+1,{}); far.assign(n+1,0); for(int i=1;i<=n;i++)st.insert(i); for(int i=1;i<=n;i++){ cin>>a; for(a;a>0;a--){ cin>>b; v[i].push_back(b); st.erase(b); } } ``` 輸入n後決定v,far的大小,並初始化st 接著照順序存子節點,並更新st #### dfs ```cpp=35 dfs(*st.begin(),0); ``` 從根節點開始遍歷,深度設為0 #### 輸出 ```cpp=36 cout<<*st.begin()<<"\n"<<ans; ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> using namespace std; int n,a,b; long long ans=0; vector<vector<int>>v; unordered_set<int>st; vector<int>far; void dfs(int id,int dep){ if(v[id].size()==0){ far[id]=dep; return; } for(int i:v[id]){ dfs(i,dep+1); far[id]=max(far[id],far[i]); } ans+=far[id]-dep; return; } int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; v.assign(n+1,{}); far.assign(n+1,0); for(int i=1;i<=n;i++)st.insert(i); for(int i=1;i<=n;i++){ cin>>a; for(a;a>0;a--){ cin>>b; v[i].push_back(b); st.erase(b); } } dfs(*st.begin(),0); cout<<*st.begin()<<"\n"<<ans; } ``` ## 第四題 物品堆疊 ### 題目敘述 https://zerojudge.tw/ShowProblem?problemid=c471 ### 解套 依照貪心法則排序 ### 題目分析 這是一題貪心題,~~但如果看不出來好像也沒辦法?~~ 我想到比較有邏輯的方式(不是通靈/靠經驗)是用複雜度分析;因為題目的$n$最大到$10^5$,時限1秒, 所以$O(n^2)$會超時,大概只能$O(nlogn)$或$O(n)$,但要$O(n)$著實有些困難(至少我想不到), 而如果要$O(nlogn)$第一個想到的大概就是排序吧? 不過這題不能單純要內建排序,需要自己寫自定義排序的比較規則。 因為自定義排序韓式只需要寫兩個東西的比較,所以我們只需要思考針對兩個物品怎麼排序就好 假設有A,B兩個物品,若A放上面,那要拿B時就會花w(A)×f(B)的能量,同理如果B放上面,那要拿A時就會花w(B)×f(A)的能量,而題目希望最小化消耗的能量,所以讓比較小的排上面(要拿較多次) 排序完後從頭遍歷一邊累計搬運次數,一邊將答案加上重量乘次數,就能得到最後答案 另外這一題會爆int(如果有100000個物品每個都重1000要拿1000次...),所以要開long long 時間複雜度$O(nlogn)$ ### 解題流程 輸入$→$排序$→$計算$→$輸出 ### 解題過程 #### define ```cpp=2 #define int long long #define w first #define f second ``` 方便起見,另外pair前面存重量、後面存次數 #### 變數宣告 ```cpp=6 int n,ans=0; pair<int,int>arr[100000+10]; ``` n同題述,ans存答案,arr存物品 #### 自定義排序 ```cpp=8 bool cmp(pair<int,int> a,pair<int,int> b){ return a.w*b.f<b.w*a.f; } ``` 邏輯同前述 #### 輸入優化 ```cpp=12 ios::sync_with_stdio(0); cin.tie(0); ``` 不加也會過 #### 輸入 ```cpp=14 cin>>n; for(int i=0;i<n;i++)cin>>arr[i].w; for(int i=0;i<n;i++)cin>>arr[i].f; ``` #### 排序 ```cpp=17 sort(arr,arr+n,cmp); ``` cmp要記得打 #### 計算答案 ```cpp=18 int cnt=arr[0].w; for(int i=1;i<n;i++){ ans+=arr[i].f*cnt; cnt+=arr[i].w; } ``` cnt紀錄累積的重量,因為最上面的不需要消耗能量,所以cnt預設為第0個物品的重量,而計算答案從第1個物品開始 #### 輸出 ```cpp=23 cout<<ans; ``` ### 完整程式碼 ```cpp=1 #include<bits/stdc++.h> #define int long long #define w first #define f second using namespace std; int n,ans=0; pair<int,int>arr[100000+10]; bool cmp(pair<int,int> a,pair<int,int> b){ return a.w*b.f<b.w*a.f; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=0;i<n;i++)cin>>arr[i].w; for(int i=0;i<n;i++)cin>>arr[i].f; sort(arr,arr+n,cmp); int cnt=arr[0].w; for(int i=1;i<n;i++){ ans+=arr[i].f*cnt; cnt+=arr[i].w; } cout<<ans; } ```