背包问题的应用
?http://acm.hdu.edu.cn/showproblem.php?pid=2602??Bone Collector
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
這是一個最簡單的01背包問題。
#include <iostream> using namespace std;const int MAX = 10010; int f[MAX]; int V; //背包的體積void ZeroOnePack (int cost, int weight) {int v;for (v = V; v >= cost; v--)f[v] = f[v] > (f[v - cost] + weight) ? f[v] : (f[v - cost] + weight); }int value[1005],volume[1005];int main(void) {int t,num,i;scanf("%d",&t);while(t--){scanf("%d %d",&num,&V);for(i=0;i<=V;i++) //沒有要求把背包裝滿{f[i]=0;}for(i=1;i<=num;i++)scanf("%d",&value[i]);for(i=1;i<=num;i++)scanf("%d",&volume[i]);for(i=1;i<=num;i++)ZeroOnePack(volume[i], value[i]);printf("%d\n",f[V]);}return 0; }http://acm.zjut.edu.cn/ShowProblem.aspx?ShowID=1355??擎天柱
?Description:
話說月光家里有許多玩具,最近他又看上了DK新買的“擎天柱”,就想用自己的跟DK的換。每種玩具都有特定的價格,價格為整數。只有月光拿出的玩具的總價格與“擎天柱”的價格相等才能換得“擎天柱”。同時,月光還希望能用最少的玩具數換回“擎天柱”。請問,月光能順利得到夢寐以求的“擎天柱”嗎?
Input:
輸入數據包含多組;對于每組數據,第一行為一個正整數n(1 ≤n≤10); 表示月光手頭有n個玩具。接下來一行有n個正整數P1,P2,……,Pn(1 ≤ Pi ≤ 1000),Pi為第i個玩具的所對應的價格。最后一行為一個正整數m(1 ≤ m ≤10000),為“擎天柱”的價格。
Output:
對于每組數據,如果能換得“擎天柱”則輸出最少玩具數;否則,輸出“-1”。
Sample Input:
3
1 2 3
4
4
4 3 3 5
2
Sample Output:
2
-1
這是浙工大ACM上的題目做完可以提交試試。。
想用剛才的方法做?發現題目里多了什么條件沒?
?????? 對了!那就是“只有月光拿出的玩具的總價格與“擎天柱”的價格相等才能換得“擎天柱”這句。 換句話說題目要求的不僅僅是最優值而且要求你求的是“能把包裝滿”的最優值!!!
?????? 怎么辦?難道就這樣束手無策了么?
解答:
0-1背包也是背包問題的最常見的兩種問法:
一是要求“恰好裝滿背包”時的最優解
二是“沒有要求必須把背包裝滿”。
這兩種問法的實現方法不同點主要在初始化上。
如果是第一種問法,要求恰好裝滿背包,那么在初始化時除了f[0]為0其它f[1..V]均設為-∞,這樣就可以保證最終得到的f[N]是一種恰好裝滿背包的最優解。
如果并沒有要求必須把背包裝滿,而是只希望價格盡量大,初始化時應該將f[0..V]全部設為0。
為什么呢?
可以這樣理解:初始化的f數組事實上就是在沒有任何物品可以放入背包時的合法狀態。如果要求背包恰好裝滿,那么此時只有容量為0的背包可能被價值為0的nothing“恰好裝滿”,其它容量的背包均沒有合法的解,屬于未定義的狀態,它們的值就都應該是-∞了。如果背包并非必須被裝滿,那么任何容量的背包都有一個合法解“什么都不裝”,這個解的價值為0,所以初始時狀態的值也就全部為0了。
這個小技巧完全可以推廣到其它類型的背包問題,后面也就不再對進行狀態轉移之前的初始化進行講解。
#include <stdio.h> #include <stdlib.h>const int MAX = 10010; int f[MAX]; int V; //背包的體積void ZeroOnePack (int cost, int weight) {int v;for (v = V; v >= cost; v--)f[v] = f[v] < (f[v - cost] + weight) ? f[v] : (f[v - cost] + weight); //這里取較小的那個值 }int value[15];int main(void) {int num,i;while(scanf("%d",&num)==1){for(i=1;i<=num;i++)scanf("%d",&value[i]);scanf("%d",&V);f[0]=0;for(i=1;i<=V;i++) //要求把背包裝滿{f[i]=0x3f; //初始化為一個比較大的值}for(i=1;i<=num;i++)ZeroOnePack(value[i],1);if(f[V]!=0x3f)printf("%d\n",f[V]);elseprintf("-1\n");}return 0; }
完全背包的應用:
有了對0-1背包的深刻認識,我們終于可以看看它的幾種變形了。首先登場的是完全背包,何為“完全背包”,就是每樣東西都有無窮多個。
我們還是先來到題目看看究竟吧!~
http://acm.hdu.edu.cn/showproblem.php?pid=1248? 寒冰王座
Problem Description
不死族的巫妖王發工資拉,死亡騎士拿到一張N元的鈔票(記住,只有一張鈔票),為了防止自己在戰斗中頻繁的死掉,他決定給自己買一些道具,于是他來到了地精商店前.
死亡騎士:"我要買道具!"
地精商人:"我們這里有三種道具,血瓶150塊一個,魔法藥200塊一個,無敵藥水350塊一個."
死亡騎士:"好的,給我一個血瓶."
說完他掏出那張N元的大鈔遞給地精商人.
地精商人:"我忘了提醒你了,我們這里沒有找客人錢的習慣的,多的錢我們都當小費收了的,嘿嘿."
死亡騎士:"......"
死亡騎士想,與其把錢當小費送個他還不如自己多買一點道具,反正以后都要買的,早點買了放在家里也好,但是要盡量少讓他賺小費.
現在死亡騎士希望你能幫他計算一下,最少他要給地精商人多少小費。
Input
輸入數據的第一行是一個整數T(1<=T<=100),代表測試數據的數量.然后是T行測試數據,每個測試數據只包含一個正整數N(1<=N<=10000),N代表死亡騎士手中鈔票的面值.
注意:地精商店只有題中描述的三種道具。
Output
對于每組測試數據,請你輸出死亡騎士最少要浪費多少錢給地精商人作為小費。
Sample Input
2
900
250
Sample Output
0
50
關鍵:
轉化為01背包問題求解
解題思路:
?????? 既然01背包問題是最基本的背包問題,那么我們可以考慮把完全背包問題轉化為01背包問題來解。最簡單的想法是,考慮到第i種物品最多選V/c[i]件,于是可以把第i種物品轉化為V/c[i]件費用及價值均不變的物品,然后求解這個01背包問題。這樣完全沒有改進基本思路的時間復雜度,但這畢竟給了我們將完全背包問題轉化為01背包問題的思路:將一種物品拆成多件物品。
?????? 但我們有更優的O(VN)的算法。這個算法使用一維數組,先看偽代碼:
??? for i=1..N
??????? for v=0..V
????????????? f[v]=max{f[v],f[v-c[i]]+w[i]};
?????? 你會發現,這個偽代碼與0-1背包的偽代碼只有v的循環次序不同而已。為什么這樣一改就可行呢?首先想想為什么0-1背包中要按照v=V..0的逆序來循環。這是因為要保證第i次循環中的狀態f[i][v]是由狀態f[i-1][v-c[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][v-c[i]]。而現在完全背包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][v-c[i]],所以就可以并且必須采用v=0..V的順序循環。這就是這個簡單的程序為何成立的道理。
?????? 這個算法也可以以另外的思路得出。例如,基本思路中的狀態轉移方程可以等價地變形成這種形式:f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]},將這個方程用一維數組實現,便得到了上面的偽代碼。
下面是AC的代碼:
#include <stdio.h> #include <stdlib.h>const int MAX = 10010; int f[MAX]; //MAX要比背包的體積大 int V; //背包的體積void CompletePack (int cost, int weight) //完全背包 {int v;for (v = cost; v <= V; v++)f[v] = f[v] > (f[v - cost] + weight) ? f[v] : (f[v - cost] + weight); }int value[4]={0,150,200,350};int main(void) {int t,i;scanf("%d",&t);while(t--){scanf("%d",&V);for(i=0;i<=V;i++) //沒有要求把背包裝滿{f[i]=0;}for(i=1;i<=3;i++)CompletePack(value[i],value[i]);printf("%d\n",V-f[V]);}return 0; }?多重背包
當每種物品不是1個也不是無窮多個,而是指定的n個時。就出現了我現在所要說的多重背包問題。
基本算法
這題目和完全背包問題很類似。基本的方程只需將完全背包問題的方程略微一改即可,因為對于第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。令f[i][v]表示前i種物品恰放入一個容量為v的背包的最大權值,則有狀態轉移方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
復雜度是O(V*Σn[i])。【好慢哦】
轉化為01背包問題
另一種好想好寫的基本方法是轉化為01背包求解:把第i種物品換成n[i]件01背包中的物品,則得到了物品數為Σn[i]的01背包問題,直接求解,復雜度仍然是O(V*Σn[i])。【依然是慢的】
參考思路:
利用二進制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價于取若干件代換以后的物品。另外,取超過n[i]件的策略必不能出現。
??? 方法是:將第i種物品分成若干件物品,其中每件物品有一個系數,這件物品的費用和價值均是原來的費用和價值乘以這個系數。使這些系數分別為1,2,4,...,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數。例如,如果n[i]為13,就將這種物品分成系數分別為1、2、4、6的四件物品。
分成的這幾件物品的系數和為n[i],表明不可能取多于n[i]件的第i種物品。另外這種方法也能保證對于0..n[i]間的每一個整數,均可以用若干個系數的和表示,這個證明可以分0..2^k-1和2^k..n[i]兩段來分別討論得出,并不難,希望你自己思考嘗試一下。
這樣就將第i種物品分成了O(log n[i])種物品,將原問題轉化為了復雜度為
O(V*Σlog n[i])的01背包問題,是很大的改進。
下面給出O(log amount)時間處理一件多重背包中物品的過程,其中amount表示物品的數量:
procedure MultiplePack(cost,weight,amount)
??? if cost*amount>=V
??????? CompletePack(cost,weight)
??????? return
??? integer k=1
??? while k<amount
??????? ZeroOnePack(k*cost,k*weight)
??????? amount=amount-k
??????? k=k*2
??? ZeroOnePack(amount*cost,amount*weight)
希望你仔細體會這個偽代碼,如果不太理解的話,看看下面我AC的程序,單步執行幾次,或者頭腦加紙筆模擬一下,也許就會慢慢理解了。
?http://acm.hdu.edu.cn/showproblem.php?pid=2191
Problem Description
急!災區的食物依然短缺!
為了挽救災區同胞的生命,心系災區同胞的你準備自己采購一些糧食支援災區,現在假設你一共有資金n元,而市場有m種大米,每種大米都是袋裝產品,其價格不等,并且只能整袋購買。
請問:你用有限的資金最多能采購多少公斤糧食呢?
后記:
人生是一個充滿了變數的生命過程,天災、人禍、病痛是我們生命歷程中不可預知的威脅。
月有陰晴圓缺,人有旦夕禍福,未來對于我們而言是一個未知數。那么,我們要做的就應該是珍惜現在,感恩生活——
感謝父母,他們給予我們生命,撫養我們成人;
感謝老師,他們授給我們知識,教我們做人
感謝朋友,他們讓我們感受到世界的溫暖;
感謝對手,他們令我們不斷進取、努力。
同樣,我們也要感謝痛苦與艱辛帶給我們的財富~
Input
輸入數據首先包含一個正整數C,表示有C組測試用例,每組測試用例的第一行是兩個整數n和m(1<=n<=100, 1<=m<=100),分別表示經費的金額和大米的種類,然后是m行數據,每行包含3個數p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分別表示每袋的價格、每袋的重量以及對應種類大米的袋數。
Output
對于每組測試數據,請輸出能夠購買大米的最多重量,你可以假設經費買不光所有的大米,并且經費你可以不用完。每個實例的輸出占一行。
Sample Input
1
8 2
2 100 4
4 100 2
Sample Output
400
再看看我AC的代碼,幫助你進一步的理解這類問題:
?
?
?
總結
- 上一篇: 背包问题详解
- 下一篇: Runtime Error VS A