01背包:模板题+实战题

一、01背包的定义

我们有一个背包,背包的容积有限,最多只能装下总体积为V的物品。现在给定我们N个物品,第i个物品的体积vi,对应的价值是wi( 1 ≤ i ≤ N 1 \leq i \leq N 1≤i≤N)。每个物品有且仅有一个。要求我们再背包容量允许的范围内,选取物品,使得总价值最大。(注意每一个物品要么选,要么不选,这就是 0 1 背包名字的由来

二、模板题

1、题目说明

牛客题目连接

解题思路

第一问

求这个背包至多能装多大价值的物品?

第一问实际上就是经典的01背包问题。给定一个背包的体积V,在n个不同的物品中选取最大化的价值。(对于一个物品,只有选或者不选两种可能

  1. 状态表示
java 复制代码
       int n=in.nextInt();//物品数量
       int V=in.nextInt();//背包的总体积
       //dp[i][j]前i个物品中选择,累计总体积不大于j获取的最大价值
       int[][] dp=new int[n+1][V+1];
  1. 状态转移方程(dp[i][j]的状态转移)
java 复制代码
       //不选第i个物品 
       dp[i][j]=dp[i-1][j]; //(体积j是不变的如果不选第i个物品)
       
       //选第i个物品(注意如果选第i个物品,必须j>=v[i],即当前体积j必须保证能够放进体积为v[i]的物品!!!)
       dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]);
       
  1. 初始化dp数组

    根据状态转移方程,我们只用初始化好上一行,也就是dp[0][j]即可。
    dp[0][j]表示在前0个物品中选择体积不大于j的最大总价值,显然0个物品,自然最大价值是0,所以dp[0][j]初始化成0,即dp二维数组的第0行初始化成0

  2. 返回值

    直接打印,dp[n][V]:前n个物品中选取,容量不大于背包的总容量V的最大价值。

  3. AC代码_Java

java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {


        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int V = scanner.nextInt();

        //dp[i][j]表示容量不大于j,最大价值是i
        int[][] dp = new int[n + 1][V + 1];

        //第i个物品的容量(1开始)
        int[] v = new int[n + 1];

        //第i个物品的价值(1开始)
        int[] w = new int[n + 1];

        //获取每个物品的容量、价值
        for (int i = 1; i <= n; i++) {
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }

        //第一道题目的转移方程
        //不选第i个物品 dp[i][j]=dp[i-1][j];
        //选第i个物品(j-v[i]>=0才允许)dp[i][j]=dp[i-1][j-v[i]]+w[i]

        //第一道题目不用初始化(自动是0)
        //返回值就是dp[n][V];

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - v[i] >= 0)dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[n][V]);
    }
}

第二问

若背包恰好装满,求至多能装多大价值的物品?
注意请先了解了第一问的做法,再来看第二问,因为第二问是基于第一问演化过来的。

这个题目其实只需要稍微修改一下状态表示即可。之前的状态表示:
dp[i][j]前i个物品中选择,累计总体积不大于j获取的最大价值

当前我们要求恰好装满背包,能装的最大价值,那么状态表示可以修改成:
dp[i][j]前i个物品中选择,累计总体积恰好是j获取的最大价值

值得注意的是,有时可能凑不出恰好为j体积的情况,怎么办呢?既然凑不出,我们可以直接将其赋值成-1,代表这种状态不可能出现。

随即,我们还要修改一下转移方程其实和第一问大差不差:

java 复制代码
  //不选第i个物品 
  dp[i][j]=dp[i-1][j];
 
  //选第i个物品,前一个状态必须能够凑齐对应的体积!!!
   if(j-v[i]>=0&&dp[i-1][j-v[i]]!=-1)
   dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);

最终AC代码_Java:

