動態規劃_購物單問題(01揹包問題)

        王強今天很開心,公司發給N元的年終獎。王強決定把年終獎用於購物,他把想買的物品分爲兩類:主件與附件,附件是從屬於某個主件的,下表就是一些主件與附件的例子:

 

主件 附件
電腦 打印機,掃描儀
書櫃 圖書
書桌 檯燈,文具
工作椅

        如果要買歸類爲附件的物品,必須先買該附件所屬的主件。每個主件可以有 0 個、 1 個或 2 個附件。附件不再有從屬於自己的附件。王強想買的東西很多,爲了不超出預算,他把每件物品規定了一個重要度,分爲 5 等:用整數 1 5 表示,第 5 等最重要。他還從因特網上查到了每件物品的價格(都是 10 元的整數倍)。他希望在不超過 N 元(可以等於 N 元)的前提下,使每件物品的價格與重要度的乘積的總和最大。

        設第 j 件物品的價格爲 v[j] ,重要度爲 w[j] ,共選中了 k 件物品,編號依次爲 j 1 , j 2 ,……, j k ,則所求的總和爲:

v[j 1 ]*w[j 1 ]+v[j 2 ]*w[j 2 ]+ … +v[j k ]*w[j k ] 。(其中 * 爲乘號)

        請你幫助王強設計一個滿足要求的購物單。

 

解決這個問題之前,先看一個更通用的01通用揹包問題模型。

問題描述

有n個物品,它們有各自的體積和價值,現有給定容量的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?

爲方便講解和理解,下面講述的例子均先用具體的數字代入,即:eg:number=4,capacity=8

總體思路

根據動態規劃解題步驟(問題抽象化、建立模型、尋找約束條件、判斷是否滿足最優性原理、找大問題與小問題的遞推關係式、填表、尋找解組成)找出01揹包問題的最優解以及解組成,然後編寫代碼實現。

動態規劃的原理

動態規劃與分治法類似,都是把大問題拆分成小問題,通過尋找大問題與小問題的遞推關係,解決一個個小問題,最終達到解決原問題的效果。但不同的是,分治法在子問題和子子問題等上被重複計算了很多次,而動態規劃則具有記憶性,通過填寫表把所有已經解決的子問題答案紀錄下來,在新問題裏需要用到的子問題可以直接提取,避免了重複計算,從而節約了時間,所以在問題滿足最優性原理之後,用動態規劃解決問題的核心就在於填表,表填寫完畢,最優解也就找到。

最優性原理是動態規劃的基礎,最優性原理是指“多階段決策過程的最優決策序列具有這樣的性質:不論初始狀態和初始決策如何,對於前面決策所造成的某一狀態而言,其後各階段的決策序列必須構成最優策略”。

揹包問題的解決過程

在解決問題之前,爲描述方便,首先定義一些變量:Vi表示第 i 個物品的價值,Wi表示第 i 個物品的體積,定義V(i,j):當前揹包容量 j,前 i 個物品最佳組合對應的價值,同時揹包問題抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 個物品選或不選)。

1、建立模型,即求max(V1X1+V2X2+…+VnXn);

2、尋找約束條件,W1X1+W2X2+…+WnXn<capacity;

3、尋找遞推關係式,面對當前商品有兩種可能性:

包的容量比該商品體積小,裝不下,此時的價值與前i-1個的價值是一樣的,即V(i,j)=V(i-1,j);
    還有足夠的容量可以裝該商品,但裝了也不一定達到當前最優價值,所以在裝與不裝之間選擇最優的一個,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
其中V(i-1,j)表示不裝,V(i-1,j-w(i))+v(i) 表示裝了第i個商品,揹包容量減少w(i),但價值增加了v(i);

由此可以得出遞推關係式:

j<w(i)      V(i,j)=V(i-1,j)
j>=w(i)     V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

4、填表,首先初始化邊界條件,V(0,j)=V(i,0)=0;

然後一行一行的填表:

如,i=1,j=1,w(1)=2,v(1)=3,有j<w(1),故V(1,1)=V(1-1,1)=0;
    又如i=1,j=2,w(1)=2,v(1)=3,有j=w(1),故V(1,2)=max{ V(1-1,2),V(1-1,2-w(1))+v(1) }=max{0,0+3}=3;
    如此下去,填到最後一個,i=4,j=8,w(4)=5,v(4)=6,有j>w(4),故V(4,8)=max{ V(4-1,8),V(4-1,8-w(4))+v(4) }=max{9,4+6}=10……
所以填完表如下圖:

5、表格填完,最優解即是V(number,capacity)=V(4,8)=10。

 

 

購物單問題:

#include <stdio.h>
   
int max(int m, int n)
{
    return m > n ? m:n;
}
  
int main()
{
    int N, m, i, j;
    while(scanf("%d %d", &N, &m) != EOF)
    {
        int v[60] = {0};
        int w[60] = {0};
        int q[60] = {0};
        int sum[3200] = {0};
        for(i=1; i<=m; i++)
        {
            scanf("%d %d %d", &v[i], &w[i], &q[i]);
        }
        for(i=1; i<=m; i++)
        {
            for(j=N/10; j>=1; j--)
            {
                if((v[i]+v[q[i]])/10 <= j)
                    sum[j] = max(sum[j-v[i]/10]+v[i]*w[i], sum[j]);
                else
                    sum[j] = 0;
            }
        }
        printf("%d\n", sum[N/10]);
    }
    return 0;
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章