算法 動態規劃(DP) 白話解釋樸素01揹包問題

先放題目(應該是洛谷裏的樣例題目吧,侵刪~ )
洛谷-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,就是選擇的答案。

這裏子問題相比原問題有兩個改變:

  1. 因爲我們拿了20,也就花了20時間,時間就從原本的22變成了2
  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", &times[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;
}

拋磚引玉,大牛繞道~
如有錯誤,煩請指出~

參考文章

  1. 知乎:什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?——阮行止的回答
  2. 知乎:什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?——王勐的回答
  3. 知乎:0-1揹包問題的動態規劃算法 ——Bat特白
  4. 入坑題 洛谷:P2392 kkksc03考前臨時抱佛腳
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章