多重背包问题(详解二进制优化原理)

news2024/11/15 13:57:48

多重背包问题及优化(详解优化原理)

  • 一、问题描述
  • 二、思路分析
    • 1、状态转移方程
      • (1)状态表示:
      • (2)状态转移:
    • 2、循环设计
  • 三、代码模板
    • 1、朴素版
    • 2、优化版

一、问题描述

在这里插入图片描述

二、思路分析

这道题同样是背包问题,那么它也同样满足三个性质:重叠子问题、最优子结构以及无后效性。那么这样的话,我们依然可以使用动态规划的思路去分析这道题目。那么动态规划的分析主要分为两步:状态转移方程的书写以及循环的设计。

1、状态转移方程

(1)状态表示:

我们在前面的两篇文章中介绍过,对于背包问题而言,我们一般用一个二维数组来表示dp数组,即我们经常写的: f ( i , j ) f(i,j) f(i,j)

那么 f ( i , j ) f(i,j) f(i,j)的意思是,当物品数量为 i i i,自己的背包容量是 j j j的时候,我们所能携带的最大价值: f [ i ] [ j ] f[i][j] f[i][j]

(2)状态转移:

状态转移的目的是为了能够将大规模的问题转化成较小规模的问题。

背包问题中,状态转移方程的书写就一个技巧:活在当下

我们此时共用 i i i个物品,我们不可能一下子就同时做出多个决策,从而得到最优解。我们就看眼前,我们眼前的就是第 i i i个物品,而我们要做的决策就是,第 i i i个物品到底选不选,选的话,在背包容量允许的条件下,我们选几个?

如果我们不选的话,我们就能写出下列的方程:

f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i1,j)

由于我们不选第 i i i个物品,所以我们只需要考虑前 i − 1 i-1 i1个。这样我们就把规模从 i i i降低到了 i − 1 i-1 i1

假设我们选的话,那么就能够写成下列形式:( v [ i ] v[i] v[i]表示第 i i i个物品的体积, w [ i ] w[i] w[i]表示第 i i i个物品的价值)

f ( i , j ) = f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k f(i,j)=f(i-1,j-v[i]*k)+w[i]*k f(i,j)=f(i1,jv[i]k)+w[i]k

上述等式成立的前提是保证: j ≥ v [ i ] ∗ k j\geq v[i]*k jv[i]k

我们只需要在上述的这些情况中取一个最大值即可:

所以我们的状态转移方程可以表示为:

