01背包--java(蓝桥杯算法提高ADV-144)

比赛迫在眉睫,而我依旧这么菜。。

蓝桥杯对dp情有独钟,临阵磨枪不快也光,所以今天就来补一下dp,首先就是经典的背包问题。

(我还是太弱了,这状态转移方程想了很久才做出来......在excel上一个一个推着去理解的....)

讲的可能会比较啰嗦,主要是为了照顾跟我一样的新手小伙伴,当然也是自己推导解题方法的过程

上题目!

资源限制

内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s

问题描述

  给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.

输入格式

  输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
  以后N行每行两个数Wi和Vi,表示物品的重量和价值

输出格式

  输出1行,包含一个整数,表示最大价值。

样例输入

3 5
2 3
3 5
4 7

样例输出

8

数据规模和约定

  1<=N<=200,M<=5000.

老规矩,先整理一下思路。

我们用 数组W 存物品的重量,用 数组V 存物品的价值,一个M(int)代表背包容量

首先说明一下,用dp就是为了优化时间,在数据量大的情况下爆搜容易超时,那如果用枚举呢,这里想说的是,所有枚举都是错误的(看了一个b站博主的解释),为什么呢,接下来举三个例子

1.按价值大的优先放入背包

M = 3

W[] = {1,2,3}

V[] = {2,2,3}

枚举结果:2 实际结果:3

解读:优先选择价值高的,会选择3号物品,此时背包已经满了不能继续装了,实际是要选择1号与2号为最优解(下面内容同理,就不需要解读了)

2.优先放入重量小的物品

M = 3

W[] = {1,2,3}

V[] = {1,2,4}

枚举结果:3实际结果:4

3.优先选择性价比最高的(即单位容量的价值)

M = 4

W[] = {2,2,3}

V[] = {2,2,5}

性价比:1     1    0.6

枚举结果:4实际结果:5

看出为什么枚举行不通了吧,因此就可以知道dp的重要性

这里开始思路讲解

我们设一个数组dp,设 i 为行,j 为列,我们用 i 表示挑选前 i 个物品,j 表示背包大小,而数组元素 dp[i][j] 则代表在只挑选前 i 件物品的条件下重量为 j 的最大价值。

那接下来就是每个物品选与不选的问题了,选第 i 件物品就是dp[i][j]=dp[i-1][j-W[i]]+V[i] (当背包容量为j的情况下)

即选的情况下,第 i 件物品的价值等于第 i 件物品的价值 加上 j 减去 第 i 件物品的重量 得到的剩余可以装的重量在选i-1件物品中可以得到的最大价值

不选即 dp[i][j] = dp[i-1][j]  ,即直接继承上一个状态,就是选前 i-1 件物品的最大价值

关于选的问题可能会有新人看着比较绕,这里再仔细说一下

比如当前选择的是 i 件物品,背包容量为 j ,此物品重量为 x ,当装下 i 件物品时背包容量剩 j - x ,那这个j - x 容量能装下的物品价值加上 第 i 件物品的价值不就是 选择 i 件物品的价值了吗。

附上excel表格推测图 与 测试运行结果

 当i = 0 , j = 0 时初始化全部为0,第一方便状态转移,第二当选0件物品跟背包容量0当然价值都是0了。

 接下来上代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();//n件物品
        int m = scan.nextInt();//背包最大容量
        int[] W = new int[n+1];//储存物品所占空间
        int[] V = new int[n+1];//物品价值

        /*
        用i表示装前i件物品,j表示背包容量
         */
        int dp[][] = new int[n+1][m+1];//状态转移

        for(int i = 1 ; i<=n ; i++){
            W[i] = scan.nextInt();
            V[i] = scan.nextInt();
        }
        for(int i = 1 ; i<=n ; i++){//从选择前1件物品遍历
            for(int j = 1 ; j<=m ; j++){//从容量为1时遍历
                /*
                 因为可能出现第i件物品容量要大于背包容量的问题,此时背包装不下第i件物品
                 装不下就是不选,直接继承上一个状态
                 */
                if(j-W[i]>=0){
                    dp[i][j] = Math.max(dp[i-1][j-W[i]]+V[i],dp[i-1][j]);
                }else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
//        调试结果是否与推测相同
//        for(int i = 0 ; i<=n ; i++){
//            System.out.println(Arrays.toString(dp[i]));
//        }
        System.out.println(dp[n][m]);
    }
}

其实通过以上内容我们可以看出来,当前状态 i 只与上一个状态  i - 1有关,我们完全可以用一维写法,用循环不断遍历更新最优解即可,此时 i 只有一层了,j 不变。

一维即优化内存,状态转移方程:选,dp[j] = dp[j-W[i]] + V[i]      不选 ,dp[j] 不变

上代码

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int M = scan.nextInt();

        int W[] = new int[n];
        int V[] = new int[n];
        int dp[] = new int[M+1];
        for(int i = 0 ; i<n ; i++){
            W[i] = scan.nextInt();
            V[i] = scan.nextInt();
        }
        for(int i = 0 ; i<n ; i++){
            for(int j = M ; j>=W[i] ; j--){//装不下就直接退出循环
                //选与不选的问题
                dp[j] = Math.max(dp[j-W[i]]+V[i],dp[j]);//不断更新最优解
            }
        }
        System.out.println(dp[M]);
    }
}

提交一下,都过了

其实状态转移就这么短,可做起来怎么这么绕呢。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值