问题描述:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
完全背包问题的特点是:每个物品可以放入的个数是无限的。这不同于01背包问题的每个物品只有一个的特点
原理和01背包问题差不多,所以解题的步骤也差不多。
首先,定义状态d[i][j]:d[i][j]表示前i个物品装到剩余体积为j的背包中的最大价值
接下来确定状态转移方程,先来回顾一下01背包问题的状态转移方程:
d[i][j]就是考虑第i个物品要不要放入背包:
- 首先,若第i个物品的体积w[i]大于剩余可用体积j的话,那自然不可以放入,那么d[i][j]就等于d[i-1][j],因为第i个物品无法放入,那么d[i][j]的最大价值就和d[i-1][j]相同
- 若第i个物品的体积w[i]小于等于剩余可用体积j的话,那么可以选择放入,可以选择不放入,这时候就要看放入和不放入时哪个的最大价值更高。也就是上面方程中max{d[i-1][j],d[i-1][j-w[i]+v[i]]}
由上面的01背包问题的状态转移方程,我们很容易得到完全背包问题的状态转移方程:
怎么理解呢?k表示第i个物品放入几个,那么k*w[i]就是k个i物品占据的体积,k*v[i]就是k个i物品的价值
k取值是有限制条件的,即0<=k*w[i]<=j,也就是必须小于剩余可用体积j
当k=0时,就是01背包问题中的不放入第i个物品
当k=1是,就是01背包问题中的放入第i个物品
当然,在完全背包问题中,k还可以取值2,3,4,….只需要满足0<=k*w[i]<=j即可
根据上面的思路,很容易参考01背包问题的核心代码来写出完全背包问题的核心代码:
for (int i = 1; i <= N; i++) {
int w = sc.nextInt();
int v = sc.nextInt();
for (int j = 0; j <= W; j++) {
for (int k = 0; k*w <= j; k++) {
d[i][j] = d[i-1][j]>d[i-1][j-k*w]+k*v?d[i-1][j]:d[i-1][j-k*w]+k*v;
}
}
}
和01背包问题的核心代码比较,就是多了一个k的for循环
完整代码如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int W = sc.nextInt();
int[][] d = new int[N+1][W+1];
for (int i = 1; i <= N; i++) {
int w = sc.nextInt();
int v = sc.nextInt();
for (int j = 0; j <= W; j++) {
for (int k = 0; k*w <= j; k++) {
d[i][j] = d[i-1][j]>d[i-1][j-k*w]+k*v?d[i-1][j]:d[i-1][j-k*w]+k*v;
}
}
}
System.out.println(d[N][W]);
}
}
测试:
输入:
3 5
3 5
2 10
2 20
输出:
40
代码优化
完全背包问题也可以将二维数组改成一维数组
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int W = sc.nextInt();
int[] d = new int[W+1];
for (int i = 1; i <= N; i++) {
int w = sc.nextInt();
int v = sc.nextInt();
for (int j = W; j >= 0; j--) {
for (int k = 0; k*w <= j; k++) {
d[j] = d[j]>d[j-k*w]+k*v?d[j]:d[j-k*w]+k*v;
}
}
}
System.out.println(d[W]);
}
}
接下来解决一个实际问题:
题目描述:
POJ 2063 Investment
你刚开始拥有金钱 x (x <= 1000 000) 现在有 n (1 <= n <= 10) 种债券可以投资
每种债券的售价均是1000的整数倍且年收益不超过售价的 10% 现在 n 种债券的售价和年收益已给出 问 y 年后你最多拥有多少钱 ( y <= 40)
债券的购买数量不设上限 例如你拥有 2000 元 有一种债券售价 1000 元 年收益是 100 元 那么你可以买 2 份这种债券 一年后你拥有的钱是
2000(两份债券的价值) + 100 * 2(每份债券收益100元) = 2200 每一年收益计算后你可以重新调整你拥有的债券的情况 即把收益较少的债券卖出去用
得到的钱和收益的钱买收益更好的债券
样例:
输入:
1 //有多少组测试数据
10000 4 //10000代表初始金额,4代表4年之后的最大收益
2 //有两种股票
4000 400 //接下来2行,每行分别代表每种股票的价值与年收益
3000 250
输出:
14050
这是一个很明显的完全背包问题
思路分析:
首先,定义状态d[i][j]:d[i][j]表示可购买的债券为前i个可投资金额为j的情况下可获得的最大收益,时间是一年。
接下来定义状态转移方程:
d[i][j] = max{d[i-1][j-k*vest[i]]+k*value[i],d[i-1][j]}
完整代码如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
for (int i = 0; i < sc.nextInt(); i++) {
int money = sc.nextInt();
int year = sc.nextInt();
int N = sc.nextInt();
int[] vest = new int[N+1];
int[] value = new int[N+1];
int[][] d = new int[N+1][100];
for (int j = 1; j < N+1; j++) {
vest[j] = sc.nextInt();
value[j] = sc.nextInt();
}
int maxValue = money;
for (int j = 0; j < year; j++) {
maxValue += investion(maxValue,d,vest,value);
}
System.out.println(maxValue);
}
}
private static int investion(int money,int[][] d,int[] vest,int[] value){
for (int i = 1; i < vest.length; i++) {
for (int j = 0; j <= money; j+=1000) {
int max = 0;
for (int k = 0; k*vest[i] <= j; k++) {
int a = d[i-1][j/1000];
int b = d[i-1][j/1000-k*vest[i]/1000]+k*value[i];
int tmp = a>b?a:b;
max = max>tmp?max:tmp;
}
d[i][j/1000] = max;
}
}
return d[vest.length-1][money/1000];
}
}
因为d[i][j]求的是一年的收益,所以第二年的投资金额就是第一年的本金+收益,第三年的投资金额就是第二年的本金+收益。也就是上面的这几行代码:
int maxValue = money;
for (int j = 0; j < year; j++) {
maxValue += investion(maxValue,d,vest,value);
}
初始投资金额maxValue等于输入的初始金额money,然后循环year次,每次的初始金额等于上一年的本金+上一年的收益。