【蓝桥杯专项】动态规划_背包问题合集(Java)

news2025/7/6 13:14:18

✨哈喽,进来的小伙伴们,你们好耶!✨

🛰️🛰️系列专栏:【蓝桥杯专项】

✈️✈️本篇内容:动态规划_背包问题合集!

🚀🚀码云仓库gitee:Java数据结构代码存放!

⛵⛵作者简介:一名双非本科大三在读的科班Java编程小白,道阻且长,你我同行!

注:每个题的标题就是原题链接

 一、完全背包问题

问题描述

有 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

思路分析:在上篇博客博主就已经介绍了0/1背包问题,那么完全背包问题跟0/1背包问题的区别就是它考虑前i个物品每个物品可以被多次选择。 那么,老规矩,我们根据状态规划可以得到:

(朴素解法)

1、曲线救国,去掉k个物品i

2、求max:f[i-1,j-k*v[i]]

3、在加回来k个物品i

由此得到我们的状态转移方程为:f[i-1,j-v[i] * k] + k*w[i]

代码实现:

import java.util.*;
public class Main{
       public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [] v = new int[1001];
        int [] w = new int[1001];
        int [][] f = new int[n+1][m+1];
        for (int i = 1; i <= n; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        for (int i = 1; i <=n ; i++) {
            for (int j = 0; j <=m ; j++) {
                for (int k = 0; k * v[i] <=j ; k++) {
                    f[i][j] = Math.max(f[i][j],f[i-1][j-v[i] * k]+ k * w[i]);
                }
            }
        }
        System.out.println(f[n][m]);
        sc.close();
    }
}

运行结果:

 我们可以发现运行时间非常的慢,那么什么原因呢?一方面是因为Java语言实现数据结构本身就比较慢,可以看到我们的代码是通过了3层循环来实现,最坏情况下时间复杂度是(n * v^2),时间复杂度很高,那么是否可以优化该背包问题的代码呢?

优化一:

通过比较0/1背包问题,通过f[i,j] f[i,j-v] 的递推公式我们可以发现,若要求得f[i,j-v]的最大值,只需要求出f[i,j]的最大值在加上一个w[i]即可,具体如下:

 代码实现:

import java.util.*;
public class Main{
       public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [] v = new int[1001];
        int [] w = new int[1001];
        int [][] f = new int[n+1][m+1];
        for (int i = 1; i <= n; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

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

运行结果:

我们发现快了整整2000ms,因为优化后的代码少了一层循环,还是很可观的。

优化二、转换为一维数组来实现

代码实现:

import java.util.*;
public class Main{
       public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [] v = new int[1001];
        int [] w = new int[1001];
        int [] f = new int[m+1];
        for (int i = 1; i <= n; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        for (int i = 1; i <=n ; i++) {
            for (int j = v[i]; j <=m ; j++) {
                f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
        }
    }
    
        System.out.println(f[m]);
        sc.close();
    }
}

注意我们这里的j是从v[i]开始的,即从小到大,与0/1背包问题不同的是0/1背包问题是从大到小,

        for (int i = 1; i <=n ; i++) {
            for (int j = m; j >= v[i] ; j--) {
                f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
            }
        }

原因:简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

二、多重背包问题

问题描述

有 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.*;
public class Main{
       public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [] v = new int[101];
        int [] w = new int[101];
        int [] s = new int[101];
        int [][] f = new int[n+1][m+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 = 0; j <=m ; j++) {
                for (int k = 0; k<=s[i] && k * v[i] <=j; k++) {
                    f[i][j] = Math.max(f[i][j],f[i-1][j-v[i] * k]+ k * w[i]);
                }
            }
        }
        System.out.println(f[n][m]);
    }
}

运行结果:

 那么如果题目给的n,v,s的比较大的话,暴力解法接不能通过了,这就需要我们考虑如何来进行优化。

 二进制优化:

博主对于本题的二进制优化也是第一次接触到,通过查阅了不少资料并且反复观看了讲解视频后的个人总结心得:

