詳細點擊一下連接(揹包九講)html
http://love-oriented.com/pack/Index.html#sec1 如下內容,有些本身想法,有些摘錄ios
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·································~~~~~~~~~~~~~~~~~~~~~~···································~~~~~~~~~~~~~~······················~~~~~~~~算法
動態規劃算法可分解成從先到後的4個步驟:數組
1. 描述一個最優解的結構;ide
2. 遞歸地定義最優解的值;學習
3. 以「自底向上」的方式計算最優解的值;優化
4. 從已計算的信息中構建出最優解的路徑。this
其中步驟1~3是動態規劃求解問題的基礎。若是題目只要求最優解的值,則步驟4能夠省略。idea
每種物品能夠選擇放進揹包或者不放進揹包(這也就是0和1)spa
設揹包容量爲V,一共N件物品,每件物品體積爲C[i],每件物品的價值爲W[i]
1) 子問題定義:F[i][j]表示前i件物品中選取若干件物品放入剩餘空間爲j的揹包中所能獲得的最大價值。
2) 根據第i件物品放或不放進行決策
(1-1)
優化空間複雜度 -----要嘗試理解,看了不少人的代碼,都是用這個的
以上方法的時間和空間複雜度均爲O(VN),其中時間複雜度應該已經不能再優化了,但空間複雜度卻能夠優化到O。
先考慮上面講的基本思路如何實現,確定是有一個主循環i=1..N,每次算出來二維數組f[i][0..V]的全部值。那麼,若是隻用一個數組f[0..V],能不能保證第i次循環結束後f[v]中表示的就是咱們定義的狀態f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]兩個子問題遞推而來,可否保證在推f[i][v]時(也即在第i次主循環中推f[v]時)可以獲得f[i-1][v]和f[i-1][v-c[i]]的值呢?事實上,這要求在每次主循環中咱們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]保存的是狀態f[i-1][v-c[i]]的值。僞代碼以下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就至關於咱們的轉移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]}
,由於如今的f[v-c[i]]就至關於原來的f[i-1][v-c[i]]。若是將v的循環順序從上面的逆序改爲順序的話,那麼則成了f[i][v]由f[i][v-c[i]]推知,與本題意不符,但它倒是徹底揹包問題最簡捷的解決方案,故學習只用一維數組解01揹包問題是十分必要的。
事實上,使用一維數組解01揹包的程序在後面會被屢次用到,因此這裏抽象出一個處理一件01揹包中的物品過程,之後的代碼中直接調用不加說明。
過程ZeroOnePack,表示處理一件01揹包中的物品,兩個參數cost、weight分別代表這件物品的費用和價值。
procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
注意這個過程裏的處理與前面給出的僞代碼有所不一樣。前面的示例程序寫成v=V..0是爲了在程序中體現每一個狀態都按照方程求解了,避免沒必要要的思惟複雜度。而這裏既然已經抽象成看做黑箱的過程了,就能夠加入優化。費用爲cost的物品不會影響狀態f[0..cost-1],這是顯然的。
有了這個過程之後,01揹包問題的僞代碼就能夠這樣寫:
for i=1..N
ZeroOnePack(c[i],w[i]);
ps:初始化問題
(1):要求剛好裝滿揹包,那麼在初始化時除了f[0]爲0其它f[1..V]均設爲-∞,這樣就能夠保證最終獲得的f[N]是一種剛好裝滿揹包的最優解
初始化的f數組事實上就是在沒有任何物品能夠放入揹包時的合法狀態。若是要求揹包剛好裝滿,那麼此時只有容量爲0的揹包可能被價值爲0的nothing「剛好裝滿」,其它容量的揹包均沒有合法的解,屬於未定義的狀態,它們的值就都應該是-∞了。
(2):若是並無要求必須把揹包裝滿,而是隻但願價格儘可能大,初始化時應該將f[0..V]所有設爲0
若是揹包並不是必須被裝滿,那麼任何容量的揹包都有一個合法解「什麼都不裝」,這個解的價值爲0,因此初始時狀態的值也就所有爲0了。
典例加深
51nod 1085 揹包問題V1
第1行,2個整數,N和W中間用空格隔開。N爲物品的數量,W爲揹包的容量。(1 <= N <= 100,1 <= W <= 10000)
第2 - N + 1行,每行2個整數,Wi和Pi,分別是物品的體積和物品的價值。(1 <= Wi, Pi <= 10000)
輸出能夠容納的最大價值。
3 6
2 5
3 8
4 9
14
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 int value[105],tiji[105]; 5 int dp[10005],num,m,i,j; 6 int main(){ 7 cin>>num>>m; 8 for(i=0;i<num;i++) 9 cin>>tiji[i]>>value[i]; 10 memset(dp,0,sizeof(dp)); 11 for(i=0;i<num;i++) 12 for(j=m;j>=tiji[i];j--)8 13 dp[j]=max((dp[j-tiji[i]]+value[i]),dp[j]); 14 cout<<dp[m]; 15 return 0; 16 }
hdoj 2546 飯卡 (01揹包變形)
1 #include <string.h> 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5 int main(){ 6 int n,V, w[1005],dp[1005]; 7 while(cin>>n&&n){ 8 memset(dp,0,sizeof(dp)); 9 for(int i=1;i<=n;i++) 10 cin>>w[i]; 11 cin>>V; 12 sort(w+1,w+1+n); //從1開始 13 if(V<5) cout<<V<<endl; 14 else{ 15 for(int i=1;i<n;i++) //留一個名額 16 for(int j=V-5;j>=w[i];j--) //保留5元,用剩下的錢去買價值更大的菜 17 dp[j]=max(dp[j],dp[j-w[i]]+w[i]); 18 cout<<V-dp[V-5]-w[n]<<endl; //餘額-最多能買的菜-最貴的菜 19 } 20 } 21 return 0; 22 }
poj 3624 Charm Bracelet(01揹包)
Description
Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a 'desirability' factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).
Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.
Input
* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di
Output
* Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints
Sample Input
4 6
1 4
2 6
3 12
2 7
Sample Output
23
1 /* G++ 2 這題數組必定要開大,但也不能太大,不然都wa 3 看了discuss中討論 4 貌似記錄數組(dp)的大小應該由M決定吧 5 6 W,D的大小是由N決定的 7 */ 8 #include <stdio.h> 9 #include <string.h> 10 #define M 14000 11 int dp[M]; 12 int main(){ 13 int n,m,v,w; 14 memset(dp,0,sizeof(dp)); 15 scanf("%d %d",&n, &m); 16 for(int i=1;i<=n;i++){ 17 scanf("%d %d",&w, &v); 18 for(int j=m;j>=w;j--){ 19 int temp=dp[j-w]+v; 20 if(temp>dp[j]) 21 dp[j]=temp; 22 } 23 } 24 printf("%d\n",dp[m]); 25 }
一個常數優化
前面的僞代碼中有 for v=V..1,能夠將這個循環的下限進行改進。
因爲只須要最後f[v]的值,倒推前一個物品,其實只要知道f[v-w[n]]便可。以此類推,對以第j個揹包,其實只須要知道到f[v-sum{w[j..n]}]便可,即代碼中的
for i=1..N
for v=V..0
能夠改爲
for i=1..n
bound=max{V-sum{w[i..n]},c[i]}
for v=V..bound
這對於V比較大時是有用的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·~~~~~~~~~~~~~~~~~·············································································································································································~~~~~~~~~~~~~~~~~~~~~~~~~·
有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。
與01揹包不一樣就是每種物品無限件可用,也就是從每種物品的角度考慮,與它相關的策略已並不是取或者不取兩種了,而是有取0件,取1件,取2件
……等不少種。若是仍然按照解01揹包時的思路,令f[i][v]表示前i種物品恰放入一個容量爲v的揹包的最大權值。仍然能夠按照每種物品不一樣的策略寫出狀態轉移方程,像這樣:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
這跟01揹包問題同樣有O(VN)個狀態須要求解,但求解每一個狀態的時間已經不是常數了,求解狀態f[i][v]的時間是O(v/c[i]),總的複雜度能夠認爲是O(V*Σ(V/c[i])),是比較大的。
因此要將01揹包問題的基本思路加以改進
徹底揹包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j知足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。這個優化的正確性顯然:任何狀況下均可將價值小費用高得j換成物美價廉的i,獲得至少不會更差的方案。對於隨機生成的數據,這個方法每每會大大減小物品的件數,從而加快速度。然而這個並不能改善最壞狀況的複雜度,由於有可能特別設計的數據能夠一件物品也去不掉。
這個優化能夠簡單的O(N^2)地實現,通常均可以承受。另外,針對揹包問題而言,比較不錯的一種方法是:首先將費用大於V的物品去掉,而後使用相似計數排序的作法,計算出費用相同的物品中價值最高的是哪一個,能夠O(V+N)地完成這個優化。這個不過重要的過程就不給出僞代碼了,但願你能獨立思考寫出僞代碼或程序。
既然01揹包問題是最基本的揹包問題,那麼咱們能夠考慮把徹底揹包問題轉化爲01揹包問題來解。最簡單的想法是,考慮到第i種物品最多選V/c[i]件,因而能夠把第i種物品轉化爲V/c[i]件費用及價值均不變的物品,而後求解這個01揹包問題。這樣徹底沒有改進基本思路的時間複雜度,但這畢竟給了咱們將徹底揹包問題轉化爲01揹包問題的思路:將一種物品拆成多件物品。
更高效的轉化方法是:把第i種物品拆成費用爲c[i]*2^k、價值爲w[i]*2^k的若干件物品,其中k知足c[i]*2^k<=V
。這是二進制的思想,由於無論最優策略選幾件第i種物品,總能夠表示成若干個2^k件物品的和。這樣把每種物品拆成O(log V/c[i])件物品,是一個很大的改進。
但咱們有更優的O(VN)的算法。
這個算法使用一維數組,先看僞代碼:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
想必你們看出了和01揹包的區別,這裏的內循環是順序的,而01揹包是逆序的。
如今關鍵的是考慮:爲什麼徹底揹包能夠這麼寫?
在次咱們先來回憶下,01揹包逆序的緣由?是爲了是max中的兩項是前一狀態值,這就對了。
那麼這裏,咱們順序寫,這裏的max中的兩項固然就是當前狀態的值了,爲什麼?
由於每種揹包都是無限的。當咱們把i從1到N循環時,f[v]表示容量爲v在前i種揹包時所得的價值,這裏咱們要添加的不是前一個揹包,而是當前揹包。因此咱們要考慮的固然是當前狀態。
事實上,對每一道動態規劃題目都思考其方程的意義以及如何得來,是加深對動態規劃的理解、提升動態規劃功力的好方法。
典例加深
51nod 換零錢(徹底揹包)
輸入1個數N,N = 100表示1元錢。(1 <= N <= 100000)
輸出Mod 10^9 + 7的結果
5
4
1 /* 2 dp[i]表示錢i能換零錢的種類數, 3 那麼每次換的時候有兩種狀況 4 dp[i]表示不換,dp[i-v[j]]表示換了, 5 其和即是答案,換與不換實際上是利用到了前邊的 6 計算結果 7 */ 8 #include <iostream> 9 #include <stdio.h> 10 using namespace std; 11 const int maxn=100005; 12 const int mod=1e9+7; 13 int n; 14 long long dp[maxn]; 15 int v[13]={1,2,5,10,20,50,100,200,500,1000,2000,5000,10000}; 16 17 int main(){ 18 scanf("%d",&n); 19 dp[0]=1; 20 for(int j=0;j<13;j++) 21 for(int i=v[j];i<=n;i++) 22 dp[i]=(dp[i]+dp[i-v[j]])%mod; 23 printf("%I64d\n",dp[n]); //printf("%lld\n",dp[n]); 24 return 0; 25 }
hdoj 1114 Piggy-Bank(徹底揹包)
1 #include <string.h> 2 #include <stdio.h> 3 #include <algorithm> 4 using namespace std; 5 6 int dp[1000005]; 7 8 int main() 9 { 10 int t; 11 int wa,wb,w; 12 int n,val[505],wei[505],i,j; 13 scanf("%d",&t); 14 while(t--) 15 { 16 scanf("%d%d",&wa,&wb); 17 w = wb-wa;//必須減去小豬自己重量 18 scanf("%d",&n); 19 for(i = 0;i<n;i++) 20 scanf("%d%d",&val[i],&wei[i]); 21 for(i = 0;i<=w;i++) 22 { 23 dp[i] = 10000000;//由於要求小的,因此dp數組必須存大數 24 } 25 dp[0] = 0; 26 for(i = 0;i<n;i++) 27 { 28 for(j = wei[i];j<=w;j++) 29 { 30 dp[j] = min(dp[j],dp[j-wei[i]]+val[i]); 31 } 32 } 33 if(dp[w] == 10000000) 34 printf("This is impossible.\n"); 35 else 36 printf("The minimum amount of money in the piggy-bank is %d.\n",dp[w]); 37 } 38 39 return 0; 40 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~···········································································································~~~~~~~~~~~~~~~~~~~~~~
多重揹包問題
有N種物品和一個容量爲V的揹包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。
這題目和徹底揹包問題很相似。基本的方程只需將徹底揹包問題的方程略微一改便可,由於對於第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揹包求解:把第i種物品換成n[i]件01揹包中的物品,則獲得了物品數爲Σn[i]的01揹包問題,直接求解,複雜度仍然是O(V*Σn[i])。
可是咱們指望將它轉化爲01揹包問題以後可以像徹底揹包同樣下降複雜度。仍然考慮二進制的思想,咱們考慮把第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的四件物品。
2^0+2^1+2^2+(2^3)>13
因此,13-2^0-2^1-2^2=6
這四個數能夠組成13中任意一個數 7=6+1, 5=4+1……
分紅的這幾件物品的係數和爲n[i],代表不可能取多於n[i]件的第i種物品。另外這種方法也能保證對於0..n[i]間的每個整數,都可以用若干個係數的和表示,這個證實能夠分0..2^k-1和2^k..n[i]兩段來分別討論得出,並不難,但願你本身思考嘗試一下。
這樣就將第i種物品分紅了O(log n[i])種物品,將原問題轉化爲了複雜度爲<math>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)
但願你仔細體會這個僞代碼,若是不太理解的話,不妨翻譯成程序代碼之後,單步執行幾回,或者頭腦加紙筆模擬一下,也許就會慢慢理解了。
51nod 1086 揹包問題V2(多重揹包)
第1行,2個整數,N和W中間用空格隔開。N爲物品的種類,W爲揹包的容量。(1 <= N <= 100,1 <= W <= 50000)
第2 - N + 1行,每行3個整數,Wi,Pi和Ci分別是物品體積、價值和數量。(1 <= Wi, Pi <= 10000, 1 <= Ci <= 200)
輸出能夠容納的最大價值。
3 6
2 2 5
3 3 8
1 4 1
9
1 //思路:二進制 + 01揹包思想 2 #include <iostream> 3 #include<cstdio> 4 #include<cmath> 5 int w,va,c,w1[10010],va1[10010]; 6 int dp[50010]; 7 using namespace std; 8 9 int main() 10 { 11 int N,W; 12 int cnt=0;//二進制以後的物件個數 13 scanf("%d%d",&N,&W); 14 for(int i=1;i<=N;i++) { 15 scanf("%d%d%d",&w,&va,&c); 16 for(int j=1;;j*=2) { 17 if(c>=j){ 18 w1[cnt]=j*w; 19 va1[cnt]=j*va; 20 c-=j; 21 cnt++; 22 } 23 else { 24 w1[cnt]=c*w; 25 va1[cnt]=c*va; 26 cnt++; 27 break; 28 } 29 } 30 } 31 for(int i=0;i<cnt;i++){ 32 for(int j=W;j>=w1[i];j--) 33 dp[j]=max(dp[j],dp[j-w1[i]]+va1[i]); 34 }//一維 空間複雜度小 35 printf("%d\n",dp[W]); 36 return 0; 37 }