01背包(详解)
原创
问题描述:
问题描述:
给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.
输入格式
输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
以后N行每行两个数Wi和Vi,表示物品的重量和价值
以后N行每行两个数Wi和Vi,表示物品的重量和价值
输出格式
输出1行,包含一个整数,表示最大价值。
样例输入
3 5
2 3
3 5
4 7
2 3
3 5
4 7
样例输出
8
数据规模和约定
1<=N<=200,M<=5000.
解题思路:
大家应该都看过很多大神的代码了,如果还有不明白的地方,不妨听听我的解释,希望能给你带来柳暗花明的感觉。
首先大家都应该知道,一个物品,有放与不放两种选择,我们的最终结果就是综合所有物品放与不放的选择来确定。
有这样一个状态方程:
F[ i ][ j ]=max( F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] );
其实不难理解,F[ i ][ j ] 代表 当放到第 i 个物品时,此时容量还剩 j ,我们可以选择放或者不放;
不放的话 F[ i ][ j ]=F[ i-1 ][ j ];就是相当于放了前 i-1 个物品中的物品;注意,不一定是前 i-1 个
物品全都放进了背包。第 i 个物品放进去的话 F[ i ][ j ]=F[ i-1 ][ j - wi[i] ] + vi[ i ]; 放进去了所以
在前面放了 i-1 个物品中的物品的基础上,容量减去 wi[i], 价值加上vi[ i ](但得在wi[i] <= j 条件下);
有些同学估计挺纳闷,在放得下的前提下从(F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] ) 这两个中选择
最大的,那肯定是加入了第 i 个物品的大呀,这还需要思考吗?请看下图:
假设有3个物品,重量分别为:9、7、8;价值分别是:10、15、16,背包容量是20.
其实只要我们按照那个公式套进去,就会发现,每个物品放与不放的情况都会被遍历到,
回到上面的问题,F[ i-1 ][ j ] < F[ i-1 ][ j - wi[i] ] + vi[ i ] 是不错,但是你放了第 i 个物品,假设是这个例子
的最后一个物品,前面你或许就得少放一个物品了。也许你放了第 i 个物品,放不下 i+1 个物品了,但是 vi[ i+1 ] > vi[ i ]
所以每种情况我们都要遍历过,选出最优的。
大家请看上图,思路就是从左上角开始,一行一行的求出所有放与不放的可能,右下角的44就是我们要求的答案了;但是需要注意的是,
其实很多数据我们根本用不着,从最后一行往上数,我们要的数据个数只是1、2、4、8、16个而已。
代码:
#include<stdio.h> #include<stdlib.h> #include<iostream> int max(int a, int b) { return a > b ? a : b; } int main() { int n, m; scanf("%d%d", &n, &m); int *w, *v; w = (int *) malloc(sizeof(int) * (n + 1)); v = (int *) malloc(sizeof(int) * (n + 1)); int **arr; //动态分配二维数组 arr = (int **) malloc(sizeof(int *) * (n + 1)); int i; for (i = 0; i <= n; i++) arr[i] = (int *) malloc(sizeof(int) * (m + 1)); int j; for (i = 1; i <= n; i++) scanf("%d%d", &w[i], &v[i]); for (i = 0; i <= n; i++){ for (j = 0; j <= m; j++) { if (i == 0 || j == 0) //0容量或者0物品置0 arr[i][j] = 0; else if (w[i] <= j) arr[i][j] = max(arr[i - 1][j], arr[i - 1][j - w[i]] + v[i]); else arr[i][j] = arr[i - 1][j]; } } printf("%d", arr[n][m]); free(arr); free(w); free(v); return 0; }
思路提升:
从上面的表格可以看到,我们用了一个二维数组存储了每一行的数据,但是实际情况是第1行数据只是第2行用,用完以后就没用了,
第 i 行的数据只是第 i+1 行使用,用完了就放在数组里面占用大量空间了;所以我们完全可以用一个 “滚动数组” 来实现这种操作,
所谓的滚动数组其实很简单,就是数组里面我们先存放第 1 行的数据,然后我们根据第 1 行的数据求出第 2 行的数据存放在数组;
就是第 i 行的数据先存放,我求出第 i+1 行的数据以后数组我来占用,这样不断变化的数组就是滚动数组,很简单吧。
代码实现:
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int max(int a,int b) 5 { 6 return a>b?a:b; 7 } 8 9 int main() 10 { 11 int n,m; 12 scanf("%d%d",&n,&m); 13 14 int *wi,*vi; 15 wi=(int *)malloc(sizeof(int)*(n+1)); 16 vi=(int *)malloc(sizeof(int)*(n+1)); 17 18 int i; 19 for(i=1;i<=n;i++) 20 scanf("%d%d",&wi[i],&vi[i]); 21 22 int *dp; 23 dp=(int *)malloc(sizeof(int)*(m+1)); //滚动数组 24 for(i=0;i<=m;i++) 25 dp[i]=0; 26 27 int j; 28 for(i=0;i<=n;i++) 29 for(j=m;j>=wi[i];j--) //容量从大到小 30 { 31 if(i==0) 32 { 33 dp[j]=0; 34 continue; 35 } 36 37 dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]); 38 } 39 40 printf("%d",dp[m]); 41 return 0; 42 }
代码中状态方程变为:dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
请大家注意,dp[j]代表的是现在要求的第 i 个物品考虑放不放在容量为 j 的物品下的最优解,而括号里面的dp[j]代表的
是放 i-1 个物品中的物品时的最优解,相当于上面的F[ i-1 ][ j ],也就是说这个是前一个状态的量,而dp[j-wi[i]]+vi[i]
就是上面的F[ i-1 ][ j - wi[i] ] + vi[ i ]了。但是容量 j 的值要从大到小了,因为我们求大容量背包的价值时要用到
小容量背包的价值,所以如果从小容量开始求,等到求放下一个物品时,从小容量开始求,那么当我们求这个物品的大容量时放与不放时
上一个物品的小容量值由于被这个物品的小容量值覆盖,所以要从大容量开始求。
2018-04-06