f ( i , j ) = m a x { f ( i − 1 , j ) f ( i − 1 , j − v [ i ] ) + w [ i ] j ≥ v [ i ] f ( i − 1 , j − v [ i ] ∗ 2 ) + w [ i ] ∗ 2 j ≥ v [ i ] ∗ 2 . . . . . . f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k j ≥ v [ i ] ∗ k f(i,j)=max \begin{cases} f(i-1,j)\\ f(i-1,j-v[i])+w[i] &j\geq v[i]\\ f(i-1,j-v[i]*2)+w[i]*2 &j\geq v[i]*2\\ ......\\ f(i-1,j-v[i]*k)+w[i]*k&j\geq v[i]*k \end{cases} f(i,j)=max f(i1,j)f(i1,jv[i])+w[i]f(i1,jv[i]2)+w[i]2......f(i1,jv[i]k)+w[i]kjv[i]jv[i]2jv[i]k

2、循环设计

我们的循环和之前所介绍的01背包问题、完全背包问题的循环设计是一样的,最外层循环是背包的规模从小到大,第二层的循环是背包的容量,从小到大。

三、代码模板

1、朴素版

我们综合上面的思路,就能够写出下面的代码:

#include<iostream>
using namespace std;
const int N=110;
int f[N][N],v[N],w[N],q[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",v+i,w+i,q+i);
    }
    for(int i=1;i<=n;i++)//枚举物品规模
    {
        for(int j=0;j<=m;j++)//枚举背包容量
        {
            for(int k=0;k*v[i]<=j&&k<=q[i];k++)//书写状态转移方程
            {
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

2、优化版

我们发现上面的这个朴素版代码包含了三重循环,那么我们如何降低一层循环呢?

其实,我们的多重背包可以转化成01背包问题。这样讲肯定很抽象,大家可以看下面的图:
在这里插入图片描述
但是这样做的话只是形式上做了优化,从 n 3 n^3 n3 n 2 n^2 n2,但实际上依旧是一个 n 3 n^3 n3的时间复杂度。

而这样没有成功优化的原因是因为我们有 n n n i i i物品,就需要重复 n n n个i物品。那么我们是否存在一种方式,我们不需要枚举 n n n次i物品,就能够表示 n n n i i i物品呢?

答案是有的。

我们的十进制数可以用二进制数来表示。

假设我们的二进制位有4位:

那么我们能表示的范围是: 0000 0000 0000 ~ 1111 1111 1111

1111 = 1000 + 0100 + 0010 + 0001 1111=1000+0100+0010+0001 1111=1000+0100+0010+0001

上面右侧的四个部分组成了这个数字。

我们可以从形式上掌握一下,这个四个部分所代表的含义就是对应的位数是1

1000 1000 1000:第四位是1
0100 0100 0100:第三位是1
0010 0010 0010:第二位是1
0001 0001 0001:第一位是1

接着我们发现, 0000 0000 0000 1111 1111 1111之间的任何一个数字,其实无非是某位是0,某位是1。如果某位是1,我们只需要加上上面四个数字中的其中一个。

例如:
1001 = 1000 + 0001 1001=1000+0001 1001=1000+0001
1101 = 1000 + 0001 + 0100 1101=1000+0001+0100 1101=1000+0001+0100

因此我们就能够得到一个结论,我们能够有这四个数字表示 0000 0000 0000 1111 1111 1111之间的所有数字。

转换成十进制而言,就是说我们能够用 1 1 1, 2 2 2, 4 4 4, 8 8 8表示出 0 0 0 15 15 15之间的任何数字。

所以,我们可以利用几个 2 k 2^k 2k,其中 ( k = 0 , 1 , 2 , . . . ) (k=0,1,2,...) k=012...,来表达一个范围内的所有数字。根据我们刚才的推导,所能表示的范围其实就是我们刚刚这几个数加在一起时的值,其实就是一个等比数列求和。

用数学公式表达就是

我们可以利用: 2 0 、 2 1 、 2 2 、 2 3 、 . . . 、 2 k 2^0、2^1、2^2、2^3、... 、2^k 20212223...2k表示 0 0 0 ~ 2(k+1) − 1 -1 1之间的所有数字。

那么如果我们的要表达的200

那么我们可以利用:
2 0 、 2 1 、 2 2 、 2 3 、 2 4 、 2 5 、 2 6 2^0、2^1、2^2、2^3、2^4、2^5、2^6 20212223242526

这几个数能表达的最大值是: 2 7 − 1 = 127 2^7-1=127 271=127

那么从128到200怎么表示呢?

其实我们只需要多加一个73即可。

也就是说,我们可以用:

2 0 、 2 1 、 2 2 、 2 3 、 2 4 、 2 5 、 2 6 、 73 2^0、2^1、2^2、2^3、2^4、2^5、2^6 、73 2021222324252673
来表达0-200。

为什么呢?

我们只用前面的这些 2 k 2^k 2k。那么我们能表示的是 0 − 127 0-127 0127
如果我们每次选择的时候都加上一个73,我们能表示的范围就是:
73 − 200 73-200 73200

虽然两部分之间有一点重叠的部分,但是重叠的话,无非就是我们重复计算了几个相同情况而已,并不影响我们结果的正确性。

那么我们发现,此时我们利用 O ( l o g n ) O(logn) O(logn)级别的数字表示了 O ( n ) O(n) O(n)

时间上做了非常大的优化。

而这种优化方式被称作二进制优化

如图所示:

在这里插入图片描述
根据上面的思路,我们就能够写出下面的优化版本的代码:

#include<iostream>
using namespace std;
const int N=3e4+10;
int dp[N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    int cnt=1;
    for(int i=1;i<=n;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        int k=1;
        //进行 “打包” 转换:二进制优化,转换成01背包
        while(k<c)
        {
            v[cnt]=k*a,w[cnt++]=k*b;
            c-=k;
            k*=2;
        }
        if(c>0)
            v[cnt]=c*a,w[cnt++]=c*b;
    }
    //利用01背包中的空间优化模板求解。
    for(int i=1;i<=cnt;i++)
        for(int j=m;j>=v[i];j--)
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[m]<<endl;
    return 0;
}

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

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

相关文章

JVM垃圾回收机制

目录 目录 前言 一. GC (垃圾回收机制) STW问题 二. GC 回收哪部分内存 三. 具体怎么回收 1. 先找出垃圾 a.引用计数 b. 可达性分析 2. 回收垃圾 a. 标记清除 b. 复制算法 c. 标记整理 d. 分代回收 前言 我们都知道 Java 运行时内存的各个区域. 对于程序计数器 …

数据库与身份认证:在项目中操作 MySQL

在项目中操作数据库的步骤 ①安装操作 MySQL 数据库的第三方模块&#xff08;mysql&#xff09; ②通过 mysql 模块连接到 MySQL 数据库 ③通过 mysql 模块执行 SQL 语句 安装与配置 mysql 模块 1. 安装 mysql 模块 mysql 模块是托管于 npm 上的第三方模块。它提供了在 Nod…

智能wifi小车-RGB三色LED灯驱动

RGB三色LED灯简介 RGB指的就是三基色光&#xff0c;R红色&#xff0c;G绿色&#xff0c;B蓝色。LED芯片所发出的光一般都是蓝光&#xff0c;都是要通过红 绿 蓝这三种颜色的荧光粉去调颜色的。RGB色彩模式是工业界的一种颜色标准&#xff0c;是通过对红(R)、绿(G)、蓝(B)三个颜…

首创证券将在上交所上市:募资约19亿元,规模不及信达证券

12月22日&#xff0c;首创证券股份有限公司&#xff08;下称“首创证券”&#xff0c;SH:601136&#xff09;将在上海证券交易所主板上市。本次上市&#xff0c;首创证券的发行价格为7.07元/股&#xff0c;发行市盈率22.98倍&#xff0c;发行数量为2.73亿股&#xff0c;募资总额…

JavaSE14-数组

目录 1.数组基本用法 1.1.什么是数组 1.2.数组声明 1.3.数组的创建与初始化 1.3.1.基本类型数组 1.3.2.对象数组 1.4.数组的使用 1.4.1.获取长度 & 访问元素 1.4.2.遍历数组 2.数组作为方法的参数 2.1.基本用法 2.2.内存 2.3.引用 2.4.初识 JVM 内存区域划分…

怎么做现货白银的心理障碍

克服投资的心理障碍&#xff0c;是怎么做现货白银投资的关键。很多时候&#xff0c;技术分析是很简单的。一根K线&#xff0c;一条均线&#xff0c;就能够让人获利。但是为什么使用同样工具的人&#xff0c;却不能获利呢&#xff1f;为什么他们还要去追求一些特别复杂的分析系统…

四平方和(蓝桥杯C/C++B组真题详解)(三种做法)

目录 题目详细&#xff1a;​编辑 题目思路&#xff1a; 暴力&#xff1a; 代码详解&#xff1a; 哈希&#xff1a; 二分&#xff1a; 题目详细&#xff1a; 题目思路&#xff1a; 这个题目大家可能马上就可以想到暴力做 例如这样 暴力&#xff1a; #include<iost…

TensorFlow和Keras应如何选择?

前些年&#xff0c;深度学习领域的研究人员、开发人员和工程师必须经常做出一些选择&#xff1a; 我应该选择易于使用但自定义困难的 Keras 库&#xff1f;还是应该使用难度更大的 TensorFlow API&#xff0c;编写大量代码&#xff1f;&#xff08;更不用说一个不那么容易使用…

pyspark之sparksql数据交互

在pyspark中&#xff0c;使用sparksql进行mysql数据的读写处理&#xff0c;将程序保存为test.py #-*- coding: UTF-8 -*- # 设置python的默认编码 import sys reload(sys) sys.setdefaultencoding(utf-8) # Spark 初始化 from pyspark.sql import SQLContext, SparkSession, …

【推荐】DDD领域驱动设计和中台实践资料合集

Domain Driven Design&#xff08;简称 DDD&#xff09;&#xff0c;又称为领域驱动设计&#xff0c;起源于杰出软件建模专家Eric Evans在2003年发表的书籍《DOMAIN-DRINEN DESIGN —TACKLING COMPLEXITY IN THE HEART OF SOFTWARE》&#xff08;中文译名《领域驱动设计—软件核…

卓海科技冲刺创业板:拟募资5.47亿 相宇阳控制52.9%股权

雷递网 雷建平 12月20日无锡卓海科技股份有限公司&#xff08;简称&#xff1a;“卓海科技”&#xff09;日前递交招股书&#xff0c;准备在深交所创业板上市。卓海科技计划募资5.47亿元&#xff0c;其中&#xff0c;1.04亿元用于半导体前道量检测设备扩产项目&#xff0c;1.84…

ev_api_server:大事件node接口项目开发

Headline 大事件后台 API 项目&#xff0c;API 接口文档请参考 https://www.showdoc.cc/escook?page_id3707158761215217 1. 初始化 1.1 创建项目 新建 api_server 文件夹作为项目根目录&#xff0c;并在项目根目录中运行如下的命令&#xff0c;初始化包管理配置文件&#x…

尚医通-前端Vue学习(十)

目录&#xff1a; &#xff08;1&#xff09;node.js介绍 &#xff08;2&#xff09;npm包管理工具 &#xff08;3&#xff09;es6模块化 &#xff08;4&#xff09;babel转码器 &#xff08;5&#xff09;webpack打包工具 &#xff08;1&#xff09;node.js介绍 浏览器的…

Python使用pandas导入xlsx格式的excel文件内容

Python使用pandas导入xlsx格式的excel文件内容1. 基本导入2. 列标题与数据对齐3. 指定导入某个sheet4. 指定行索引5. 指定列索引6. 指定导入列7. 指定导入的行数8. 更多的参数1. 基本导入 在 Python中使用pandas导入.xlsx文件的方法是read_excel()。 # codingutf-8 import pa…

Windows环境下在VScode中运行开源运动规划库(zhm-real / PathPlanning)的方法

本文主要介绍Windows环境下&#xff0c;在Vscode中运行zhm-real发布的开源运动规划库PathPlanning的实现方法&#xff0c;包括环境配置及运行开源包时常见错误解决方法。    一、环境配置 &#xff08;1&#xff09;VScode 下载及安装&#xff0c;官网如下&#xff1a; http…

Flowable工作流进阶使用教程(监听器+流程变量+网关)

一、任务分配和流程变量 1.任务分配 1.1 固定分配 固定分配就是我们前面介绍的&#xff0c;在绘制流程图或者直接在流程文件中通过Assignee来指定的方式 1.2 表达式分配 Flowable使用UEL进行表达式解析。UEL代表Unified Expression Language&#xff0c;是EE6规范的一部分…

【Python】用python将html转化为pdf

其实早在去年就有做过&#xff0c;一直没有写&#xff0c;先简单记录下 1、主要用到的工具【wkhtmltopdf】 【下载地址】wkhtmltopdf 根据系统选择安装包&#xff0c;速度有点慢&#xff0c;先挂着 2、下载Python库 pip install pdfkit pip install wkhtmltopdf 3、简单代码…

CAD教程:CAD自定义之基础设置的操作技巧

在使用国产CAD软件绘制CAD图纸的过程中&#xff0c;有些时候会需要CAD自定义设置&#xff0c;那么你知道浩辰CAD建筑软件中CAD自定义之基础设置怎么使用吗&#xff1f;不知道也没关系&#xff0c;接下来的CAD教程就让小编来给大家介绍一下国产CAD软件——浩辰CAD建筑软件中CAD自…

【1799. N 次操作后的最大分数和】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你 nums &#xff0c;它是一个大小为 2 * n 的正整数数组。你必须对这个数组执行 n 次操作。 在第 i 次操作时&#xff08;操作编号从 1 开始&#xff09;&#xff0c;你需要&#xff1a; 选择两个…

实验一 逻辑回归

一、实验目的 &#xff08;1&#xff09;学习并掌握常见的机器学习方法&#xff1b; &#xff08;2&#xff09;能够结合所学的python知识实现机器学习算法&#xff1b; &#xff08;3&#xff09;能够用所学的机器学习算法解决实际问题。 二、实验内容与要求 &#xff08…