王強今天很開心,公司發給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;
}