java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {


        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int V = scanner.nextInt();

        //dp[i][j]表示容量不大于j,最大价值是i
        int[][] dp = new int[n + 1][V + 1];

        //第i个物品的容量(1开始)
        int[] v = new int[n + 1];

        //第i个物品的价值(1开始)
        int[] w = new int[n + 1];

        //获取每个物品的容量、价值
        for (int i = 1; i <= n; i++) {
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }

        //第一道题目的转移方程
        //不选第i个物品 dp[i][j]=dp[i-1][j];
        //选第i个物品(j-v[i]>=0才允许)dp[i][j]=dp[i-1][j-v[i]]+w[i]

        //第一道题目不用初始化(自动是0)
        //返回值就是dp[n][V];

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - v[i] >= 0)dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[n][V]);

        //把dp表清空
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                dp[i][j] = 0;
            }
        }

        for (int j = 1; j <= V; j++)dp[0][j] = -1;

//第二题记得初始化,价值一直是0,那么除了00 其他-1
        //第二道题目的转移方程(选i个物品,恰好容量达到j的最大价值)规定dp如果是-1,代表这种情况不可能发生
        //不选第i个物品 dp[i][j]=dp[i-1][j];
        //选第i个物品 (j-v[i]>=0&&dp[i-1][j-v[i]]!=-1才允许)dp[i][j]=dp[i-1][j-v[i]]+w[i]
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - v[i] >= 0&&dp[i-1][j-v[i]]!=-1)dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[n][V]==-1?0:dp[n][V]);
    }
}

三、实战题

题目

蓝桥云课题目连接

解题思路

乍一看,好像没有思路,它让我们求平分,和我01背包有什么关系?我们其实可以换一个角度思考。我们先计算所有宝藏的总价值sumsum/2是奇数,直接打印no。因为题目说了,每个宝藏是不能被拆分的,只能拿,或者不拿。

如果不是奇数,我们把sum/2想象成01背包的背包容量(即宝藏总价值的一半 )。每个宝藏想象成一个个物品,宝藏的价值就是物品的价值。那么在容量不大于sum/2的情况下取获取物品,使得选取的总价值恰好等于sum/2不就说明可以平分吗?

于是通过这个思路,我们就把这个题转化成了模板题中的第二问!

AC_Code_Java

java 复制代码
   import java.util.*;
   
    public class Main {
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            int n=scan.nextInt();
            int[] arr=new int[n+1];
            int sum=0;
            for(int i=1;i<=n;i++){
                arr[i]=scan.nextInt();//第i个宝藏(物品)的价值
                sum+=arr[i];
            }
            scan.close();
            if(sum%2==1){
                System.out.print("no");
                return;
            }

            //sum一定是偶数
            //dp[i][j]前i个物品中选,体积恰好是j的最大价值
            int[][] dp=new int[n+1][sum/2+1];

            dp[0][0]=0;
            for(int i=1;i<=sum/2;i++)dp[0][i]=-1;//0个物品,凑不出大于0的价值,所以设置成无效值

            //填写dp表
            for(int i=1;i<=n;i++){
                for(int j=1;j<=sum/2;j++){
                    dp[i][j]=dp[i-1][j];
                    if(j>=arr[i]&&dp[i-1][j-arr[i]]!=-1){
                        dp[i][j]=Math.max(dp[i][j],dp[i-1][j-arr[i]]+arr[i]);
                    }
                }
            }

            System.out.print(dp[n][sum/2]==-1?"no":"yes");

        }
    }
相关推荐
Smark.10 分钟前
Gurobi基础语法之 addConstr, addConstrs, addQConstr, addMQConstr
算法
S-X-S28 分钟前
算法总结-数组/字符串
java·数据结构·算法
linwq832 分钟前
设计模式学习(二)
java·学习·设计模式
桦说编程1 小时前
CompletableFuture 超时功能有大坑!使用不当直接生产事故!
java·性能优化·函数式编程·并发编程
@_@哆啦A梦1 小时前
Redis 基础命令
java·数据库·redis
Joyner20181 小时前
python-leetcode-从中序与后序遍历序列构造二叉树
算法·leetcode·职场和发展
因兹菜1 小时前
[LeetCode]day9 203.移除链表元素
算法·leetcode·链表
LNsupermali1 小时前
力扣257. 二叉树的所有路径(遍历思想解决)
算法·leetcode·职场和发展
雾月551 小时前
LeetCode LCR180文件组合
算法·leetcode·职场和发展
萌の鱼1 小时前
leetcode 2080. 区间内查询数字的频率
数据结构·c++·算法·leetcode