# SCIST S4 Week 4 :::info 時間:12/24 9:00 ~ 17:00 主題:實作技巧與模擬 ::: [TOC] ## 課程內容 - 實作技巧與模擬 - 講義連結:https://hackmd.io/@sa072686/cp/%2F%40sa072686%2FrJpY0i7PS ## 建表 ### [Kattis Trik](https://open.kattis.com/problems/trik) 撰寫者:小麥 雖然用簡單的條件判斷 + 陣列模擬就可以解決了 但這邊還是用建表的技巧實作看看 :::spoiler 參考程式碼 ```cpp #include<iostream> using namespace std; int mv[3][2] = {{0, 1}, {1, 2}, {0, 2}}; bool cup[3] = {1, 0, 0}; // 球所在的位置 int main(){ string s; cin >> s; for (int i = 0; i < s.size(); i++){ int tp = s[i] - 'A'; swap (cup[ mv[tp][0] ], cup[ mv[tp][1] ]); } for (int i = 0; i < 3; i++) if (cup[i]) cout << i + 1 << '\n'; return 0; } ``` ::: --- ### [AtCoder abc202_b (基本練習)](https://atcoder.jp/contests/abc202/tasks/abc202_b) --- ### [Kattis t9spelling (基本練習)](https://open.kattis.com/problems/t9spelling) --- ### [UVa 11223 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?11223) --- ### [Kattis astrologicalsign](https://open.kattis.com/problems/astrologicalsign) 這一題就是課堂上講的判斷星座的題目,只要自己手動把表打出來就好。 最後再去用 for 迴圈判斷有沒有在範圍內。 :::spoiler 參考程式碼 ```cpp= ``` ::: --- ### [AtCoder abc075_b](https://atcoder.jp/contests/abc075/tasks/abc075_b) 撰寫者:Eason 這題要求每一格附近有多少個地雷,有兩種實作方法 - 第一種:找地雷所在的位置,把地雷周圍的座標計數加一 - 第二種:將每個位置的周圍都掃一遍,再將周圍地雷個數紀錄到該位置 以下會用第一種實作,其實不管哪一種方法,都是要用二維陣列實作,我們可以利用「將移動方向建表」來降低實作的複雜程度。 | (x, y) | -1 | 0 | 1 | | :----: | :-----: | :-----: | :-----: | | -1 | (-1, -1) | (0, -1) | (1, -1) | | 0 | (-1, 0) | (0, 0) | (1, 0) | | 1 | (-1, 1) | (0, 1) | (1, 1) | 將自己設為 (0, 0),周圍的座標就可以像上表那樣表示,code 裡面的第 5、6 行就是將上表寫成程式碼的樣子。所以接下來我要找一個位置周圍的座標時,就可以用一個迴圈把 x_mv、y_mv 掃過一遍即可(第 27 行)。 這個實作技巧在之後的 BFS 會很常出現,學員務必要熟悉。 :::spoiler code ```cpp= #include<iostream> using namespace std; // 八個方位的表示,可配合題解中的表格(由左而右,由上而下) const int x_mv[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; const int y_mv[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; string g[55]; int ans[55][55]; int h, w; bool in_scope (int a, int b){ // 檢查 a, b 是否超出範圍 return a >= 0 && a < h && b >= 0 && b < w; } int main(){ cin >> h >> w; for (int i = 0; i < h; i++){ cin >> g[i]; } for (int i = 0; i < h; i++){ for (int j = 0; j < w; j++){ if (g[i][j] == '#'){ ans[i][j] = -1; for (int k = 0; k < 8; k++){ int ti = i + y_mv[k]; int tj = j + x_mv[k]; if (in_scope(ti, tj) && ans[ti][tj] != -1){ ans[ti][tj]++; } } } } } for (int i = 0; i < h; i++){ for (int j = 0; j < w; j++){ if (ans[i][j] == -1) cout << '#'; else cout << ans[i][j]; } cout << '\n'; } return 0; } ``` ::: --- ### [Kattis nineknights](https://hackmd.io/@sa072686/Kattis_nineknights) 撰寫者:Eason 解法跟 [AtCoder abc075_B](https://atcoder.jp/contests/abc075/tasks/abc075_b) 差不多,,稍微改一下表的內容,要記得多判斷 k 的數量。 :::spoiler code ```cpp= #include<iostream> using namespace std; const int x_mv[8] = {-2, -1, 1, 2, -2, -1, 1, 2}; const int y_mv[8] = {-1, -2, -2, -1, 1, 2, 2, 1}; string g[10]; bool in_scope (int a, int b){ return a >= 0 && a < 5 && b >= 0 && b < 5; } int main(){ for (int i = 0; i < 5; i++){ cin >> g[i]; } bool is_valid = true; int cnt = 0; for (int i = 0; i < 5; i++){ for (int j = 0; j < 5; j++){ if (g[i][j] == 'k'){ cnt++; for (int k = 0; k < 8; k++){ int ti = i + y_mv[k]; int tj = j + x_mv[k]; if (in_scope(ti, tj) && g[ti][tj] == 'k'){ is_valid = false; } } } } } if (is_valid && cnt == 9) cout << "valid\n"; else cout << "invalid\n"; return 0; } ``` ::: --- ### [AtCoder abc096_c (基本練習)](https://atcoder.jp/contests/abc096/tasks/abc096_c) --- ### [UVa 10500 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?10500) --- ### [UVa 118 (延伸挑戰)](http://domen111.github.io/UVa-Easy-Viewer/?118) --- ### [ZJ k731](https://zerojudge.tw/ShowProblem?problemid=k731) --- ### [Kattis majstor (基本練習)](https://open.kattis.com/problems/majstor) --- ### [Kattis marko (延伸挑戰)](https://open.kattis.com/problems/marko) --- ### [Kattis soundex (延伸挑戰)](https://open.kattis.com/problems/soundex) --- ### [ZJ a020](https://zerojudge.tw/ShowProblem?problemid=a020) 撰寫者:Eason 將題目給的 A~Z 對應的數字建成表,然後就按照題目講得實作即可 :::spoiler code ```cpp= #include<iostream> using namespace std; // A~Z 對應到的數字 int num[30] = {10, 11, 12, 13, 14, 15, 16, 17, 34, 18, 19, 20, 21, 22, 35, 23, 24, 25, 26, 27, 28, 29, 32, 30, 31, 33}; int main(){ string s; cin >> s; int idx = s[0] - 'A'; int sum = num[idx] / 10; sum += (num[idx] % 10) * 9; for (int i = 1; i <= 8; i++){ sum += (s[i] - '0') * (9 - i); } sum += s[9] - '0'; if (sum % 10) cout << "fake\n"; else cout << "real\n"; return 0; } ``` ::: --- ### [UVa 10082](http://domen111.github.io/UVa-Easy-Viewer/?10082) --- ### [Kattis goodmorning (延伸挑戰)](https://open.kattis.com/problems/goodmorning) --- ### [Kattis playfair (基本練習)](https://open.kattis.com/problems/playfair) --- ### [Kattis printingcosts (基本練習)](https://open.kattis.com/problems/printingcosts) --- ### [Kattis anewalphabet (基本練習)](https://open.kattis.com/problems/anewalphabet) --- ### [Kattis bela (基本練習)](https://open.kattis.com/problems/bela) --- ### [Kattis keylogger](https://open.kattis.com/problems/keylogger) --- ### [Kattis parentgap (基本練習)](https://open.kattis.com/problems/parentgap) --- ### [ZJ j123 (基本練習)](https://zerojudge.tw/ShowProblem?problemid=j123) --- ### [UVa 706 (延伸挑戰)](http://domen111.github.io/UVa-Easy-Viewer/?706) --- ### [Kattis tetris (延伸挑戰)](https://hackmd.io/@sa072686/Kattis_tetris) --- ### [Kattis queens (基本練習)](https://hackmd.io/@sa072686/Kattis_queens) ## 窮舉 ### [ZJ b840](https://zerojudge.tw/ShowProblem?problemid=b840) 撰寫者:小麥 窮舉每個點當作左上角的起點,然後用兩層for迴圈窮舉所有子矩形的大小(長&寬) 再遍歷 所以最後程式長相會是有6個for迴圈包在一起 時間複雜度:$O(n^6)$ $n \leq 20$ 所以 $n^6$ 的計算量可以在 1 秒內跑完! 補充: 先用建立二維前綴和,在枚舉起點和終點。 時間複雜度:$O(n^2)$ :::spoiler 參考程式碼 ```cpp= #include <iostream> using namespace std; int nums[20][20]; int prefixSum[20 + 1][20 + 1]; int main() { int n; cin >> n; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cin >> nums[i][j]; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { prefixSum[i][j] = (prefixSum[i-1][j] - prefixSum[i-1][j-1]) + prefixSum[i][j-1] + nums[i-1][j-1]; } } int maxi = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { for (int o = i; o <= n; o++) { for (int k = j; k <= n; k++) { int sum = prefixSum[o][k] - prefixSum[o][j-1] - prefixSum[i-1][k] + prefixSum[i-1][j-1]; if (sum > maxi) { maxi = sum; } } } } } cout << maxi << '\n'; return 0; } ``` ::: --- ### [Kattis doggopher (基本練習)](https://hackmd.io/@sa072686/Kattis_doggopher) --- ### [ZJ f312](https://zerojudge.tw/ShowProblem?problemid=f312) 撰寫者:小麥 先從第一個工廠有最多的員工開始,第一個工廠-1,第二個工廠就+1,一直到第一個工廠到0為止 :::spoiler 參考程式碼 ```cpp= #include <iostream> using namespace std; int main() { int A1, B1, C1; int A2, B2, C2; int n; cin >> A1 >> B1 >> C1; cin >> A2 >> B2 >> C2; cin >> n; int maxi = -1000000; int left_pointer = n; int right_pointer = 0; while (right_pointer != n + 1) { int Y1 = A1 * (left_pointer * left_pointer) + B1 * left_pointer + C1; int Y2 = A2 * (right_pointer * right_pointer) + B2 * right_pointer + C2; int sum = Y1 + Y2; if (maxi < sum) { maxi = sum; } left_pointer--; right_pointer++; } cout << maxi << '\n'; return 0; } ``` ::: --- ### [UVa 100 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?100) --- ### [Kattis bubbletea (基本練習)](https://open.kattis.com/problems/bubbletea) 撰寫者:fishhh 這個問題要解決的是,在給定一定金額的情況下,最多能夠邀請多少位學生參加奶茶派對。 奶茶店提供多種茶和配料,每種都有不同的價格,但不是每種茶和配料的組合都是合法的。 - 一杯奶茶的價格等於茶的價格加上配料的價格。 - 一個學生只能喝一杯奶茶,且每杯奶茶都只能搭配一種茶和一種配料。 - **不是每種茶都能與所有的配料搭配,這會在輸入的時候給。** - PVH首先要為自己買一杯奶茶。 例如: ``` 3 10 20 30 5 1 2 3 4 5 1 4 5 2 1 3 5 3 1 2 4 42 ``` 表示有三種茶,價格分別是 10、20 和 30;有五種配料,價格分別是 1、2、3、4 和 5。接下來的信息表示: - 第一種茶可以搭配第四和第五種配料。 - 第二種茶可以搭配第一、第三和第五種配料。 - 第三種茶可以搭配第一、第二和第四種配料。 - PVH有42元,問題是他最多能夠為多少位學生購買奶茶。在這個例子中,可以發現最便宜的組合是第一種茶搭配第四種配料,總價14。這樣PVH還剩下28元,他可以再為兩位學生購買奶茶,因此答案是3。 :::spoiler 參考程式碼 ```cpp= #include<iostream> using namespace std; int main(){ int sm=999,g,k,i,n,m,money,tea[2000],topping[2000]; cin>>n; for(i=0;i<n;i++){ cin>>tea[i]; } cin>>m; for(i=0;i<m;i++){ cin>>topping[i]; } for(i=0;i<n;i++){ cin>>k; while(k--){ cin>>g; g--; if(topping[g]+tea[i]<sm)sm=topping[g]+tea[i]; } } cin>>money; if(money/sm==0){ cout<<0; return 0; } cout<<money/sm-1<<"\n"; } ``` ::: --- ### [ZJ f606](https://zerojudge.tw/ShowProblem?problemid=f606) 撰寫者:fishhh 這一題可能需要讀懂他的題意? 那就是方案總花費為每個伺服器傳送到每個城市的加總 直接按題目要求處理即可 因為第$i$個伺服器要傳到城市$j$的流量(也就是$Q[i][j]$)是固定的,所以先把它存起來 而每一個方案會有不同配置方式,所以每一個方案計算需要的總花費,最後再取最小值即可 :::spoiler 參考程式碼 ```cpp= #include "iostream" using namespace std; #define int long long signed main(){ int n,m,k; int q[51][51]={}; cin>>n>>m>>k; for(int y=0;y<n;y++){ for(int x=0;x<m;x++){ cin>>q[x][y]; } } int minn=1e9; while(k--){ int ans=0; int sum[60][60]={}; int l; for(int i=0;i<n;i++){ cin>>l; for(int v=0;v<m;v++){ sum[l][v]+=q[v][i]; } } for(int y=0;y<m;y++){ for(int x=0;x<m;x++){ if(x==y){ ans+=sum[x][y]; } else if(sum[x][y]<=1000){ ans+=sum[x][y]*3; } else{ ans+=3000; ans+=(sum[x][y]-1000)*2; } } } // cout<<ans<<" "; minn= min(minn,ans); } cout<<minn; } ``` ::: --- ### [Kattis refrigerator (基本練習)](https://open.kattis.com/problems/refrigerator) --- ### [Kattis damagedequation (基本練習)](https://open.kattis.com/problems/damagedequation) --- ### [Kattis icpcawards (基本練習)](https://open.kattis.com/problems/icpcawards) --- ### [AtCoder abc216_b](https://hackmd.io/@sa072686/AtCoder_abc216_b) 撰寫者:fishhh 透過兩個迴圈來枚舉是否有兩個人的名字和姓氏相同,需要注意到 for 迴圈會不會枚舉到相同的兩個人。 :::spoiler 參考程式碼 ```cpp= #include "iostream" using namespace std; string s[1010],t[1010]; int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>s[i]>>t[i]; } bool yes = 0; for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ if(s[i]==s[j]&&t[i]==t[j]){ yes = 1; } } } if(yes==1){ cout<<"Yes\n"; } else{ cout<<"No\n"; } } ``` ::: --- ### [Kattis musicalscales (延伸挑戰)](https://open.kattis.com/problems/musicalscales) --- ### [Kattis dodecaphony](https://hackmd.io/@sa072686/Kattis_dodecaphony) --- ### [Kattis geneticsearch (基本練習)](https://open.kattis.com/problems/geneticsearch) --- ### [UVa 12289 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?12289) --- ### [UVa 608](http://domen111.github.io/UVa-Easy-Viewer/?608) --- ### [Kattis klockan2 (延伸挑戰)](https://open.kattis.com/problems/klockan2) --- ### [UVa 11059](http://domen111.github.io/UVa-Easy-Viewer/?11059) --- ### [UVa 626 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?626) --- ### [UVa 11236 (延伸挑戰)](http://domen111.github.io/UVa-Easy-Viewer/?11236) --- ### [UVa 592 (延伸挑戰)](https://hackmd.io/@sa072686/UVa_592) --- ### [Kattis odds (基本練習)](https://hackmd.io/@sa072686/Kattis_odds) --- ### [AtCoder abc207_c (基本練習)](https://hackmd.io/@sa072686/AtCoder_abc207_c) --- ### [AtCoder abc112_c (延伸挑戰)](https://atcoder.jp/contests/abc112/tasks/abc112_c) --- ### [Kattis lektira](https://hackmd.io/@sa072686/Kattis_lektira) --- ### [Kattis names (基本練習)](https://open.kattis.com/problems/names) --- ### [Kattis peg (延伸挑戰)](https://open.kattis.com/problems/peg) --- ### [Kattis glitchbot](https://hackmd.io/@sa072686/Kattis_glitchbot) ## 模擬 ### [UVa 127 (延伸挑戰)](http://domen111.github.io/UVa-Easy-Viewer/?127) --- ### [Kattis traveltheskies (基本練習)](https://open.kattis.com/problems/traveltheskies) --- ### [Kattis acm (基本練習)](https://open.kattis.com/problems/acm) --- ### [ZJ e287](https://zerojudge.tw/ShowProblem?problemid=e287) --- ### [ZJ h081](https://zerojudge.tw/ShowProblem?problemid=h081) 撰寫者:fishhh 根據題意模擬。可以稍微注意一下要如何記錄目前手中持有股票的狀態。 :::spoiler ```cpp= #include <iostream> using namespace std; int main() { int n, D; cin >> n >> D; int a[n]; for (int i = 0; i < n; i++) { cin >> a[i]; } int x = a[0], y = 0; int ans = 0; for (int i = 1; i < n; i++) { if (x != -1) { //有股票 if (a[i] >= x + D) { ans += a[i] - x; //獲利 x = -1; //清空持股 y = a[i]; //紀錄上一次的賣出價格 } } else { //沒有股票 if (a[i] <= y - D) { x = a[i]; //買進股票的價格 } } } cout << ans << "\n"; return 0; } ``` ::: --- ### [Kattis keytocrypto](https://open.kattis.com/problems/keytocrypto) --- ### [UVa 151 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?151) --- ### [UVa 133 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?133) --- ### [ZJ h082 (基本練習)](https://zerojudge.tw/ShowProblem?problemid=h082) --- ### [Kattis coconut (延伸挑戰)](https://open.kattis.com/problems/coconut) --- ### [ZJ f580 (延伸挑戰)](https://zerojudge.tw/ShowProblem?problemid=f580) --- ### [Kattis coffeecupcombo](https://open.kattis.com/problems/coffeecupcombo) --- ### [ZJ g596 (基本練習)](https://zerojudge.tw/ShowProblem?problemid=g596) --- ### [Kattis ferryloading4](https://open.kattis.com/problems/ferryloading4) --- ### [ZJ c297 (基本練習)](https://zerojudge.tw/ShowProblem?problemid=c297) --- ### [Kattis turtlemaster](https://hackmd.io/@sa072686/Kattis_turtlemaster) --- ### [UVa 10443 (基本練習)](http://domen111.github.io/UVa-Easy-Viewer/?10443) --- ### [ZJ f313](https://zerojudge.tw/ShowProblem?problemid=f313) 撰寫者:fishhh 記得開兩個陣列來進行模擬,一個是目前的狀態,一個是未來的狀態。 在一個回合結束後再把未來的狀態取代掉目前的狀態。 :::spoiler ```cpp= #include<iostream> using namespace std; #define fastio ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) int main(){ fastio; int r,c,k,m; int num[51][51]={0},tmp[51][51]={0}; cin>>r>>c>>k>>m; for(int x=0;x<r;x++){ for(int y=0;y<c;y++){ cin>>num[x][y]; } } for(int i=0;i<m;i++){ for(int y=0;y<r;y++){ for(int x=0;x<c;x++){ tmp[y][x]=(num[y][x]>=0)*(num[y][x]/k); } } for(int x=0;x<r;x++){ for(int y=0;y<c;y++){ if(num[x][y]==-1){ continue; } if(x==0){ num[x][y]+=tmp[x+1][y]; num[x+1][y]-=tmp[x+1][y]; } else if(x==r){ num[x][y]+=tmp[x-1][y]; num[x-1][y]-=tmp[x-1][y]; } else{ num[x][y]+=tmp[x+1][y]; num[x+1][y]-=tmp[x+1][y]; num[x][y]+=tmp[x-1][y]; num[x-1][y]-=tmp[x-1][y]; } if(y==0){ num[x][y]+=tmp[x][y+1]; num[x][y+1]-=tmp[x][y+1]; } else if(y==c){ num[x][y]+=tmp[x][y-1]; num[x][y-1]-=tmp[x][y-1]; } else{ num[x][y]+=tmp[x][y+1]; num[x][y+1]-=tmp[x][y+1]; num[x][y]+=tmp[x][y-1]; num[x][y-1]-=tmp[x][y-1]; } } } } int big=0,small=999; for(int x=0;x<r;x++){ for(int y=0;y<c;y++){ if(num[x][y]>big){ big=num[x][y]; } if(num[x][y]<small&&num[x][y]!=-1){ small=num[x][y]; } } } cout<<small<<endl<<big; } ``` ::: --- ### [ZJ g276 (基本練習)](https://zerojudge.tw/ShowProblem?problemid=g276)