AcWing-背包问题汇总-java实现

1. 01背包问题

有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。

第 i件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品数量背包容积

接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积价值

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

思路

(1)状态f[i][j]定义:前 i 个物品,背包容量 j 下的最优解(最大价值):
当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,则需要 N 次决 策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前 i 个物品最优解即为前 i−1 个物品最优解:

 对应代码:f[i][j] = f[i - 1][j]。
(3)当前背包容量够,可以选,因此需要决策选与不选第 i 个物品:
 选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
 不选:f[i][j] = f[i - 1][j] 。
 我们的决策是如何取到最大价值,因此以上两种情况取 max() 。

代码实现

import java.util.*;

public class Main{
  /*
   * dp[i][j] 表示为前i个物品j重量的最大价值 
   *当v[i]>j 剩下容量放不进去 直接继承 前i-1件的最大价dp[i][j]=dp[i-1][j] 
   * 当v[i]<=j 有两种选择 当前物品放进去和不放进去 
   *放进去dp[i][j]=dp[i-1][j-v[i]]+w[i]; 不放进去 dp[i][j]=dp[i-1][j]
   */

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int V = sc.nextInt();
    int[] v = new int[N + 1];
    int[] w = new int[N + 1];

    for (int i = 1; i <= N; i++) {
      v[i] = sc.nextInt();
      w[i] = sc.nextInt();
    }
    /*
     * dp[i][j]在两种情况中选择比较大的情况作为当前的最优解; if(j >= v[i]): dp[i][j] = max(dp[i-1][j],
     * dp[i-1][j-v[i]] + w[i]) else: dp[i][j] = dp[i-1][j]
     */
    int[][] dp = new int[N + 1][V + 1];
    dp[0][0] = 0;
    for (int i = 1; i <= N; i++) {
      for (int j = 0; j <= V; j++) {
        // 当前背包容量能装进第i个物品,则价值选和不选去Max值
        if (j >= v[i]) {
          dp[i][j] = Math.max(dp[i - 1][j - v[i]] + w[i], dp[i - 1][j]);
        } else {
          // 当前背包容量装不进第i个物品,则价值等于前i-1个物品
          dp[i][j] = dp[i - 1][j];
        }
      }
    }
    System.out.println(dp[N][V]);
  }
}

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {

    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int V = sc.nextInt();
    int[] v = new int[N + 1]; 
    int[] w = new int[N + 1]; 

    for (int i = 1; i <= N; i++) {
      v[i] = sc.nextInt();
      w[i] = sc.nextInt();
    }

    int[] dp = new int[V + 1];
    dp[0] = 0;
    for (int i = 1; i <= N; i++) {
      for (int j = V; j >= v[i]; j--) {
        dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
      }//若j从小到大,f[j-v[i]]中,由于j-v[i]小于j,f[j-v[i]]已经在i这层循环被计算了,而我们想要的f[j-v[i]]应该是i-1层循环里面的,所以j从大到小的话保证此时的f[j-v[i]]还未被计算,也就是第i-1层的数据
    }
    System.out.println(dp[V]);
  }

}

2. 完全背包问题

有 N 件物品和一个容量是 V的背包。每种物品都有无限件可用。

第 i件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品数量背包容积

接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积价值

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

思路

在这里插入图片描述
在这里插入图片描述

代码实现

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int V = sc.nextInt();
    int[] v = new int[N + 1]; 
    int[] w = new int[N + 1]; 

    for (int i = 1; i <= N; i++) {
      v[i] = sc.nextInt();
      w[i] = sc.nextInt();
    }

    int[][] dp = new int[N + 1][V + 1];
    for (int i = 1; i <= N; i++) {
      for (int j = 0; j <= V; j++) {
        dp[i][j] = dp[i - 1][j];
        if (j >= v[i]) {
          // 计算了从0~V 的所有体积转移的可能结果,所以不用再每次枚举选第i个物品几个
          // 直接对应了每个物品可选无限次
          dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
      }
    }
    System.out.println(dp[N][V]);
  }

}

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int V = sc.nextInt();
    int[] v = new int[N + 1];
    int[] w = new int[N + 1];

    for (int i = 1; i <= N; i++) {
      v[i] = sc.nextInt();
      w[i] = sc.nextInt();
    }

    int[] dp = new int[V + 1];

    for (int i = 1; i <= N; i++) {
      // 逆序是为了保证更新当前状态时,用到的状态是上一轮的状态,保证每个物品只有一次或零次;
      // 在这里,因为每个物品可以取任意多次,所以不再强求用上一轮的状态,即本轮放过的物品,在后面还可以再放;
      for (int j = v[i]; j <= V; j++) // 注意了,这里的j是从小到大枚举,和01背包不一样
        dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
    }
    System.out.println(dp[V]);
  }
}

