題目 : UVa Link
有 N 種商品,每個價值為 Vi、喜愛程度為 Di,求金額不超過 M 的話,Dot 小姐可以獲得的最大喜愛點數總合。
此外,若實際購買金額超過 ( exeed ) 2000 的話,可以多獲得 200 的回饋
也就是若是購買了 5000 元,則就可以購買總額 5200 元。
測資(ㄧ)
Input :
500 4
100 2
100 3
200 3
400 4
Output :
8
測資(二)
Input :
1801 3
2000 3
1900 1
101 1
1801 3
2001 3
1900 1
101 1
Output :
2
3
核心思想
觀察一下,可以發現這題是 01 背包問題再加上一點變形。
我們來一步一步慢慢思考。
這題由題意來看,並不需要剛剛好把錢花完,因此在初始化紀錄動態規劃過程的陣列 val 時可以全部初始化為 0
代表無論最高花費金額為多少,0 點喜愛點數都是合法的。計算完後,解答就是 val[M]
反之,若條件是一定要剛好把錢花完,則要初始化為 -INFINITE (不合法狀態),只有 val[0] = 0
因為只有 Value = 0 時是合法的,可以剛好取得 nothing,其他的 Value 皆為不合法
轉換方程式
從前 i 個商品中,總金額不超過 j 元的大最喜愛點數
val[i][j] = max(val[i-1][j], val[i-1][j-P[i]] + D[i])
或是縮減版的val[j] = max(val[j], val[j - P[i]] + D[i])
解法引導
若不考慮可以額外加 200 元的這個 bonus,當最高花費金額為 M 時,最後答案就是 val[M]
再將此 bonus 加入考慮時,我們就要來探討一下尷尬的邊界範圍,也就是在 2000 附近
- M <= 2000 且 M + 200 <= 2000
這種情況下因為不會觸碰到特殊規則,所以就是做完 01 背包問題後,解答為 val[M] - M > 2000
這種情況下最後的解答為 val[M + 200]
因為若是最後購買的總合沒有用到 bonus ( 假設為 s 元且 s <= 2000 ),那麼解答 val[M + 200] = val[M] = val[s]
若最後的購買總額 b > 2000,那麼 val[M + 200] = val[b] M <= 2000 且 M + 200 > 2000
這種情況下整體的最佳解會落在以下 3 個區域- A 區域 : 不會碰到特殊規則,答案為 val[M]
- B 區域 : 不合法地帶,因為購買金額超過了 M,但又沒有到達 2000,因此答案是 val[M]
- C 區域 : 答案為 val[M + 200]
那麼要如何知道最佳解會落在那裡呢?
根據我們剛剛的前提,並不需要把錢都剛好花完
所以假設 val[i] 剛好更新為在花費不超過 i 元的情況下所能得到的最大喜愛點數 Di
且下一個更新點為 val[k] = Dk ( k > i )
因此 val[i ~ k-1] 就皆會等於 Di,且 val[i] != val[i-1] 和 val[i] != val[k]
所以我們可以從 M+200 開始往回掃描,分析當 val[i] != val[i - 1] 時- i >= 2001 : 最佳解在 C 區,答案為 val[M+200]
- i <= 2000 且 i > M : 最佳解在 B 區,答案為 val[M]
- i <= M : 最佳解在 A 區,答案為 val[M]
乍看之下,本題似乎已經解出來了呀 ~~ 收工!!
但不知道各位有沒有注意到,第三種情況的答案會出問題!!!
Why????
原因在於 : 若最佳解落在 B 區域,則會連帶地影響到 C 區域的最佳解
舉例來說,若實際上不含 B 區域的最佳解在 C 區域且為 20 ( 下圖 )
但是若又在 B 區域找更好的解答 ( 但實際上他是不合法的 ),他卻會把後面的真正解答蓋掉 ( 下圖 )
根據我們剛剛的演算法,當我們判斷出最佳解是落在 B 區域時
除非我們要在計算過程中去紀錄資訊,否則我們會沒辦法找回真正的解答
因此,我們需要修正我們的演算法
正確解法
可以發現,剛剛的問題是出在我們需要找的真正解答會被覆蓋掉
那麼我們就要想辦法去保留住他
一個很簡單的改變方式就是我們把我們剛剛的前提改一下
改成我們必須把錢剛好花完,因此在初始化 val 陣列時,只有 val[0] = 0,其餘皆為 -INFINITE ( 代表不合法 )
因此轉換方程式就要改為 :
若 val[j-P[i]] 有合法的最佳解,則選出最大的
if(val[j - P[i]] != -INFINITE) --> val[j] = max(val[j], val[j - P[i]] + D[i])
並同樣來分析一下 3 種情況
- M <= 2000 且 M + 200 <= 2000
這種情況下因為不會觸碰到特殊規則,解答為 max( val[0] ~ val[M] ) - M > 2000
解答為 max( val[0] ~ val[M + 200] ) - M <= 2000 且 M + 200 > 2000
這種情況下整體的最佳解就是從 A 和 C 區域中去挑選
改良過後,每一個合法解都會被保留住,最後只需要判斷出範圍並找出最大值就好
程式碼
1 |
|
UVa 真實測資
以下我偷渡出來的 UVa 測資和我的程式 run 的解答,請低調享用