<!-- 注意上面三行 --> # CPE 衝刺班 ### by AlanHacker --- ![image](https://hackmd.io/_uploads/rJCvkBDUa.png) ### [前測](https://forms.gle/XFaZV4ZyR3naBRs36) --- ![image](https://hackmd.io/_uploads/BJoOyHvU6.png) [儲幹報名(必填)](https://forms.gle/B2nkhx7DfJAmSP8e8) --- ![image](https://hackmd.io/_uploads/Bk4KJrDUa.png) [機房參訪](https://forms.office.com/r/JANueyiH7Q) --- ## Outline * 心態準備 * 一星題講解 --- ## 心態準備 ---- ### 學習資源 * [考古題 & 題庫](https://yuihuang.com/cpe/) * [SA 流 C++ 競程修練心法](https://hackmd.io/@sa072686/cp/%2F%40sa072686%2FBkTJ0imPB) ---- ### C++ or C? * 以解題而言當然 C++ * 演算法相關函式庫支援差很多 * 但以 CPE 畢業門檻而言 C 夠用 * 但提交程式一律建議丟 C++(反正兼容 ---- ### 解題能力階段 * 語法熟悉:能解無演算法的模擬題(約 1~2 個月) * 演算法初學:各種遞迴、暴力(約 1~2 個月) * 演算法中堅:圖論、貪心、分治、DP (約半年) * 演算法大師:競程大佬(依天賦而定) ---- ### 畢業門檻語法熟悉就穩了! ---- ### 語法? * 基本輸入輸出 * 條件判斷 * 迴圈 * 陣列模擬(一維、二維) * 字串處理 * 浮點數精度 * 基本遞迴套路 ---- ### 常見輸入輸出 -- C 語言 ```cpp= scanf("%lld%d%lf", &longlongNum, &intNum, &realNumber); printf("%lld%d%lf", longlongNum, intNum, realNumber); char c; c = getchar(); putchar(c); char s[10]; gets(s); puts(s); ``` ---- ### 多筆測資 ```cpp= int n; // 遇到多筆測資把解題過程與輸入拆開往往比較好理解 void solve() { // 解題過程 } int main() { while (scanf("%d", &n) != EOF) { solve(); } } ``` ---- ### CPE 小技巧 1 ![image](https://hackmd.io/_uploads/BkV51BD86.png) * 提交前先測人工公開測試資料 * 基本上過了就是對的 ---- ### CPE 小技巧 2 * 永遠不用擔心 overflow 啦! ```cpp= #define int long long signed main() { /* 解題流程 */ return 0; } ``` ---- ### CPE 小技巧 3 * 陣列開大沒關係!!只要在 $10^8$ 以下都很穩 * 除非有特殊需求,不然陣列都從 1 開始 index * 通常新手遇到 Runtime Error 無非就是: * overflow:整數超出範圍 * 陣列超出範圍:負號索引或超出範圍 * 除 0 錯誤 ---- ```cpp= int arr[100000000]; int arr2[10000][10000]; int main() { for (int i = 1; i <= n; ++i) { scanf("%d", &arr[i]); } return 0; } ``` ---- ### CPE 小技巧 4 * 暴力是很優雅的 * 看到感覺很數學的問題不要急著算數學 * 先試著暴力就對了!! ---- ### 範例 * 給你 $y(-10^{18}\le y\le10^{18})$ * 求 $2x^3+8x^2+x+212=y$ 中 x 的任一整數解 ---- ```cpp= #include <stdio.h> #define int long long signed main() { int y; scanf("%lld", &y); // 以暴力枚舉而言, 10^8 以下都是小 case 啦! for (int x = -1000000; x <= 1000000; ++x) { if (2 * x*x*x + 8 * x*x + x + 212 == y) { printf("%lld", x); return 0; } } puts("No Answer!"); } ``` ---- # 免責聲明 ## 以下技巧不是常規技巧 ### 真的走投無路再使用 ---- ## 如果只差幾筆測資就對了 ---- ### 外鄉人打法 1-1 ```cpp= int main() { int input; scanf("%d", &input); /* 解題過程 */ printf("%d", ans); } ``` * 輸出: ``` 3 1 3 2 // 錯了!答案是 4 1 ``` ---- ```cpp= int main() { int input; scanf("%d", &input); /* 解題過程 */ if (ans == 2) { puts("4"); return 0; } printf("%d", ans); } ``` ---- ### 外鄉人打法 1-2 ```cpp= int main() { int input; scanf("%d", &input); /* 解題過程 */ printf("%d", ans); } ``` * 輸入: ``` 3 1 3 // 這筆輸入錯了,假設正解是 8 ,程式卻輸出 11 2 1 ``` ---- ```cpp= int main() { int input; scanf("%d", &input); if (input == 3) { puts("8\n"); return 0; } /* 解題過程 */ printf("%d", ans); } ``` --- ## 一星題講解 --- ### [Uva 10189 Minesweeper](https://vjudge.net/problem/UVA-10189) ---- ### 劃重點 ![image](https://hackmd.io/_uploads/HydiyrwLa.png) ---- ### 思路(1) * 地雷不變(無論輸入輸出都是 `'*'`) * 其它變數字(周圍地雷數量) ---- ### 思路(2) * 用二維陣列模擬盤面 * 地雷用 -1 表示(因為不可能出現 -1) * 其他地方初始設為 0 * 剩下按照題目要求處理輸入輸出 ---- ### ~~這題根本難在輸入輸出~~ ```cpp= [4-5|8-19|21-28] #include <stdio.h> #include <string.h> int n, m; int board[105][105]; // 因為 (0 < n, m ≤ 100) 所以保險起見多抓一點 void solve() { memset(board, 0, sizeof(board)); // 記得初始化 getchar(); // 記得拿掉換行 // 從 1 開始存取?因為這樣就不用特別判斷邊界,等一下比較明顯 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { char c; c = getchar(); if (c == '*') board[i][j] = -1; else board[i][j] = 0; } getchar(); // 記得拿掉換行 } // 輸出結果 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (board[i][j] == -1) putchar('*'); else printf("%d", board[i][j]); } putchar('\n'); } } int main() { int firstTime = 1; // 紀錄是否為第一個測資 int x = 0; // 紀錄當前是第幾個測資 while (scanf("%d%d", &n, &m), n > 0 && m > 0) { // Uva 很愛叼換行,很 G8 if (firstTime == 0) { putchar('\n'); } firstTime = 0; printf("Field #%d:\n", ++x); solve(); } return 0; } ``` ---- ### 思路(3) * 每個非地雷格子都要變成周圍地雷的數量 * 暴力計算周圍所有格子地雷的數量! * 只要記得排除自己就好 * 地雷呢?跳過啦! ---- ### 計算周圍地雷 ```cpp= for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (board[i][j] == -1) continue; int count = 0; for (int dx = -1; dx <= 1; ++dx) { for (int dy = -1; dy <= 1; ++dy) { if (dx == 0 && dy == 0) continue; // 從 1 開始存取的好處 // 若從 0 開始,i + dx == -1 還要特別判斷 if (board[i + dx][j + dy] == -1) ++count; } } board[i][j] = count; } } ``` ---- ## [C 解法](https://gist.github.com/AW-AlanWu/cebb30921cdd37ff47ab71a36172d3a6) ## [Python 解法](https://gist.github.com/KevinYu0515/b96a1201425244995a87754b0cf0f81a) --- ### [Uva 10924 Prime Words](https://vjudge.net/problem/UVA-10924) ---- ### 劃重點 ![image](https://hackmd.io/_uploads/HJF2kBPLa.png) ---- ### 思路(1) * 其實沒什麼好講,只是把字串按照題意轉數字 * 再做質數判斷就好 ---- ### 思路(2) * 大小寫轉數字的部份可以善用內建函數 * `isupper` 、 `islower` 、 `tolower` 等 * 雖然這題沒影響,但記得 strlen 呼叫一次就好 ---- ```cpp= #include <stdio.h> #include <string.h> #include <ctype.h> char word[25]; int main() { while (scanf("%s", word) != EOF) { // strlen 拉出來,不要放在 for 迴圈 int n = strlen(word); int sum = 0; for (int i = 0; i < n; ++i) { // 小巧思,先一律轉小寫來處理 int value = tolower(word[i]) - 'a' + 1; // 再判斷大寫 if (isupper(word[i])) value += 26; sum += value; } if (isPrime(sum)) { puts("It is a prime word."); } else { puts("It is not a prime word."); } } return 0; } ``` ---- ### 思路(3) * 質數判斷的部份,只要找 $[2, \sqrt{n}]$ 範圍內是否有因數即可 * 因為對於 $\ge\sqrt{n}$ 的因數,必定存在另一個 $\le\sqrt{n}$ 的因數相乘等於 $n$ * 記得是**從 2 開始** ---- ```cpp= int isPrime(int num) { for (int i = 2; i * i <= num; ++i) { if (num % i == 0) return 0; } return 1; } ``` ---- ## [C 解法](https://gist.github.com/AW-AlanWu/f0ece6f1a2aabed67a1c8eac74d96d27) ## [Python 解法](https://gist.github.com/AW-AlanWu/d625bdeeae41f8d94edaff2306634459) --- ### [Uva 477 Points in Figures: Rectangles and Circles](https://vjudge.net/problem/UVA-477) ---- ### 劃重點(1) ![image](https://hackmd.io/_uploads/B15TyBDI6.png) ---- ### 劃重點(2) ![image](https://hackmd.io/_uploads/HkZRyHvI6.png) ---- ### 思路(1) * 這題概念很簡單 * 給你一堆長方形和圓形、一堆點座標 * 求每個點分別在幾個圖形裡面? * 難是難在是實作細節有點多 ---- ### 思路(2) * 遇到複雜的問題,先別寫 code * 先把問題拆分成不同子問題 * 怎麼存圖形、點座標? * 怎麼判斷點是否在圖形內? ---- ### 思路(3) -- 怎麼存圖形、點座標? * 用陣列?很難做!因為很難區分圓形和矩形 * 用 struct !自定義型別可以按需求客製化 * 所以我們需要圓形和矩形的 struct * 因為點座標很單純,所以用兩個變數存即可 ---- ```cpp= [5-17|19-41|43-50] #include <stdio.h> #include <stdlib.h> #include <math.h> struct Rect { int ord; double upper_left[2]; double lower_right[2]; } rect[15]; struct Circle { int ord; double center[2]; double rad; } circle[15]; int len_r = 0, len_c = 0; void input_figure() { int cnt = 0; char fig; while (scanf("%c", &fig), fig != '*') { if (fig == 'r') { scanf("%lf%lf%lf%lf", &rect[len_r].upper_left[0], &rect[len_r].upper_left[1], &rect[len_r].lower_right[0], &rect[len_r].lower_right[1]); rect[len_r].ord = ++cnt; ++len_r; } else { scanf("%lf%lf%lf", &circle[len_c].center[0], &circle[len_c].center[1], &circle[len_c].rad); circle[len_c].ord = ++cnt; ++len_c; } getchar(); } } int main() { input_figure(); double x, y; while (scanf("%lf%lf", &x, &y), x != 9999.9 || y != 9999.9) { /* 判斷點是否在圖形 */ } return 0; } ``` ---- ### 思路(3) -- 怎麼判斷點是否在圖形內? * 圓形:使用國中教的距離公式 $\sqrt{(x_1-x_0)^2+(y_1-y_0)^2}$ ,小於半徑就代表在圓內 * 矩形:如下圖判斷 ![image](https://hackmd.io/_uploads/SyeJlBvIp.png =45%x) ---- ```cpp= int inCircle(double x, double y, struct Circle c) { double x_bias = fabs(x - c.center[0]); double y_bias = fabs(y - c.center[1]); double dis = sqrtl(x_bias * x_bias + y_bias * y_bias); if (dis <= c.rad) return 1; else return 0; } int inRect(double x, double y, struct Rect r) { if (x < r.upper_left[0] || x > r.lower_right[0]) return 0; if (y < r.lower_right[1] || y > r.upper_left[1]) return 0; return 1; } ``` ---- ### 思路(4) * 題目要求對於每個點所在的圖形 * 要按照輸入順序輸出 * 要排序呀! ---- ```cpp= int cmp(const void *a, const void *b) { return *(int*)a - *(int*)b; } int main() { input_figure(); double x, y; int ans[15], point_num = 1; while (scanf("%lf%lf", &x, &y), x != 9999.9 || y != 9999.9) { int len_ans = 0; for (int i = 0; i < len_c; ++i) { if (inCircle(x, y, circle[i])) { ans[len_ans++] = circle[i].ord; } } for (int i = 0; i < len_r; ++i) { if (inRect(x, y, rect[i])) { ans[len_ans++] = rect[i].ord; } } qsort(ans, len_ans, sizeof(int), cmp); if (len_ans == 0) { printf("Point %d is not contained in any figure\n", point_num); } else { for (int i = 0; i < len_ans; ++i) { printf("Point %d is contained in figure %d\n", point_num, ans[i]); } } point_num++; } return 0; } ``` ---- ## [C 解法](https://gist.github.com/AW-AlanWu/ea63a42ebd9c406fe45c36a553f48600) ## [Python 解法](https://gist.github.com/KevinYu0515/51b0a3d0ff66e7d8fc5a76ba1d8a976e) --- ## Extra : C++ intro ---- * 萬用標頭檔 * 有了這個再也不用背一堆 `#include <XXX>` ```cpp= #include <bits/stdc++.h> ``` ---- * 排序比一比 * ~~qsort 什麼垃圾~~ ```cpp= int arr[100]; sort(arr, arr + 100); qsort(arr, 100, sizeof(int), cmp); ``` --- # 大家明天加油! --- ![image](https://hackmd.io/_uploads/SyEllSD8T.png) ### [後測](https://forms.gle/Wi95hsgChfFZhiCL7) --- ![image](https://hackmd.io/_uploads/BJixeBwI6.png) [儲幹報名(必填)](https://forms.gle/B2nkhx7DfJAmSP8e8) --- ![image](https://hackmd.io/_uploads/BkZbgBDUp.png) [機房參訪](https://forms.office.com/r/JANueyiH7Q)
{"title":"CPE 衝刺班","contributors":"[{\"id\":\"b700cab6-685f-4ba8-87b8-088044d9367d\",\"add\":31620,\"del\":21038}]"}
    904 views
   owned this note