# Ch2 解題指引
## 找出最小的完全平方數
### 解題思路
以下想法有其中兩種是可行的,一種會超時(TLE),猜猜看是哪一個!
#### ==想法一==
- 使用`for`迴圈,從 $k$ 位數的第一個數,檢查到 $k$ 位數的最後一個數。
- 每檢查到一個數,就看看他是否符合「各位數字皆為偶數」且「為完全平方數」。若同時滿足兩個條件,即為所求。
#### ==想法二==
- 設 $m$ 為起始數字,由 $m = 2$ 開始,檢查 $m^2$ 是不是 $k$ 位數,又是否符合「各位數字皆為偶數」這個條件。
- 每次`m += 2`,若 $m^2$ 為第一個符合條件的 $k$ 位數字,則記錄下來,此即所求。
#### ==想法三==
- 找出 $k$ 位數中最小的偶數 $e$。
- 由 $\lceil{\sqrt{e}}\rceil$ 開始,一個一個數字檢查,檢查其平方數是否符合「各位數字皆為偶數」,符合的話即為所求。
- 另外可使用加速條件:偶數的平方數尾數必為0, 4, 6。
### 引導問題
:::spoiler **Q. 是哪個想法會超時呢?**
A. 答案是想法一!因為`a、b`之間的數字,在位數`k`較大的時候,檢查了太多不必要的數字。
已知位數限制為 $0 < k \leq 10$,在最極端的狀況:$k=10$,需要檢查的數字個數,量級到了 $10^9$ ~ $10^{10}$ 之間,顯然會超過時間限制。
相較之下,想法二、三只檢查了「完全平方數」,平方後不超過 10 位數的數字,最大不超過 $10^5$。
:::
:::spoiler **Q. 如何判斷一個數是否為 k 位數?**
A. 有兩種方法。
- [法一] 使用`while`迴圈處理整數變數
宣告一個總和變數,只要除以 10 不為 0 就 +1。迴圈總共執行幾次,就是幾位數。
- [法二] `string`的`size()`
可以使用 C++ 11 中的`stoi`函式。先將一個數字轉換成字串(`string`),再用字串長度判斷位數。若你是使用code::blocks撰寫程式,需要手動調整使用的編譯器版本,具體作法可參考[這裏](https://stackoverflow.com/questions/18174988/how-can-i-add-c11-support-to-codeblocks-compiler)。
:::
::: spoiler **Q. 如何判斷一個數的各位數字都是偶數?**
A. 承上,使用迴圈檢查每一個位數,確認都能被 2 整除即可。具體如何實作呢?你可以宣告一個布林變數`is_all_even`,初始值為`true`,只要有一個位數的數字為奇數,就把`is_all_even`設為`false`,然後結束迴圈,不用再繼續檢查下去。
```cpp=
bool is_all_even = true; // 意義:各位數字是否都是偶數
while(...){ // 使用迴圈檢查每一位數字
if(...){ // 只要發現有一個位數是奇數
is_all_even = false;
break;
}
...
}
```
若要將其改寫為自訂函式,回傳資料型態可以設為布林值,代表「是否各位數字皆為偶數」:
```cpp=
bool is_all_even(int n){
while(...){ // 使用迴圈檢查每一位數字
if(...){ // 只要發現有一個位數是奇數
return false;
}
...
}
return true; // 前面都沒有return false,表示各位數字皆為偶數
}
```
:::
::: spoiler **Q. 我使用`int`來做題目,為什麼會拿到 WA(15%)?**
A. 因為題目有說到,最多為 10 位數字,`int`型態的數字無法容納所有 10 位數字,可能會出現 ==溢位(overflow)== 狀況,進而計算錯誤的結果。幸好數字也沒有太大,請嘗試使用長整數(`long long`)這個資料型別,其可容納的數字範圍可上網查詢。
:::
## 一起回家的日子
> 這題楷宗老師有特別說明最小公倍數的解法,請參考[函式實作示例](https://hackmd.io/@letscoding/ByzfuV0uA)。以下將延續課堂講解的解題邏輯進行說明。
### 解題思路
- ==宣告所需自訂函式==:最小公因數、最大公因數、閏年判斷。
- ==處理輸入==:作法與「計算時間差」這題相似,處理特定日期格式輸入。
- ==計算天數==:根據輸入的人數與天數,求最小公倍數,代表下次一起回家所需天數。
- ==推算日期==:扣除所需天數,跨月、跨年,直到下次一起回家的日期。
- ==處理輸出==:作法與「計算時間差」這題相似,輸出特定日期格式。
### 引導問題
::: spoiler **Q. 怎麼判斷一個數是否為閏年?**
A. 網路上有很多種解法,歡迎同學們去查查看,觀察每種解法的思路。最常看到的寫法為:
- 被 4 整除的數字,如果不是 100 的倍數,即為閏年。
- 如果是 400 的倍數,必為閏年。
以上兩式寫成邏輯運算式,`||`起來就是閏年判斷的條件式。
大部分人應該都學過閏年判斷,不妨嘗試寫成自訂函式,在主程式呼叫使用。
:::
::: spoiler **Q. 具體如何針對跨月、跨年,往前推進到一起回家的日子?**
A. 做法有非常多,順著加、倒著扣都有對應的寫法。而課堂範例的思路大概是這樣的:
- 先將現在的日期加上下次回家所需天數備用。
- 只要所需天數大於該月份天數,就扣掉這些天數,開心度過這個月。
- 到下一個月。注意若 12 月過完,要回到 1 月,並進入新的一年。
- 重複以上步驟。當可扣掉的天數未滿該月份總天數,該月份、剩餘天數即為一起回家的日子。
:::
::: spoiler **Q. 我該如何把閏年納入考量?**
A. 閏年的影響視在特定年份的 2 月多 1 天。基本判斷準則如下:
- 該年是否為閏年?
- 如果是閏年,剩餘天數加上去後,是否需要跨過該年的 2/29?
同時符合以上條件,才需要多加一天。
:::
:::spoiler **Q. 要一個一個月份判斷有幾天超級麻煩,有沒有簡化程式碼的方法?**
A. 宣告一個陣列,存放每個月的天數:
```cpp=
int month_day[] = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
```
另外,也可以宣告 13 個整數的陣列,在陣列最前面多放一個數字 0 ,如此一來,就可以接以月份為陣列索引值,取用每個月對應的天數。
:::
::: spoiler **Q. 不知道哪裡寫錯了,我的想法很對啊QQ 可以怎麼檢查呢?**
A. 建議出幾筆測資給自己測試。例如,我喜歡拿 1 個人、1 天後回家,來測試閏年/非閏年跨天、跨月、跨年的各種可能性:
```
1
1
2024/02/29
```
發想各種例外情況,避免無端被扣分,是賽後測評機制的重要能力。這題是很好的練習機會,大家試試看吧!
:::
## 賓果遊戲
(待補)