---
# System prepended metadata

title: CPE 衝刺班
tags: [112-1社課]

---

---
type: slide
title: CPE 衝刺班
---

<!-- 注意上面三行 -->

# 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)