01 背包
有n 種不同的物品,每個物品有兩個屬性,size 體積,value 價值,現在給一個容量為 w 的背包,問最多可帶走多少價值的物品。
1 for (int i=0; i<n; i++) 2 for (int j=w; j>=size[i]; j--) 3 f[j] = max(f[j], f[j-size[i]]+value[i]);
完全背包
如果物品不計件數,就是每個物品不只一件的話,稍微改下即可
1 for (int i=0; i<n; i++) 2 for (int j=size[i]; j<=w; j++) 3 f[j] = max(f[j], f[j-size[i]]+value[i]);
f[w] 即為所求
初始化分兩種情況:
1、如果背包要求正好裝滿則初始化 f[0] = 0, f[1~w] = -INF;
2、如果不需要正好裝滿 f[0~v] = 0;
舉例:
01背包
V=10,N=3,c[]={3,4,5}, w={4,5,6}
(1)背包不一定裝滿
計算順序是:從右往左,自上而下:因為每個物品只能放一次,前面的體積小的會影響體積大的
(2)背包剛好裝滿
計算順序是:從右往左,自上而下。注意初始值,其中-inf表示負無窮
完全背包:
V=10,N=3,c[]={3,4,5}, w={4,5,6}
(1)背包不一定裝滿
計算順序是:從左往右,自上而下: 每個物品可以放多次,前面的會影響后面的
(2)背包剛好裝滿
計算順序是:從左往右,自上而下。注意初始值,其中-inf表示負無窮
多重背包:
多重背包問題要求很簡單,就是每件物品給出確定的件數,求可得到的最大價值
多重背包轉換成 01 背包問題就是多了個初始化,把它的件數C 用二進制分解成若干個件數的集合,這里面數字可以組合成任意小於等於C的件數,而且不會重復,之所以叫二進制分解,是因為這樣分解可以用數字的二進制形式來解釋
比如:7的二進制 7 = 111 它可以分解成 001 010 100 這三個數可以組合成任意小於等於7 的數,而且每種組合都會得到不同的數
15 = 1111 可分解成 0001 0010 0100 1000 四個數字
如果13 = 1101 則分解為 0001 0010 0100 0110 前三個數字可以組合成 7以內任意一個數,即1、2、4可以組合為1——7內所有的數,加上 0110 = 6 可以組合成任意一個大於6 小於等於13的數,比如12,可以讓前面貢獻6且后面也貢獻6就行了。雖然有重復但總是能把 13 以內所有的數都考慮到了,基於這種思想去把多件物品轉換為,多種一件物品,就可用01 背包求解了。
看代碼:
int n; //輸入有多少種物品 int c; //每種物品有多少件 int v; //每種物品的價值 int s; //每種物品的尺寸 int count = 0; //分解后可得到多少種物品 int value[MAX]; //用來保存分解后的物品價值 int size[MAX]; //用來保存分解后物品體積 scanf("%d", &n); //先輸入有多少種物品,接下來對每種物品進行分解 while (n--) //接下來輸入n中這個物品 { scanf("%d%d%d", &c, &s, &v); //輸入每種物品的數目和價值 for (int k=1; k<=c; k<<=1) //<<右移 相當於乘二 { value[count] = k*v; size[count++] = k*s; c -= k; } if (c > 0) { value[count] = c*v; size[count++] = c*s; } }
定理:一個正整數n可以被分解成1,2,4,…,2^(k-1),n-2^k+1(k是滿足n-2^k+1>0的最大整數)的形式,且1~n之內的所有整數均可以唯一表示成1,2,4,…,2^(k-1),n-2^k+1中某幾個數的和的形式。
證明如下:
(1) 數列1,2,4,…,2^(k-1),n-2^k+1中所有元素的和為n,所以若干元素的和的范圍為:[1, n];
(2)如果正整數t<= 2^k – 1,則t一定能用1,2,4,…,2^(k-1)中某幾個數的和表示,這個很容易證明:我們把t的二進制表示寫出來,很明顯,t可以表示成n=a0*2^0+a1*2^1+…+ak*2^(k-1),其中ak=0或者1,表示t的第ak位二進制數為0或者1.
(3)如果t>=2^k,設s=n-2^k+1,則t-s<=2^k-1,因而t-s可以表示成1,2,4,…,2^(k-1)中某幾個數的和的形式,進而t可以表示成1,2,4,…,2^(k-1),s中某幾個數的和(加數中一定含有s)的形式。
(證畢!)
現在用count 代替 n 就和01 背包問題完全一樣了
杭電2191題解:此為多重背包用01和完全背包:
#include<stdio.h> #include<string.h> int dp[102]; int p[102],h[102],c[102]; int n,m; void comback(int v,int w)//經費,重量。完全背包; { for(int i=v; i<=n; i++) if(dp[i]<dp[i-v]+w) dp[i]=dp[i-v]+w; } void oneback(int v,int w)//經費,重量;01背包; { for(int i=n; i>=v; i--) if(dp[i]<dp[i-v]+w) dp[i]=dp[i-v]+w; } int main() { int ncase,i,j,k; scanf("%d",&ncase); while(ncase--) { memset(dp,0,sizeof(dp)); scanf("%d%d",&n,&m);//經費,種類; for(i=1; i<=m; i++) { scanf("%d%d%d",&p[i],&h[i],&c[i]);//價值,重量,數量; if(p[i]*c[i]>=n) comback(p[i],h[i]); else { for(j=1; j<c[i]; j<<1) { oneback(j*p[i],j*h[i]); c[i]=c[i]-j; } oneback(p[i]*c[i],h[i]*c[i]); } } printf("%d\n",dp[n]); } return 0; }