3. 多重背包问题I

有 N 种物品和一个容量是 V 的背包。

第 i种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤100
0<vi,wi,si≤100

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

思路

在这里插入图片描述

代码实现

import java.util.Scanner;
 public class Main{
     public static void main(String[]args){
         Scanner sc=new Scanner(System.in);
         int N = sc.nextInt();
         int V = sc.nextInt();
         int[]v=new int[N+1];
         int[]w=new int[N+1];
         int[]s=new int[N+1];
         int dp[]=new int[V+1];
         for(int i=1;i<=N;i++){
              v[i]=sc.nextInt();
              w[i]=sc.nextInt();
              s[i]=sc.nextInt();
         }
         for(int i=1;i<=N;i++){
             for(int j=V;j>=1;j--){
                 for(int k=1;k<=s[i]&&k<=j/v[i];k++){
                     dp[j]=Math.max(dp[j],dp[j-k*v[i]]+k*w[i]);
                 }
                 
             }
         }
         System.out.print(dp[V]);
     }
 }

4. 多重背包问题 II

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

提示:

本题考查多重背包的二进制优化方法。

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

思路

这道题是二进制差分的方法来优化的多重背包问题。
这里讲一下,为什么可以那样拆分:
比如说s=127,分成几个数,按照分的思路分出来就是1,2,4,8,16,32,64大家可以试一下,这里的几个数,可以凑出所有0≤k≤127的所有数,而背包问题会选择最优解。
最后按0/10背包问题的思路进行枚举即可。

代码实现

import java.util.Scanner;
public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner([System.in](http://System.in));
    int n = sc.nextInt();
    int m = sc.nextInt();
    int[] f = new int[m + 1];
    for (int i = 0; i < n; i++) {
      int v = sc.nextInt();
      int w = sc.nextInt();
      int s = sc.nextInt();
      // 0-1背包是特殊的多重背包问题
      // 二进制优化
      // k是每种物品的个数
      for (int k = 1; k <= s; k *= 2) {
        // 使用01背包模板处理
        for (int j = m; j >= k * v; j--) {
          f[j] = Math.max(f[j], f[j - k * v] + k * w);
        }
        // 减去上一个物品的个数
        s -= k;
      }
      // 剩下的个数不是2的幂
      if (s > 0) {
        for (int j = m; j >= s * v; j--) {
          f[j] = Math.max(f[j], f[j - s * v] + s * w);
        }
      }
    }
    System.out.println(f[m]);
  }
  }

5. 混合背包问题

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 si次(多重背包);

每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。

接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。

  • si=−1 表示第 i种物品只能用1次;
  • si=0 表示第 i 种物品可以用无限次;
  • si>0 表示第 i种物品可以使用 si 次;
输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000

输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8

思路

在这里插入图片描述

代码实现

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(), V = sc.nextInt();
        int[] v = new int[N + 1];
        int[] w = new int[N + 1];
        int[] s = new int[N + 1];
        for(int i = 1; i <= N; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
            s[i] = sc.nextInt();
        }
        int[] dp = new int[V + 1];
        for(int i = 1; i <= N; i++) {
            if(s[i] == -1 || s[i] > 0) {
               int n = s[i] == -1 ? 1 : s[i];
                for(int k = 1; k <= n; k *= 2){
                    for(int j = V; j >= 1; j--) {
                        if(j - v[i] * k >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]*k] + w[i]*k);
                    }
                    n -= k;
                }
                if(n != 0) {
                    for(int j = V; j >= 1; j--) {
                        if(j - v[i] * n >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]*n] + w[i]*n);
                    }
                }
            }
            else {
                for(int j = 1; j <= V; j++) if(j - v[i] >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[V]);
    }
}

6. 二维费用的背包问题

有 N件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式

