問題描述:
有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]);
}
}