1、在完全背包中,我们可以通过两个状态转移方程:

f[i,j]=max(f[i−1,j],f[i−1,j−v]+w,f[i−1,j−2v]+2w,f[i−1,j−3v]+3w,.....)

f[i,j−v]=max(f[i−1,j−v],f[i−1,j−2v]+w,f[i−1,j−2v]+2w,.....)
最后推导出->  f[i][j]=max(f[i−1][j],f[i][j−v]+w)。

2、在多重背包中,我们的推导过程为:

f[i,j] = max(f[i−1,j],f[i−1,j−v]+w,f[i−1,j−2v]+2w,.....f[i−1,j−Sv]+Sw,)
f[i,j−v]= max(f[i−1,j−v],f[i−1,j−2v]+w,.....f[i−1,j−Sv]+(S−1)w,f[i−1,j−(S+1)v]+Sw)

我们可以发现f[i,j−v]的最后一项比f[i,j]多出来一项,这就令人很是难受,对于完全背包问题我们直接对求的结果在加上个w[i]便可求出最大值,那么对于本题我们首先要了解一点:

怎么比完全背包方程比较就多出了一项?

其实,一般从实际含义出发来考虑即可,这里是在分析f[i,j−v]这个状态的表达式,首先这个状态的含义是 从前i个物品中选,且总体积不超过(j-v)的最大价值, 我们现在最多只能选s个物品,因此如果我们选s个第i个物品,那么体积上就要减去 s∗v,价值上就要加上s∗w,那更新到状态中去就是 f[i−1,j−v−s∗v]+s∗w,提取公因式v,也就是f[i−1,j−(S+1)v]+Sw。

什么是二进制优化?

简单来说就是比如系统给出一个数1023,那么按照常规惯例一个一个枚举我们是不是要枚举1023次才能得到这个结果,那么二进制优化是怎么做的呢?比如1023,那么我们可以通过2^0,2^1,2^2 ……2^k,这里的k应该是9,为什么是9呢,因为用2^0~2^9之间的数字拼凑可以任意用来表示1~1023之间的任何数字,这样就大大减少了我们的枚举数量,也就是一个时间复杂度从Si -> logSi的一个提高,这里我参照了网上的一个特别有意思的案列,可以根据这个案列来理解:

原地址:二进制优化思维

二进制优化思维就是:现在给出一堆苹果和10个箱子,选出n个苹果。将这一堆苹果分别按照1,2,4,8,16,.....512分到10个箱子里,那么由于任何一个数字x∈[0,1023] (第11个箱子才能取到1024,评论区有讨论这个)都可以从这10个箱子里的苹果数量表示出来,但是这样选择的次数就是 ≤10次。

比如:

如果要拿1001次苹果,传统就是要拿1001次;二进制的思维,就是拿7个箱子就行(分别是装有512、256、128、64、32、8、1个苹果的这7个箱子),这样一来,1001次操作就变成7次操作就行了。

这样利用二进制优化,时间复杂度就从 O(n^3)
降到O(n^2*logS),从4∗10^9降到了2∗10^7。

代码实现:

import java.util.Scanner;

public class Main {
    /** 多重背包问题
     *
     */
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [] v = new int[25000];
        int [] w = new int[25000];
        int [] f = new int[25000];
        int count = 0;//用来表示还剩几种物品
        int a,b,s,k = 1;
        //k就相当于每次多少个物品
        for (int i = 1; i <= n ; i++) {
            a = sc.nextInt();//体积
            b = sc.nextInt();//价值
            s = sc.nextInt();//数量
            while(k<=s){
                count++;
                v[count] = a*k;
                w[count] = b*k;
                s -= k;
                k *= 2;
            }

            if(s>0){//如果还有多余空间
                count++;
                v[count] = a*s;
                w[count] = b*s;
            }
        }
        n = count;//重置count

        for (int i = 1; i <= n ; i++) {//这里写一遍0/1背包一维实现便可
            for (int j = m; j >=v[i] ; j--) {
                f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
            }
        }