第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i件物品的体积、重量和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N≤10000
0<V,M≤1000
0<vi,mi≤1000
0<wi≤10000

输入样例

4 5 6

1 2 3

2 4 4

3 4 5

4 5 6

输出样例:

8

思路

在这里插入图片描述

代码实现

import java.util.Scanner;
 
public class Main {
    public static void main(String[] args) {
        int [][] bags=new int[1010][1010];
        Scanner scanner=new Scanner(System.in);
        int num=scanner.nextInt();
        int volume=scanner.nextInt();
        int weight=scanner.nextInt();
        int v[]=new int[1010],w[]=new int[1010],val[]=new int[1010];
        for (int i=0;i<num;i++){
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
            val[i]=scanner.nextInt();
        }
 
        for (int i=0;i<num;i++){
            for (int j=volume;j>=v[i];j--){
                for (int k=weight;k>=w[i];k--){
                    bags[j][k]=Math.max(bags[j][k],bags[j-v[i]][k-w[i]]+val[i]);
                }
            }
        }
        System.out.println(bags[volume][weight]);
 
    }
}

7. 分组背包问题

有 N组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i是组号,j是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N组数据:

  • 每组数据第一行有一个整数 Si,表示第 i个物品组的物品数量;
  • 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i个物品组的第 j个物品的体积和价值;
输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤100
0<Si≤100
0<vij,wij≤100

输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:

8

思路

在这里插入图片描述

代码实现

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int g = sc.nextInt();
        int w = sc.nextInt();
        int dp[] = new int[w+1];
        for(int i = 0;i < g;i++){
                int m = sc.nextInt();
                int v[] = new int[m];
                int w[] = new int[m];
                for(int s = 0;s < m;s++){
                    v[s] = sc.nextInt();
                    w[s] = sc.nextInt();
                }
                for(int j = w;j >= 0;j--){
                    for(int k = 0;k < m;k++)
                        if(j >= v[k])
                            dp[j] = Math.max(dp[j],dp[j-v[k]]+w[k]);
                }
        }
        System.out.println(dp[w]);
    }
}

8. 背包问题求方案数

有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示 方案数 模 10^9+7 的结果。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
思路在这里插入图片描述在这里插入图片描述

代码实现

import java.util.Scanner;
public class Main {
  public static void main(String[] args) {
    int mod = 1000000007;
    Scanner sc = new Scanner(System.in)
    int N = sc.nextInt(), V = sc.nextInt(), mmax = 0;
    int[] f = new int[V + 1];
    int[] g = new int[V + 1];
    for(int i = 1; i <= V; i++) f[i] = -10000;
    g[0] = 1;
    for(int i = 1; i <= N; i++) {
      int v = sc.nextInt(), w = sc.nextInt();
      for(int j = V; j >= v; j--) {
        int left = f[j], right = f[j - v] + w;
        f[j] = Math.max(left, right);
        if(left < right) g[j] = g[j - v];
        else if(left == right) g[j] = g[j] + g[j - v];
        if(g[j] >= mod) g[j] -= mod;
      }
    }
    for(int n : f) mmax = Math.max(mmax, n);
    int res = 0;
    for(int i = 0; i <= V; i++) {
      if(f[i] == mmax) res+=g[i];
    }
    System.out.println(res);
  }
}

9. 背包问题求具体方案

有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N1…N。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4

思路

在这里插入图片描述

代码实现

import java.util.*;
public class Main{
    static int N = 1010;
    static int[][] f = new int[N][N];
    static int[] v = new int[N];
    static int[] w = new int[N];
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int V = scan.nextInt();
        for(int i = 1 ; i <= n ; i++){
             v[i] = scan.nextInt();
             w[i] = scan.nextInt();
        }
        for(int i = n ;  i >= 1 ;i-- ){
            for(int j = 0 ; j <= V ; j ++){
                f[i][j] = f[i+1][j];
                if(j >= v[i] ) f[i][j] = Math.max(f[i][j],f[i+1][j-v[i]]+w[i]);
            }
        }
        int j = V ;
        //f[1][V] 是最大值
        for(int i = 1 ; i <= n ; i++ ){
            if(j >= v[i] && f[i][j] == f[i+1][j-v[i]]+w[i] ){
                System.out.print(i + " ");
                j -= v[i] ;
            }
        }
    }
}

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wen wef

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值