背包系列专题之完全背包问题

xiaoxiao2021-02-28  105

专题二:完全背包问题 /* Name: 背包系列专题之完全背包问题 Author: 巧若拙 Description: 完全背包问题:在n种物品中选取若干件(同一种物品可多次选取)放在容量为c的背包里,分别用P[i]和W[i]存储第i种物品的价值和重量。 求解怎么装物品可使背包里物品总价值最大。 样例输入 4 12 2 3 5 7 6 8 10 12 样例输出 18 */ #include<iostream> using namespace std; const int MAXC = 10000; //背包最大容量 const int MAXN = 2000; //物品的最大个数 int W[MAXN+1];//物品的重量 int P[MAXN+1];//物品的价值 int B1[MAXN+1][MAXC+1]; //记录给定n个物品装入容量为c的背包的最大价值 int B2[MAXN+1][MAXC+1]; //记录给定n个物品装入容量为c的背包的最大价值 int pre[MAXC+1]; //pre[j]相当于B2[i-1][j] int cur[MAXC+1]; //cur[j]相当于B2[i][j] int B4[MAXC+1]; //记录最优解 int CompletePack_1(int n, int c);//动态规划+朴素的穷举思想 int CompletePack_2(int n, int c);//动态规划:F[i][j] = max(F[i-1][j], F[i][j-W[i]] + P[i]) int CompletePack_3(int n, int c);//优化的动态规划算法,用2个一维数组代替二维数组 int CompletePack_4(int n, int c);//优化的动态规划算法,一维数组存储记录 int main() { int n, c; cin >> n >> c; for (int i=1; i<=n; i++)//不计下标为0的元素 { cin >> W[i] >> P[i]; } //动态规划+朴素的穷举思想,需要用到全局变量W[], P[], 另有B1[MAXN+1][]初始化为0 cout << CompletePack_1(n, c) << endl; //动态规划:二维数组存储记录,需要用到全局变量W[], P[], 另有B2[MAXN+1][]默认初始化为0 cout << CompletePack_2(n, c) << endl; //动态规划:使用2个一维数组存储记录,需要用到全局变量W[], P[], 另有pre[]和cur[]均初始化为0 cout << CompletePack_3(n, c) << endl; //优化的动态规划算法,一维数组存储记录,需要用到全局变量W[], P[], 另有B4[]初始化为0 cout << CompletePack_4(n, c) << endl; return 0; } 算法1:动态规划+朴素的穷举思想,需要用到全局变量W[], P[], 另有B1[MAXN+1][]初始化为0。 int CompletePack_1(int n, int c)//动态规划+朴素的穷举思想 { for (int i=1; i<=n; i++) { for (int j=1; j<=c; j++) { int bestP = 0; for (int k=0; k*W[i]<=j; k++) //语句1 { if (bestP < B1[i-1][j-k*W[i]] + k*P[i]) bestP = B1[i-1][j-k*W[i]] + k*P[i]; } B1[i][j] = bestP; } } return B1[n][c]; } 问题1:语句1能否改为:for (int k=j/W[i]; k>=0; k--)?为什么? 参考答案: 问题1:可以,因为语句1所在循环体的作用是穷举装入第i种物品的数量,并计算出能获得的最大价值,循环变量k值递增或递减均可,实际上修改后的语句1效率更高。 算法2:动态规划:二维数组存储记录,需要用到全局变量W[], P[], 另有B2[MAXN+1][]默认初始化为0。 int CompletePack_2(int n, int c)//动态规划:F[i][j] = max(F[i-1][j], F[i][j-W[i]] + P[i]) { for (int i=1; i<=n; i++) { for (int j=1; j<=c; j++) //语句1 { if (j < W[i]) B2[i][j] = B2[i-1][j]; else B2[i][j] = max(B2[i-1][j], B2[i][j-W[i]] + P[i]); //语句2 } } return B2[n][c]; } 问题1:语句1能否改为:for (int j=c; j>0; j--) ?为什么? 问题2:语句2能否改为:B2[i][j] = max(B2[i-1][j], B2[i-1][j-W[i]] + P[i]);?为什么? 参考答案: 问题1:不能。因为B2[i][j] = max(B2[i-1][j], B2[i][j-W[i]] + P[i]);即B2[i][j]的值可能由B2[i][j-W[i]]来决定,也就是说第i行的某个元素可能由同一行中列坐标j较小的元素来决定,故应该先求出第i行列坐标j较小的元素,再求j较大的元素,则循环变量j要递增。 问题2:不能。因为第i件物品可以装无限次,故做出本次选择之前,背包中可能已经装有第i件物品了,即使本次选择不装第i件物品,也是在给定i个物品的条件下做出的选择,而不是在给定i-1个物品的条件下做出的选择。 若把语句2改为B2[i][j] = max(B2[i-1][j], B2[i-1][j-W[i]] + P[i]);则变成0-1背包问题了。 算法3:动态规划:使用2个一维数组存储记录,需要用到全局变量W[], P[], 另有pre[]和cur[]均初始化为0。 int CompletePack_3(int n, int c)//优化的动态规划算法,用2个一维数组代替二维数组 { for (int i=1; i<=n; i++) { for (int j=1; j<=c; j++) { if (j < W[i] || pre[j] > cur[j-W[i]] + P[i]) cur[j] = //语句1 else cur[j] = //语句2 } for (int j=0; j<=c; j++) { pre[j] = cur[j]; } } return cur[c]; } 问题1:将语句1和语句2补充完整。 参考答案: 问题1:语句1:cur[j] = pre[j]; 语句2:cur[j] = cur[j-W[i]] + P[i]; 算法4:优化的动态规划算法,一维数组存储记录,需要用到全局变量W[], P[], 另有B4[]初始化为0。 int CompletePack_4(int n, int c)//进一步优化的动态规划算法,用1个一维数组代替二维数组 { for (int i=1; i<=n; i++) { for (int j=W[i]; j<=c; j++) //语句1 { if (B4[j] < B4[j-W[i]] + P[i]) B4[j] = //语句2 } } return B4[c]; } 问题1:语句1能否改为:for (int j=c; j>=W[i]]; j--)?为什么? 问题2:将语句2补充完整。 参考答案: 问题1:不能。我们与用二维数组记录结果的算法做比较: 在二维数组算法中B2[i][j] = max(B2[i-1][j], B2[i][j-W[i]] + P[i]); 即B2[i][j]的值可能由B2[i][j-W[i]]来决定,也就是说第i行的某个元素可能由同一行中列坐标j较小的元素来决定,故应该先求出第i行列坐标j较小的元素,再求j较大的元素,则循环变量j要递增。 若我们用一维数组B4[j]代替B2[i][j],则只记录了列坐标j,未记录行坐标i,在同一行中,必须先求出列坐标j较小的元素,再求j较大的元素。这样先改变的是下标j较小的元素,再利用它来求j较大的元素。故在内层循环中,应该让循环变量j的值从小到大递增。 说明:完全背包问题与0-1背包问题的一维数组算法除了内层循环变量j的值变化方向相反以外,其他部分代码完全相同。但就是这点不同,造成逻辑上的巨大差异。 问题2:语句2:B4[j] = B4[j-W[i]] + P[i]; 拓展练习:通过算法2的CompletePack_2()函数,我们用二维数组B2[][]记录了各种解的信息,现在请你根据B2[][]记录的信息,设计一个递归函数void Show(int i, int j);//i和j分别表示正在处理的第i个物品和此时背包的剩余容量。 输出物品装载情况,按照编号顺序,递归输出装入背包的物品信息(编号,数量,重量,价值)。 参考答案: void Show(int i, int j) //i和j分别表示正在处理的第i个物品和此时背包的剩余容量 { if (j == 0 || i == 0) return; if (B2[i][j] == B2[i-1][j]) { Show(i-1, j); //未装载物品i } else { for (int k=j/W[i]; k>0; k--) { if (B2[i][j] == B2[i-1][j-k*W[i]] + k*P[i]) //装载了k个物品i { Show(i-1, j-k*W[i]); cout << i << ": " << k << " " << W[i] << " " << P[i] << endl; return; } } } } 课后练习: 练习1: 1426_找零钱的程序 描述:现在假设你是个店员,为了方便/准确/最优的找零钱,你设计了一个程序.该程序应该实现如下功能: 第一行输入客户所给你金额 第二行输入客户消费的总金额 第三行输出应找的总零钱是多少 第四行输出各种面额的张数(总金额之和要与第三行的数相等,并且要求货币总张数是最少的方案输出) 注:为了简单,假设上述中的金额都是整数,现规定金额的面值为100,50,20,10,5,1元.并且假定客户的金额总是大于所需支付的总金额. 数据类型用int整数表示. 输入描述 Input Description 第一行输入一个整数(表示客户所付的金额),如100 第二行输入一个整数(表示商品的总计金额),如25 输出描述 Output Description 第一行输出 应找的零钱,如75 第二行输出 金额面值1*张数1+金额面值2+张数2+....+金额面值N*张数N=总零钱数:.(面值较大的零钱优先排在前面,如50元比20元大,应排在前面) 样例输入 Sample Input 样例输入1: 100 25 样例输入2: 95 2 样例输出 Sample Output 样例输出1:(面值较大的零钱优先排在前面) 75 50*1+20*1+5*1=75 样例输出2:(面值较大的零钱优先排在前面) 93 50*1+20*2+1*3=93 练习2:整数划分问题 描述: 所谓整数划分,是指把一个正整数n写成如下形式: n=m1+m2+...+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,...,mi}为n的一个划分。 如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。 这里我们记n的m划分的个数为f(n,m); 例如当n=4时,有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1}; 注意4=1+3 和 4=3+1被认为是同一个划分。 该问题是求出n的所有划分个数,即f(n, n)。 提示:这是一个典型的完全背包问题,只不过不是求最优解,而是求所有可能的组合,故需要累计所有的组合。
转载请注明原文地址: https://www.6miu.com/read-26341.html

最新回复(0)