---
# System prepended metadata

title: 題解：鳳梨組
tags: [Academic]

---

---
tags: Academic
---

# 題解：鳳梨組

[Final Scoreboard](https://i.imgur.com/SfqZGBJ.jpg)

## 道歉啟示

對於 房地產投資客 和 K進制轉換 兩題的題目中出現的錯誤，我們均是在比賽進行中和題解撰寫時才發現。

對於這些錯誤，我們會銘記在心，並且會在下一屆比賽時特別注意。

## A. 金錢規劃

題目：教授
題解：曾俊宏

照著題目做即可!

先讀取總測資數後，對於每一筆測資讀兩數字整數(分別是收入 $I$ 與支出筆數 $N$)，之後再讀取 $N$ 個數字並且從 $I$ 中扣除即可!

[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E9%87%91%E9%8C%A2%E8%A6%8F%E5%8A%83/amos.py)
[C++ by 郭晏誌](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E9%87%91%E9%8C%A2%E8%A6%8F%E5%8A%83/macaca.cpp)

## B. 房地產投資客

題目：教授
題解：黃鈺程

非常抱歉，這題題目有一點小瑕疪，我們現在寫題解時才注意到：題目關於「投入金額、房貸利率、操作次數」的型態，輸入格式、技術規格、範測是不太一致的。我們測試的測資裡使用的是技術規格中所說的 float, float, int。這可能造成部份隊伍只使用範測測試是找不出這種情況的。

另外這題在 judge 時有開放浮點數誤差，只要你的結果與答案誤差在 0.1 以下即可。比賽中會 rejudge 來自於 pc^2 會錯誤估計程式執行時間，他把他判斷是否在誤差內的時間也算進去，而他判斷的時間莫名的長……。至於有些隊得到的 Incomplete Output，是因為在 pc^2 的誤差處理機制有瑕疵。如果最後一筆測資在輸出答案的時候並沒有輸出換行，會造成比對錯誤。

另外比賽中有許多隊沒看到要輸出結果至**小數點後二位**，造成無法 AC。

Python(3.6) 的寫法是 `print(f'{ans:.2f})'`
C 的寫法是 `printf(“%.2f“, ans);`
C++ 的寫法是 `cout << fixed << setprecision(2) << ans << endl;`

[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%88%BF%E5%9C%B0%E7%94%A2%E6%8A%95%E8%B3%87%E5%AE%A2/amos.py)
[C++ by 陳煒杰](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%88%BF%E5%9C%B0%E7%94%A2%E6%8A%95%E8%B3%87%E5%AE%A2/andy.cpp)

## C. 車用發電機電壓轉譯

題目：教授
題解：黃鈺程

Hint 中給的應該足夠明顯。假設每個測資有字串 `L`, `H`, `Q`，那所求即為找出 `L` 與 `Q` 不同的那連續 7 個位置，然後將 `Q` 中對應位置的子字串轉為 10 進位。實作就一個 for 掃一下，看是從第幾個位置開始，`L` 與 `Q` 是不同的。

[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%BB%8A%E7%94%A8%E7%99%BC%E9%9B%BB%E6%A9%9F%E9%9B%BB%E5%A3%93%E8%BD%89%E8%AD%AF/amos.py)
[C++ by 陳煒杰](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%BB%8A%E7%94%A8%E7%99%BC%E9%9B%BB%E6%A9%9F%E9%9B%BB%E5%A3%93%E8%BD%89%E8%AD%AF/andy.cpp)


## D. 單字背誦

題目：黃鈺程、郭晏誌
題解：黃鈺程

這題是經典的一筆畫問題。用圖論的語言來講，是歐拉環（Euler Circuit）。每筆測資可以轉換成一個有向圖 $G$。每個單字是圖中的邊，圖的節點是字母，例如 `[electricity, yarn, negotiate, abandon, ninja]` 與 `[xy, yx, ab, ba]` 分別建成圖：

![](https://i.imgur.com/TRcUdrC.png)

題目所求就是在問 $G$ 是不是一個或多個歐拉環所組成，而這可以藉由檢查圖中是不是每個節點的入邊數（in degree）等於出邊數（out degree）。這個 **充要關係** 可在各離散課本找到，因此不在此說明。題外話，如果這題改成問給定的單字可不可以組成 **僅一個** 歐拉環，那就得先判這個圖中所有 degree ≠ 0 的點是連通的喔。

不過如果不知道歐拉環，這題也是某種程度可以解出來的：

1. 每個單字只有字首與字尾的字母有意義，中間有什麼字母不影響結果。
2. 如果一筆測資是 Lucky，則對於任意字母，它「出現在單字字首的次數」要等於「出現在單字字尾的字數」。
 
這可以用反證法證明：假設這些單字是 Lucky，且字母 c 出現在字尾的次數比出在字首的次數多一，則會有一個字尾為 c 的單字找不到下一個單子繼續接，因此不是 Lucky。

至此，你就可以 **猜說** 是不是統計一下各字母的情況，來判定是 Lucky 還是 QAQ。這剛好會是對的，不過不正確，因為並沒有在邏輯中證明這是「若且唯若」的關係。上述只證明了 => 的關係，並沒有 <=，也就是說如果統計的結果成立，那這筆測資就一定是 Lucky 嗎？（答：是的，性質由歐拉環的證明保證）。

我不知道解出這題的選手們是不是都有注意到這個，不過就算沒有，既然是比賽，證明不嚴謹是可以接受的啦~有想法比較重要。這題被許多應該沒聽過歐拉環的人（非資工系的選手與資工大一）解出來，讓我非常驚訝！

這題實作非常簡單，統計一下即可，利用英文字母的 ASCII Code 是連續的，可以直接開陣列算一下各字母的次數。

[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%96%AE%E5%AD%97%E8%83%8C%E8%AA%A6/solution.py)
[C++ by 郭晏誌](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%96%AE%E5%AD%97%E8%83%8C%E8%AA%A6/macaca.cpp)

## E. 寶藏獵人

題目：李晨維
題解1：李晨維

此題是一題簡單 DP 題，運用的是高中排列組合的加法原理，對於每一個位置，我們去看他的下方以及左方的位置是否為陷阱，如果否就把走到那個位置的方法數加到目前在算的位置上，所以 DP 更新式子如下:
```
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]    if dp[i - 1][j] and dp[i][j - 1] are not traps
dp[i][j] = dp[i - 1][j]                   if dp[i][j - 1] is a trap
dp[i][j] = dp[i][j - 1]                   if dp[i - 1][j] is a trap
```
另外，此題有兩個地方須注意:

1. dp 表必須開 long long int，因為 `m` , `n` 最大到 30 時 int 會 overflow (思考一下，完全沒陷阱時，路徑數為 $\frac{60!}{30!30!}$，這個數字有多大呢？)。
2. 如果做法是一開始將邊界初始化為 1，須小心邊界是否有陷阱，如果有的話除了起點到陷阱的部分初始化為 1 之外，其餘部分需初始化為 0 或是直接標記成陷阱。

----

題解2：黃鈺程

你可以將題目的地圖與 dp 表格分開存，也許會比較好理解。假設題目給的地圖是 `A`（0-based index），則 dp 寫成：

```
dp[r][c] = 從起點走到位置 (r, c) 的方法數

dp[0][0] = 0 if A[0][0] is trap else 1
dp[r][0] = 0 if A[r][0] is trap else dp[r-1][0]
dp[0][c] = 0 if A[0][c] is trap else dp[0][c-1]
dp[r][c] = 0 if A[r][c] is trap else dp[r-1][c] + dp[r][c-1]
```

顯而易見的，在都沒有陷阱的情況下，走到 `(r, c)` 方法數即為走到 `(r-1, c)` 的方法數加上走到 `(r, c-1)` 的方法數。

[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%AF%B6%E8%97%8F%E7%8D%B5%E4%BA%BA/amos.py)
[C++ by 李晨維](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%AF%B6%E8%97%8F%E7%8D%B5%E4%BA%BA/solution.cpp)

## F. 最後衝刺

題目：教授
題解：曾俊宏、郭晏誌

關鍵點是 sorting 針對時間區間的右端點排序，如果右端點相同時再對左端點排序。dp 轉移方程式定義為 $dp[ i ]$ 表示使用前 $i - 1$ 個科目且必使用第 $i$ 個科目時所能達到的最大分數提升。

初始值設定為 $dp[ i ] = inp[ i ].score$ (第 $i$ 個科目的分數)

之後迴圈迭代 $j < i$ 時先將 $dp[ i ]$ 值取出($cur\_score$) 從後面往前看如果第 $j$ 個科目的時間沒有與 第 $i$ 個科目重疊則 $dp[ i ] = max (dp[ i ] , cur\_score + dp[ j ] )$

**注意**: 可能有起始時間 = 結束時間的 case，所以說，如果沒有對右端點相同時進行左端點排序的話，下面這筆測資會炸開。

Sample Input
```
1
4
1 3 10
8 8 1
5 8 5
6 8 5
```

Sample Output
```
16
```


[Python by 黃鈺程](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%9C%80%E5%BE%8C%E8%A1%9D%E5%88%BA/amos.py)
[C++ by 李晨維](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%9C%80%E5%BE%8C%E8%A1%9D%E5%88%BA/tom.cpp)
[C++ by 陳煒杰](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%9C%80%E5%BE%8C%E8%A1%9D%E5%88%BA/andy.cpp)
[C++ by 曾俊宏](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E6%9C%80%E5%BE%8C%E8%A1%9D%E5%88%BA/henry.cpp)

## G. 超級傳情

題目：教授
題解：黃鈺程

題目稍為難讀懂，不過從範測推一下，應該可以了解。這題有 2 種做法，時間分別是 $O(N*E)$ 與 $O(N^3)$，$N$ 代表圖的節點數而 $E$ 是邊數。因為這題的 $N$ 不大，所以兩種解法都會 AC。

首先 $O(N*E)$ 的解法來自一個簡單的想法：針對每個人，我去算他能傳情給多少人，假設第 `u` 個人能傳情給 `cnt[u]` 的人，那題目所求就是 `sum(cnt)`。而我們可以利用圖論來求出某一個人能傳情給多少人：將傳情當作有向圖的邊，人當作有向圖的點，那對於點 `u` 來說，它能傳情的數量就是從 `u` 出發，能走到的點的數量再減一（去掉自己）。這可以用一個簡單的 dfs 來完成，dfs 本身的時間為 $O(N+E)$。因此，對每個點 dfs 算 `cnt`，最後加總，整體時間為 $O(N * (N + E) + N) = O(NE)$。如果是完全圖的話，會是 $O(N^3)$

$O(N^3)$ 的解法改自 Floyd Warshall，dp 寫成：
```
dp[k][u][v] = 只使用前 k + 1 個點的情況下，u 是否可以傳情給 v。
```
我們特設一開始自己是可以傳情給自己，另外再加上題目給定的傳情，我們有以下初使情況：
```
dp[0][:][:] = False
dp[0][u][u] = True
dp[0][u][v] = True 對於所有題目給定的 (u, v)
```
彷造 Floyd Warshall 的轉移方程：
```
dp[k][u][v] = dp[k - 1][u][v] || (dp[k - 1][u][k] && dp[k - 1][k][v])
```
當我們建好這個 dp 後，我們枚舉任 2 個**相異點** `(u, v)`，查表看看 `u` 能不能傳情給 `v`，加總所有可以的情況，即為題目所求。

[Python by 黃鈺程, $O(NE)$](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%B6%85%E7%B4%9A%E5%82%B3%E6%83%85/amos.py)
[C++ by 曾俊宏, $O(N^3)$](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%B6%85%E7%B4%9A%E5%82%B3%E6%83%85/henry.cpp)

## H. 資工學霸

題目：教授
題解：

對於學霸的定義，$P[i] \geq P[t]$ 且 $M[i] \geq M[t]$  且 $M[i] + P[i] > M[t]+P[t]$，直接思考可能有點抽象。如果我們換個方式思考，以數學能力為 $x$ 軸，程式能力為 $y$ 軸進行作圖，我們可以觀察到如果一個點 $(x_i, y_i)$ 代表的是學霸的話，他的右上方區域就不會有其他點的存在！換句話說，大於 $x_i$ 且 大於 $y_i$ 的區域裡，是不會有其他點的！

實作的話，我們先對其中一種成績進行排序，之後對於該成績由大到小檢查回來即可。

[C++ by 李晨維](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%AD%B8%E9%9C%B8/tom.cpp)
[C++ by 陳煒杰](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%AD%B8%E9%9C%B8/andy.cpp)
[C++ by 曾俊宏](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E5%AD%B8%E9%9C%B8/henry.cpp)


## I. 蟲洞

題目：黃鈺程
題解：黃鈺程

這題是倍增法再稍為轉個彎。首先，我們換個思路，題目原先問建築物 0 之前 K 步可能在哪，我們改成枚舉每個建築物，看看這個建築物 K 步後是不是在建築物 0，而找出每個建築物 K 步後在哪，可以透過倍增法預建表，將查詢時間降到 $O(lgK)$。

```
dp[i][u] = 從建築物 u 出發，走 2^i 步後在哪
dp[0][u] = p
```

```
dp[i][u] = dp[i - 1][dp[i - 1][u]]
```

建築物 `u` 走 `2^i` 步後的位置，即為走 `2^(i-1)` 後的位置，再走 `2^(i-1)` 步。例如 8 步後的位置，即為走 4 步再走 4 步。當我們把這個 dp 表建好，我可以透過對 K 二進制分解，來查詢 K 步後的位置。例如我想知道從建築物 3 走 21 步後的位置，因為 `21 = 16 + 4 + 1 = 2^4 + 2^2 + 2^0`，所以答案是 `dp[4][dp[2][dp[0][3]]]`，即為從建築物 3 走 `2^0` 步再走 `2^2` 步再走 `2^4` 步。

預建表的時間為 $O(lgK * N)$，查詢時間為 $O(N * lgK)$，所以整體時間為 $O(NlgK)$。實作上，因為 N 仍然不小，使用 Python 會 TLE，得使用 C++ 或 Java 這題才會 AC。

[Python (by 黃鈺程，TLE)](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%9F%B2%E6%B4%9E/solution.py)
[C++ (by 黃鈺程，AC)](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%9F%B2%E6%B4%9E/solution.cpp)
[C++ by 陳煒杰](https://github.com/henrybear327/2018-CCU-Pineapple-Contest/blob/master/%E8%9F%B2%E6%B4%9E/andy.cpp)