本文主要为了记载用,详细解释请自己根据提示亲自画图操作下,切忌眼高手低。
一、装满背包
装满背包,要求最红认定最大val值的前提是——此刻背包刚好装满,仅从实现上看,和“可不满”背包的区别只是初始化方法不同。以0-1背包为例,
V=10,N=3,c[]={3,4,5}, w={4,5,6}
1)背包不一定装满
计算顺序是:从右往左,自上而下:因为每个物品只能放一次,前面的体积小的会影响体积大的
2)背包刚好装满
计算顺序是:从右往左,自上而下。注意初始值,其中-inf表示负无穷
关于装满背包,读者可以自己体会下-inf作用,亲自划一下图,就明白为何如此初始化了。
二、0-1背包
问题描述:有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大
状态方程1:dp[i][j] = max{dp[i - 1][j], dp[i - 1][j - c[i] + w[i]]}
解决方案1:资料很多,此处从略。
状态方程2:优化空间,用f[v]代替dp[i][v]:max{f[v], f[v-c[i]] + w[i]}
for i in 0 ... N for v = V ... 0 f[v] = max{f[v], f[v-c[i]] + w[i]}
至于为什么v是从V到0,自己划一下才能明白。网上说法一大堆,不是自己的理解。
解决方法2:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1010 int value[N], volume[N], dp[N]; // 0-1背包,优化空间 void dpPackage(int n, int v) { int i, j; memset(dp, 0, sizeof(dp)); for (i = 1; i <= n; i ++) { for (j = v; j >= volume[i]; j --) { dp[j] = dp[j] > dp[j - volume[i]] + value[i] ? dp[j] : dp[j - volume[i]] + value[i]; } } printf("%d ", dp[v]); } int main(void) { int i, t, n, v; scanf("%d", &t); while (t --) { // 接收参数 scanf("%d %d", &n, &v); for (i = 1; i <= n; i ++) scanf("%d", value + i); for (i = 1; i <= n; i ++) scanf("%d", volume + i); // 0-1背包 dpPackage(n, v); } return 0; }
解决方法3(递归):这是最直接的实现动态规划的方法,但往往被人们忽略,摘自《0-1背包问题的递归实现与非递归实现》,源码贴在下面
#include<iostream> using namespace std; const int W = 150; const int number = 5; const int VALUE[] = {60, 20, 10, 60, 100}; const int WEIGHT[] = {20, 30, 50, 60, 80}; //function Make( i {处理到第i件物品} , j{剩余的空间为j}) :integer; int Make(int i, int j) { int r1 = 0; int r2 = 0; int r = 0; if (i == -1) { return 0; } if(j >= WEIGHT[i]) //背包剩余空间可以放下物品 i { r1 = Make(i-1,j - WEIGHT[i]) + VALUE[i]; //第i件物品放入所能得到的价值 r2 = Make(i-1,j); //第i件物品不放所能得到的价值 r = (r1>r2)?r1:r2; } return r; } void main() { int maxValue = Make(number-1, W); cout<<"maxValue: "<<maxValue<<endl; }
三、完全背包
问题描述:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价格是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
状态方程1:dp[i][v] = max{dp[i-1][v - k * c[i]] + k * w[i] | 0 <= k * c[i]<= v}
解决方法1:参考《完全背包基本实现》
using namespace std; int maxV[11][201]; int weight[11]; int value[11]; int V, N; void main() { int i, j, k; scanf("%d %d",&V, &N); for(i = 0; i < N; ++i) { scanf("%d %d",&weight[i],&value[i]); } for(i = 0; i < N; ++i) { for(j = 0; j <= V; ++j) { if(i > 0) { maxV[i][j] = maxV[i-1][j]; if(j/weight[i] >= 1) { int max_tmp = 0; for(k = 1; k <= j/weight[i]; ++k) { if(maxV[i-1][j-k*weight[i]] + k*value[i] > max_tmp) { max_tmp = maxV[i-1][j-k*weight[i]] + k*value[i]; } } maxV[i][j] = maxV[i][j] > max_tmp ? maxV[i][j] : max_tmp; } }else { if(j/weight[0] >= 1) { maxV[0][j] = j/weight[0] * value[0]; } } } } printf("%d",maxV[N-1][V]); }
状态方程2:f[v] = max{f[v], f[v-cost] + weight} 这个方案的得到,依赖1)转化为0-1背包问题 2)并压缩空间,参见《01背包、完全背包、多重背包》
for i = 1 ... N for v = 0 ... V f[v] = max{f[v], f[v-cost] + weight}
解决方法2:
/* * 完全背包问题 */ #include <stdio.h> #include <stdlib.h> #define INF 50000000 typedef struct coin { int price, weight; } coin; void dynamicPackage(coin *coins, int n, int v) { if (v < 0) { printf("This is impossible. "); return; } int i, j, *dp; // 动态分配内存 dp = (int *)malloc(sizeof(int) * (v + 1)); // 初始化 dp[0] = 0; for (i = 1; i <= v; i ++) dp[i] = INF; // 完全背包问题 for (i = 1; i <= n; i ++) { for (j = coins[i].weight; j <= v; j ++) { dp[j] = (dp[j] < dp[j - coins[i].weight] + coins[i].price) ? dp[j] : dp[j - coins[i].weight] + coins[i].price; } } if (dp[v] >= INF) printf("This is impossible. "); else printf("The minimum amount of money in the piggy-bank is %d. ", dp[v]); // 清理内存 free(dp); dp = NULL; } int main(void) { int t, e, f, n, i; coin *coins; scanf("%d", &t); while (t --) { scanf("%d %d", &e, &f); scanf("%d", &n); // 接收货币 coins = (coin *)malloc(sizeof(coin) * (n + 1)); if (coins == NULL) exit(-1); for (i = 1; i <= n; i ++) { scanf("%d %d", &coins[i].price, &coins[i].weight); } // 完全背包 dynamicPackage(coins, n, f - e); free(coins); coins = NULL; } return 0; }
四、0-1背包 和 完全背包 装满方案数
先说下,问题的意义,
1)完全背包装满
假设现在有1元、2元、5元的纸币很多张,现在需要20块钱,你能给多少种找钱方案,这就可以认为是完全背包问题,即背包容量为20,物品体积分别为1、2、5。
2)01背包装满
给定一个数m,将m拆成不同的自然数的和的形式有多少种方案,这就是典型的01背包问题,背包容量为m,物品件数为k,这里面的k是隐含条件,可以求出来,因为m最多由1+2+…+k得到,由此可以根据m求得物品件数的上限。此题非递归见下文“背包问题”,递归方法见
《解题笔记(31)——从数列1,2...n中随意取几个数,使其和等于m》
具体解决方法,这里不再陈述,参见《背包问题——“01背包”及“完全背包”装满背包的方案总数分析及实现》
此题也可以先求出小于m的所有可能的数的组合方案,然后从中找出方案内数字和等于m的。具体可参考《解题笔记(21)——字符串的排列组合问题》
另外,对于可以不满的情况,有如下博文分析方案总数,
五、多重背包
待续。
六、动态规划之《TSP旅行商问题》