        System.out.println(f[m]);
    }
}

三、分组背包问题

问题描述

有 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.*;
class Main {
    
    public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int maxV = 105;
    int maxN = 105;
    int N, M, V;
    int[] dp = new int[maxV];
    int[] v = new int[maxN];
    int[] w = new int[maxN];
    
    N = sc.nextInt(); V = sc.nextInt();
    for (int i = 0; i < N; i++) {
        M = sc.nextInt();
        for (int j = 0; j < M; j++) {
            v[j] = sc.nextInt();
            w[j] = sc.nextInt();
        }
            for (int j = V; 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[V]);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++笔试强训】第二十二天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

动态内存管理

目录 内存中的栈区和堆区 malloc free calloc realloc 内存中的栈区和堆区 我们知道php的底层是C (任何语言其实都可以分为大同小异的几块) 而C语言的内存模型分为5个区&#xff1a;栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下&#xff1a; 1、栈区&…

网络:IP与MAC

如果我们要跟对方通信&#xff0c;我们需要知道对方的IP地址与MAC地址。 一、IP IP地址&#xff0c;32位&#xff0c;工作在网络层&#xff0c;属IP协议族。在互联网中逻辑的代表某一台设备&#xff0c;但是在不同的时间&#xff0c;与我合作的主机非常多。某一个设备使用完我…

2022/11/12 json格式转换对象 动态sql

PostMapping public Integer save(RequestBody User user){return userMapper.insert(user); }选择json格式。以为本人忘记选了415错误&#xff0c;media错误 mybatisx插件 sprinboot yml文件导入xml mybatis: mapper-locations: classpath:mapper/*.xml 一直报错 发现重复了…

【华为ICT大赛】华为云激活设备的方法以及数据上下行

先展示一下没有激活的时候在线调试的状态 然后下面我将激活他&#xff0c;让他变为下面这个样子 官方教程 这里我从0演示一个产品的创建到MQTT.fx连接到云平台并且接收数据。 进入华为云平台控制台 然后开始创建一个产品 创建完毕产品之后就可以开始创建服务了&#xff0c;一…

提高工作效率的 keychron 键盘,你还没有入手吗?

前言 大家好&#xff0c;今天给大家推荐一款我最近入手并且一直在使用的机械键盘 keychron K4 。我自己是一个键盘的重度使用者&#xff0c;由于工作的需求&#xff0c;对键盘有很大的依赖&#xff0c;而一款可以提高工作效率的键盘&#xff0c;往往可以让我们的工作事半功倍。…

文化馆建筑方案设计原理及方案

文化馆建筑的分类与作用 根据职能不同&#xff0c;文化馆建筑可分为文化馆、群众艺术馆和文化站等形式。 文化馆是国家设立的开展社会宣传教育、普及科学文化知识、组织辅导群众文化艺术(活动)的综合性文化事业机构和场所。 群众艺术馆是国家设立的组织指导群众文化艺术活动及…

Github工程中的Markdown语言应用

Github工程中的Markdown语言应用1. 介绍2. 工具2.1 下载链接2.2 编辑界面2.3 插件安装3. 基本操作3.1 标题编写3.2 正文编写3.3 代码块编写3.4 加粗倾斜3.5 有序列表3.6 无序列表3.7 行内代码编写4. 参考资料最早开源代码中&#xff0c;接触的最多的就是Readme文件&#xff0c;…

Altium格式PCB转换成Allegro操作指导

Altium格式PCB转换成Allegro操作指导 首先打开Altium Design的PCB文件,输出一个“PCB ASCII File(*.pcbdoc)”格式的文件。如下图 打开Allegro,选择was performance L模式,新建空白的brd。 导入Altium PCB 选择文件转换即可 转换成功 This section is describe what t…

Spring更简单的实现Bean对象的存取

目录 一、前言&#xff1a; 二、储存Bean对象 5大类注解 Bean方法注解 三、获取Bean对象 属性注入 优缺点 Setter注入 优缺点分析 构造方法注入 优缺点分析 经典面试题 &#xff1a;属性注入 &#xff0c;构造方法注入 和 Setter 注入 之间&#xff0c;有什么区别…

大数据Presto(四):Presto自定义函数和JDBC连接

文章目录 Presto自定义函数和JDBC连接 一、Presto 自定义函数 1、​​​​​​​UDF函数 2、​​​​​​​UDAF函数 二、Presto JDBC连接 Presto自定义函数和JDBC连接 ​​​​​​​一、Presto 自定义函数 我们可以登录Presto客户端&#xff0c;使用命令&#xff1a;s…

JavaScript 71 JavaScript JSON 71.5 JSON.parse()

JavaScript 文章目录JavaScript71 JavaScript JSON71.5 JSON.parse()71.5.1 实例 – 解析 JSON71.5.2 来自服务器的 JSON71.5.3 作为 JSON 的数组71.5.4 例外71.5.5 浏览器支持71 JavaScript JSON 71.5 JSON.parse() JSON 的常规用途是同 web 服务器进行数据传输。 在从 web…

Hive笔记-01 架构概述

文章目录1.概述2.Metadata/Metastore的作用3 Metastore三种配置方式3.1 Hive配置参数说明3.1.1 基本配置参数3.1.2 其他配置参数3.2 内嵌模式&#xff08;Embedded&#xff09; 3.2.1 hive-site.xml配置说明 3.2.2 hive-site.xml配置样例3.2.3 启动方式3.2.4 缺点3.3 本地模式&…

cmd常用命令行

前言 最近在看《深入剖析Tomcat》&#xff0c;其中涉及了常见的dos命令&#xff0c;这里做一些简单记录&#xff0c;其实跟linux命令很像。 案例 .bat&#xff1a;批处理文件 rem&#xff1a;用于注释&#xff0c;解释器不会执行以rem命令开始的行 - pause&#xff1a;暂停…

Executors工具类的相关方法

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生。本篇将记录创建线程池的Executors工具类里面的方法&#xff0c;方便加深知识印象和复习使用。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&#xff0c;面试中的大佬&a…

Matlab之机载雷达系统中的空时自适应处理(STAP)技术(附源码)

目录 一、介绍 二、系统设置 2.1 天线定义 2.2 雷达设置 2.3 目标 2.4 杂波 2.5 传播路径 三、模拟循环 3.1真实目标范围、角度和多普勒 3.2 使用 DPCA 消除器进行杂波抑制 ​四、总结 五、程序 本例简要介绍了空时自适应处理&#xff08;STAP&#xff09;技术&…

【C++】模板进阶 —— 非类型模板参数 | 特化 | 模板的分离编译

&#x1f308;欢迎来到C专栏~~模板进阶 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&…

java线程基础

最近&#xff0c;想弄一个雪花飘落&#xff0c;结果&#xff0c;搞了两三个小时没弄出来。主要是雪花飘落可能需要用到线程。有人是通过一个雪花去实现&#xff0c;然后通过集合去实现漫天雪花。不管怎么说&#xff0c;做开发&#xff0c;可能线程学习也是一块绕不过去的大山。…

【JavaWeb】jsp

文章目录⭐️ 一.jsp概念及其作用⭐️ 二.jsp的本质⭐️三.jsp的三种语法⭐️四.jsp的九大内置对象⭐️五.jsp四大域对象⭐️六.jsp中的out输出和response.getWriter输出的区别⭐️七.jsp的常用标签⭐️八.Listener监听器⭐️ 一.jsp概念及其作用 1.概念:jsp的全称是java serv…

SpringMVC框架中的异常处理机制

目录 1. 什么是异常处理&#xff1f; 2. SpringMVC框架中的异常处理机制是什么&#xff1f; 3. SpringMVC框架汇中实现异常处理的实现步骤 4. SpringMVC框架出现异常时候的处理过程 5. 附手写代码&#xff0c;并含有注释 1. 什么是异常处理&#xff1f; http://t.csdn.cn/x…