---
tags: Algorithm
---
# DIY Problem 2 - CH15 Dynamic Programming
吳柏毅與傅朋達盛行,你因為作業太多寫不完懶得出去買午餐。因此決定午餐要叫外送解決。假設你所在的外送區域總共有 m 家商店,這 m 家商店總共販賣了 n 種商品(每家商品的產品皆不完全相同,且每家店都至少供應了一種商品)。而如果你在某家店購買了商品,你就需要付這家店的運費(每家店運費可能不同),而且不管你在這家店購買了多少的商品,運費都是一樣的(舉例來說,假設你在 A 店買了1 塊炸雞,需要付出 30 元的運費,那你在 A 店買了100塊炸雞,仍然只需要付 30 元的運費)。你現在手上只有 x 元,而且你想要全部花光,請寫出一種演算法,可以告訴他**如果把這 x 元全部花光,可以有幾種餐點組合**
### 輸入的資料有 :
1. `n` => 「商品的數量」
2. `m` => 「商店的數量」
3. `x` => 「目前有的錢」
4. `goods[n][2]` => 紀錄 n 種商品,存放的資料分別是 「此商品是由哪家店販賣的」(index = 1),以及 「此商品售價是多少」 (index = 2)
5. `store[m]` => 紀錄 m 家商店, 存放的資料是 「此商店所需的運費」
### 解題思路:
使用 Dynamic Programming,因為這個問題可以拆分成許多子問題,而且會有許多重複的子問題。
例如:
假設我總共有 500 元,目前我在 A 店購買了兩個 50 元的商品 1 (運費 20 元), 且在 B 店購買了三個 25 元 的商品 2 (運費 25 元), 因為解題是利用迴圈解決,因此接下來的選擇只會是商品 3 ~ n。 在另一次組合中, 我在 A 店購買了一個 50 元的商品 1 (運費一樣為 20 元), 且在 B 店購買了五個 25 元 的商品 2 (運費一樣為 25 元)。在這兩次目前的組合中,都已經選定好了商品 1 及 商品 2 的數量。而且剩餘的錢都是 280 元,那剩下要做的都會是 「以 280 元選取商品3 ~ n 的組合」,意即重複的子問題,因此,若利用Dynamic Programming ,就可以省去重複計算的困擾
### 先寫出遞迴過程中需要用到的資料結構與其意義:
1. `goods[n][3]` => 與原本輸入資料不同的是,多了第三維資料,其中存放的是 「目前的組合中有多少此商品」(index = 3)
2. `store[m][2]` => 與原本輸入資料不同的是,多了第二維資料,其中存放的是 「目前的組合中是否有選過此商店的商品」 (index = 2)
3. `table[n][x]` => DP 表格,紀錄「此子問題以往的最佳解」
5. `index` => 「目前看到第幾種商品」
6. `remain` => 「目前剩下多少錢」
7. `ans` => 「以目前的組合繼續選取商品下去,最後會有幾種組合滿足條件」
### Pseudo code
```C++=
Buy(n, m, index, remain , store[m][2] , goods[n][3])
ans = 0
// 如果剩下的錢是 0 元,代表已經成功把原本的 x 元全部花完,因此組合多了一種
if (remain == 0){
return 1
}
// 如果已經看完了全部 n 種商品,卻還有剩錢,表示這種組合並不符合條件
elif (index >= n){
return 0
}
// 如果已經計算過剩下組合可能的答案,就不用再計算一遍了(Dynamic Programming精神)
if (table[index][remain] != -1){
return table[index][remain]
}
// 如果目前的組合已經有這家商店的商品,就不用再付一次運費了
elif (store[goods[index][1](哪家店)][2] == False){
ans += Buy(index + 1, store[m][2],remain)
store[goods[index][1](哪家店)][2] = True
for (int i = 1; remain-store[goods[index][1](哪家店)][1] - goods[index][2]*i>= 0; i ++)
ans += Buy(index + 1, store[m][2],remain - i * goods[index][2] )
}
// 如果目前的組合沒有這家商店的商品,就需要付運費
else{
for (int i = 0 ; remain - i * goods[index][2] * i >= 0; i ++ )
ans += Buy(index + 1, store[m][2], remain - i * goods[index][2] * i)
}
//紀錄下來剛剛算過的子問題會有多少答案
table[index][remain] = ans
return ans,
```
```C++=
main (n, m, x,goods[n][2],store[m])
// 預處理
fill table[n][x] with -1 // 一開始沒有算過任何組合,先將 DP 陣列先初始化為未啟動狀態
for i = 1 to n: // 每個商品一開始購買的數量都是0
goods[n].append(0)
for i = 1 to m : // 一開始沒有在任何一家店有想要買的商品
store[m].append(False)
ans = Buy (n, m, 0,x,store,goods) // 從第一種商品開始嘗試,且一開始有 x 元
print("You have", ans, " kinds of combination")
```