一、 01背包
问题描述:给定 N 件物品,物品的重量为 w[i],物品的价值为 c[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 V,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
输入:
4 10
2 1
3 3
4 5
7 9
输出:
12
思路:手推dp表如下:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int V = scanner.nextInt();
int[] w = new int[N+1];
int[] c = new int[N+1];
for(int i=1;i<=N;i++){
w[i] = scanner.nextInt();
c[i] = scanner.nextInt();
}
int[][] dp = new int[N+1][V+1];
for(int i=1; i<=N;i++){ //表示当前有第i个物品可以拿
for(int j=1;j<=V;j++){ //表示当前背包的容量为j
if( j < w[i]){ //如果当前背包容量j放不下重量为w[i]的物体
dp[i][j] = dp[i-1][j]; //不拿i物品,此时背包价值依旧等于成功拿了上一个物体时的价值
}else{
//背包装得下,但是考虑一下值不值得拿,比较一下拿了和没拿前后背包价值的变化,取最大的方法
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j- w[i]]+c[i]);
}
}
}
//输出dp表
for(int i=1;i<=N;i++){
for(int j=1;j<=V;j++){
System.out.print(dp[i][j]+" ");
}
System.out.println(" ");
}
System.out.println(dp[N][V]);
//return dp[N][V];
}
}
进阶:dp表中每一个dp[i]的值只与dp[i-1]有关系,即后无效性原则。因此可以对dp[][]表进行压缩。
思路:每次小循环都把内层循环压缩成一个值而不是一个数组。
此时dp[i]每一次循环都刷新数组,相当于每次循环等于上面dp[][]的一行。值得注意的是由于会刷新覆盖数组,又dp[i]和dp[i-1]相关,因此,只能从后向前递推。
import java.util.Scanner;
class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int V = scanner.nextInt();
int[] w = new int[N+1];
int[] c = new int[N+1];
for(int i=1;i<=N;i++){
w[i] = scanner.nextInt();
c[i] = scanner.nextInt();
}
int[] dp = new int[V+1];
for(int i=1;i<=N;i++){
for(int j=V;j>=1;j--){ //从后往前推
if(j >=w[i]){
dp[j] = Math.max(dp[j], dp[j-w[i]]+c[i]);
}
}
//每次遍历完一次后打印输出一下
for(int i=1;i<=V;i++){
System.out.print(dp[i] +" ");
}
System.print("\n-------------------\n");
}
System.out.println(dp[V]);
}
}
二、完全背包
问题描述:给定 N 种物品每种物品都有无限件可用,物品的重量为 w[i],物品的价值为 c[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 V,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
输入:
4 5
1 2
2 4
3 4
4 5
输出:
10
import java.util.Scanner;
class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int V = scanner.nextInt();
int[] w = new int[N+1];
int[] c = new int[N+1];
for(int i=1;i<=N;i++){
w[i] = scanner.nextInt();
c[i] = scanner.nextInt();
}
int[][] dp = new int[N+1][V+1];
for (int i = 1;i <= N;i++){
for (int v = 1;v <= V;v++){
dp[i][v] = 0;
//最多可以放nCount个物品i
int nCount = v / w[i];
//和01背包的区别就在这里,01背包只有两种状态:放与不放
//而完全背包可以放0到k个物品i,同样是取最大值
for (int k = 0;k <= nCount;k++){
dp[i][v] = Math.max(dp[i][v],dp[i - 1][v - k * w[i]] + k * c[i]);
}
}
}
System.out.println(dp[N][V]);
}
}
三、多重背包
问题描述:给定 N 种物品每种物品i都有s[i]件可用,物品的重量为 w[i],物品的价值为 c[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 V,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
输入:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出:
10
import java.util.Scanner;
/**
* Description:第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
* 接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
* 题目地址:https://www.acwing.com/problem/content/4/
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int N = sc.nextInt(); //物体总类
int V = sc.nextInt(); //背包总重量
int w[] = new int[N + 1]; //重量/体积
int c[] = new int[N + 1]; //价值
int s[] = new int[N + 1]; //数量
int dp[][] = new int[N + 1][V + 1];
for (int i = 1; i < N + 1; i++) {
w[i] = sc.nextInt();
c[i] = sc.nextInt();
s[i] = sc.nextInt();
}
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= 0; j--) {
for (int k = 0; k <= s[i]; k++) {
if (j >= k * w[i]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
}
}
}
}
System.out.println(dp[N][V]);
}
}
进阶成一维dp表:
import java.util.Scanner;
class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int V = scanner.nextInt();
int[] w = new int[N+1];
int[] c = new int[N+1];
int[] s = new int[N+1];
for(int i=1;i<=N;i++){
w[i] = scanner.nextInt();
c[i] = scanner.nextInt();
s[i] = scanner.nextInt();
}
int[] dp = new int[V+1]; //此时dp代表的是背包的价值,所以维度等于背包的容量V
for(int i=1;i<=N;i++){
for(int j=V;j>=1;j--){
for(int k=0;k<=s[i];k++){
if(j >= k*w[i]){
dp[j] = Math.max(dp[j], dp[j - k*w[i]]+k*c[i]);
}
}
}
}
System.out.println(dp[V]);
}
}