问题描述:
有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。1≤n≤100,1≤wi,vi≤100,1≤W≤10000。注意:物品的数量是无限的,可以无限拿取。
在前面学习的基础上,其实这道题目并不难,首先普通的动规思路,在前面的01背包的基础上,因为物品的数量是无限的,那么递推的思路就是这个物品可以选取0,1...n个,以物品的质量为{7, 4, 3, 2},价值为{9, 5, 3, 1}为例。
简化思路:普通的动规采用的策略是对于当前的物品我可以拿取0,1,2,3,4...k倍,然后剩余的容量拿取的最大价值我们是从上一行对应的剩余质量的列中去寻找的,依次比较拿取的k倍的这些物品的价值来得到当前dp数组中对应的值,但是我们可以通过推导发现对于当前的物品我们是可以选择不拿的,那么价值为上一行同一列的价值,但是也可以拿取一个,那么剩余的质量我们是可以在同一行对应的剩余质量的列中去寻找,把拿取一个物品的价值与同一行对应的剩余质量的列中的dp数组的值加起来比较两者的最大值即为dp数组当前的值。
为什么可以这样做呢?我们发现当我们拿取了一个物品之后,那么剩余质量的物品的dp数组表示的意思也是对当前同样的物品可以拿取0,1,2,3,4...k倍,那么当我们拿取一个的时候相当于拿取了当前物品的1,2,3,4,k...倍,那么这种情况又是回到了我们之前的情况所以两种方法求解出来的最大价值是一样的,但是这种方法可以省略了一个for循环可以降低时间的复杂度,这样两个for循环就可以解决问题,如果是普通的动规思路则需要三个for循环。
代码:
public class 完全背包 { static int[] values = { 9, 5, 3, 1 }; static int[] weights = { 7, 4, 3, 2 }; static int n = 4; static int total = 10; public static void main(String[] args) { dp(); // 输出12 } static int[][] state = new int[n][total + 1];// 不同的物品范围下不同的容量能装出来的最大价值 /*** * 递推 */ static void dp() { // row 行号 int row = n - 1; // v是容量 int v = 1; int w = weights[row]; for (; v < total + 1; v++) { state[row][v] = values[row] * v / w; } for (int r = row - 1; r >= 0; r--) { // r 当前处理的行,也是当前处理的物品 w = weights[r]; for (int c = 1; c < total + 1; c++) { // c 当前处理的容量 // 能抓 if (c >= w) { // 要抓 int v1 = values[r] + state[r][c - w]; // 不抓 int v2 = state[r + 1][c]; state[r][c] = Math.max(v1, v2); } else { // 不能抓 state[r][c] = state[r + 1][c]; } } } System.out.println(state[0][total]); } }