先放題目(應該是洛谷裏的樣例題目吧,侵刪~ )
洛谷-P1048 採藥
這個範例emmmm 我們重新編個範例輸入,爲了方便思考,我們先直接考慮花費時間和價值相等時的情況,也就是拿了多少價值就花掉多少時間
22 5
1 1
5 5
6 6
9 9
20 20
拿這個新編造的範例舉例,問題也就是:
在時間22之內,要拿走下面的哪幾個數,才使拿走的數的總和儘量更大?
編號 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
數 | 1 | 5 | 6 | 9 | 20 |
如果說貪心拿大的數,第一步先拿第5個數20,肉眼可以看的出來前面4個數之和爲1+5+6+9=21,顯然比20大,所以WA
如果說貪心拿小的數。。。。隨便換一下樣例就掛了(比如把5換成1)
貪心是不可能貪心了,,,動態規劃就這幾行代碼,還是來看看怎麼動態規劃吧
嘗試拆解成子問題
比如對於第5個數20,我們只可能進行兩個操作:拿,或者不拿
如果選擇拿的話,問題就變成這樣:
在時間2內,拿走下面的哪幾個數,才使拿走的數的總和儘量更大?
編號 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
數 | 1 | 5 | 6 | 9 |
這個子問題解決後(等下會將怎麼解決這個子問題的),得出來的總和加上這一波的20,就是選擇拿的答案。
這裏子問題相比原問題有兩個改變:
- 因爲我們拿了20,也就花了20時間,時間就從原本的22變成了2
- 因爲我們的第5個數已經拿走了,所以現在只用考慮前4個數了
如果選擇不拿的話,問題就變成這樣:
在時間22內,拿走下面的哪幾個數,才使拿走的數的總和儘量更大?
編號 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
數 | 1 | 5 | 6 | 9 |
這個子問題解決後(這個子問題跟上面的差不多,就時間不一樣,等下也會來解決掉的),得出來的答案就是選擇不拿的答案。(都不拿了肯定當然就不加20了,相應的,這個子問題的時間也沒有減20)
於是可以發現,無論這裏的20選擇拿還是不拿,問題都能變成一個關於前4個數的子問題,只是限制的時間不太一樣
最後通過解決這個子問題,得到最終選擇拿的答案(拿的子問題總和加上這波拿的20)和不拿的答案(不拿的子問題總和),比較這兩個答案誰更大,就能得到這一波該選擇拿還是不拿
我們希望通過關於前4個數的子問題來決定第5個數是拿還是不拿,但子問題中,限制的時間是由第5個數字拿不拿決定的。。。。於是喜聞樂見地套娃了
所以我們把關於前4個數的子問題變成這樣:
在時間 t (t ∈ [1, n], n = 22) 內,拿走下面的哪幾個數,才使拿走的數的總和儘量更大?
編號 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
數 | 1 | 5 | 6 | 9 |
這裏t是變量,範圍是1到22(22是範例裏給的時間來着,無論咋樣t最多肯定只有22了,也就是全部選不拿的情況),也就是把時間爲1到22時都求一遍,這樣第5個數無論是多少,都能取到拿或者不拿的結果(比如第5個數是範例中的20,那麼對於拿的子問題,就有t=22-20=2,對於不拿的子問題,就有t=22,和上面推出來的一樣,然後無論是2還是22都在t的範圍內,也就都已經算好值等着拿了),也就是說,解出了這個前4個數的子問題的解,就必能解出前5個數的問題的解
同理的,對於前4個數的問題,也可以換成前3個數的子問題;前3個數的問題,也可以換成前2個數的子問題······最後,換成了前1個數的子問題,那就能直接算了(t 小於這個數時不夠時間必取不了,總和就是0,t 不小於這個數時就只取這個數,因爲只有一個數不取也沒別的了,總和就等於這個數)
這時候我們求出了前1個數的問題的解,就能推前2個數的問題的解······最後,我們就能推出前5個問題的解,然後AC !
下面推下具體過程
先回顧下數據:
編號 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
數 | 1 | 5 | 6 | 9 | 20 |
前1個數的問題,對於每個 t 的答案如下
t | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
最大總和 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
第1個數是1,t>=1時,就都直接刷成了1
前2個數的問題,對於每個 t 的答案如下
t | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
最大總和 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
第2個數是2,由前1個數的問題得到的解可以推出這份解。比如t=3時, 設f(i, j) 爲前 i 個問題中 t 爲 j 時的解,選擇拿的答案是f(1, 1)+2=1+2=3,而選擇不拿的答案爲f(1, 3)=1,比較後決定在t=3時,要選擇拿的操作,且最大總和爲3。
······
實在太多,算了算了,最後引用大佬解釋的兩個概念和判斷一個問題能否使用DP解決
【無後效性】
一旦f(n)確定,“我們如何湊出f(n)”就再也用不着了。
要求出f(15),只需要知道f(14),f(10),f(4)的值,而f(14),f(10),f(4)是如何算出來的,對之後的問題沒有影響。
“未來與過去無關”,這就是無後效性。
(嚴格定義:如果給定某一階段的狀態,則在這一階段以後過程的發展不受這階段以前各段狀態的影響。)
【最優子結構】
回顧我們對f(n)的定義:我們記“湊出n所需的最少鈔票數量”爲f(n).
f(n)的定義就已經蘊含了“最優”。利用w=14,10,4的最優解,我們即可算出w=15的最優解。
大問題的最優解可以由小問題的最優解推出,這個性質叫做“最優子結構性質”。
引入這兩個概念之後,我們如何判斷一個問題能否使用DP解決呢?
能將大問題拆成幾個小問題,且滿足無後效性、最優子結構性質。
作者:阮行止
鏈接:https://www.zhihu.com/question/23995189/answer/613096905
最後附上遞推代碼
#include <cstdio>
#include <algorithm>
using namespace std;
int main(int argc, char const *argv[])
{
int t, m, times[128], values[128], bags[1024] = { 0 };
int i, j, k;
scanf("%d%d", &t, &m);
for (i = 0; i < m; i++)
{
scanf("%d%d", ×[i], &values[i]);
}
for (i = 0; i < m; i++)
{
// 這裏循環時要從後往前,每一次都要用上一輪的結果
// 如果從前往後用的時候就變成這一輪的結果了
for (j = t; j >= times[i]; j--)
{
// 這一輪的結果等於選擇不拿(bags[j])或者選擇拿(values[i] + bags[j - times[i]])
// 各得到的結果的最大值
bags[j] = max(bags[j], values[i] + bags[j - times[i]]);
}
}
printf("%d", bags[t]);
return 0;
}
拋磚引玉,大牛繞道~
如有錯誤,煩請指出~
參考文章