# Dynamic Programming | 動態規劃
By Aaron Lee | Apr 1
## 題目
- 假如你是一個超商店員,需要找錢給別人,並且要找最少的數量的硬幣
- 這個國家的貨幣有 [A] 種
- 如何找到使用最少貨幣的方法(只需要知道用了多少硬幣就好)
## 你知道的東西
- N : 需要找多少錢 (1 <= N <= 100)
- COIN_TYPE[] : 有什麼硬幣
## 範例題目
- 貨幣種類:
| 1 | 4 | 8 | 10 | 13 | 20 | 23 |
| --- | --- | --- | --- | --- | --- | --- |
- 要找 18 元
- 你會怎麼解這題?
---
## 一般人會怎麼想
- 從最後面找,最大的可以用的就使用
1. `23 >= 18` → 不使用
1. `20 >= 18` → 不使用
1. `13 <= 18` → 使用 並且改成找 `18 - 13 = 5`, 使用 `1` 個硬幣
1. `13 >= 5` → 不使用
1. `10 >= 5` → 不使用
1. `8 >= 5` → 不使用
1. `4 <= 5` → 使用 並且改成找 `5 - 4 = 1`, 使用 `2` 個硬幣
1. `4 >= 1` → 不使用
1. `1 >= 1` → 使用 並且已經找完 `1 - 1 = 0`, 使用 `3` 個硬幣
- 一樣的東西表格化版本
| Step | N (目前找的數) | COIN (硬幣, 從大找到小) | 判斷結果 | AMT (所需的硬幣數量) |
| ---- | ------------ | --------------------- | ------ | ----------------- |
| 初始 | 18 | 23 | -- | 0 |
| 1 | 18 | 23 | 不使用 | 0 |
| 2 | 18 | 20 | 不使用 | 0 |
| 3 | 18 | 13 | 使用,更新 `N = N - COIN = 5` | `AMT = AMT + 1 =` 1 |
| 4 | 5 | 13| 不使用 | 1 |
| 5 | 5 | 10| 不使用 | 1 |
| 6 | 5 | 4 | 使用,更新 `N = N - COIN = 1` | `AMT = AMT + 1 =` 2 |
| 7 | 5 | 4 | 使用,更新 `N = N - COIN = 1` | `AMT = AMT + 1 =` 2 |
| 8 | 5 | 4 | 不使用 | 2 |
| 9 | 5 | 1 | 使用,更新 `N = N - COIN = 0` → 結束 | `AMT = AMT + 1 =` 3 → 結束 |
### BUT, 這個想法是錯誤的 (而且這題的正解是 `2`,因為 `8 + 10 = 18` → 最少的硬幣組)
---
## 正確做法 (Dynamic Programming | 動態規劃)
- 新增一個 陣列 DP ,並且動態更新他(一直更新他)
- 黑人問號我知道 嘿嘿
- 解釋(表格就好了不然我打到死掉 QQ)
| 貨幣(從小到大):arrow_down: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|:--------------------------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **1** | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| **4** | 0 | 1 | 2 | 3 | 1 | 2 | 3 | 4 | 2 | 3 | 4 | 5 | 3 | 4 | 5 | 6 | 4 | 5 | 6 |
| **8** | 0 | 1 | 2 | 3 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 2 | 3 | 4 | 5 | 2 | 3 | 4 |
| **10** | 0 | 1 | 2 | 3 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 2 | 2 | 3 | 2 | 3 | 2 | 3 | 2 |
| **13** | 0 | 1 | 2 | 3 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 2 | 1 | 2 | 3 | 2 | 2 | 3 |
- 結束(bc 20 >= 18) :+1:
- 好你一定不知道我在寫什麼 :smirk:
### 解釋
- 第一個 column (:arrow_down:) 是代表正在比較的硬幣值
- 第一個 row (:arrow_right:) 是代表要找的錢(如果把全部都列出來會大幅減少運算的時間)→ 這個稱為檢索的值
- 中間每一個都是各值最少的貨幣值(可以使用上面的貨幣,但不能使用下面的)
- 數學證明這個做法會有最佳的答案
```
重複 需要找多少錢 遍
如果 (DP 的第 [檢索的值 - 正在比較的硬幣值] 的值 + 1 < DP 的第 [檢索的值] 的值) 就
DP 的第 [檢索的值] 的值 *改成* DP 的第 [檢索的值 - 正在比較的硬幣值] 的值 + 1
最少硬幣和為 DP 的第 [需要找多少錢] 的值
```
- Code form (C/C++):
```cpp=
for (int i = 0; i <= N; i++) // This just means repeat N times
if ((DP[i - COIN_TYPE] + 1) < DP[i])
DP[i] = DP[i - COIN_TYPE] + 1;
int ANS = DP[N];
```
---
## 參考資料
台中女中程式解題系統
```cpp=
/**********************************************************************************/
/* Problem: b028 "忙碌的超商店員" from 動態規劃-最少零錢數 */
/* Language: C++ */
/* Result: AC (3ms, 124KB) on ZeroJudge */
/* Author: aaronlee at 2021-03-22 22:26:10 */
/**********************************************************************************/
#include <stdio.h>
const int COIN_TYPE[6] = {1, 5, 10, 12, 16, 20};
int main() {
int N;
scanf("%d", &N);
int arr[N];
for (int i = 0; i <= N; i++)
arr[i] = i;
for (int i = 0; i < 6 && COIN_TYPE[i] < N; i++)
for (int j = COIN_TYPE[i]; j <= N; j++)
if ((arr[j - COIN_TYPE[i]] + 1) < arr[j])
arr[j] = arr[j - COIN_TYPE[i]] + 1;
printf("%d", arr[N]);
return 0;
}
```