01背包详解,状态设计,滚动数组优化,通用问题求解

news2025/1/19 20:36:10

文章目录

  • 0/1背包
    • 前言
    • 一、0/1背包的状态设计
      • 1、状态设计
      • 2、状态转移方程
      • 3、初始状态
      • 4、代码实现
      • 5、滚动数组优化
        • 二维优化为两个一维
        • 二维优化为一个一维,倒序递推
    • 二、0/1背包的通用问题
      • 求最大值
      • 求最小值
      • 求方案数

0/1背包

前言

0/1包问题,作为动态规划问题的经典问题,可以帮助捋顺思维。核心就是有一堆物品,有两个维度的限制,在保证一个维度限制的情况下,使得另一个维度最优。

一、0/1背包的状态设计

有n(n≤100)个物品和一个容量为m(m≤10000)的背包。
第i个物品的容量是c[i],价值是w[i]。现在需要选择一些物品放入包, 总容不能超过背包容量,求能够达到的物品的最大总价值。

以上就是0/1背包问题的完整描述,之所以叫0/1背包,因为每种物品只有一个,可以选择
放入背包或者不放,而0代表不放,1 代表放。

1、状态设计

状态(i, j)表示前i个物品恰好放入容量为j的背包(0≤i<n,0≤j≤m);
令dp表[][]示状态(i, j)下该背包得到的最大价值,即前i个物品恰好放入容量为j的背包所得
到的最大总价值;

2、状态转移方程

列出状态转移方程如下:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − c [ i ] ] + w [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i-1][j - c[i]] + w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jc[i]]+w[i])
因为每个物品要么放,要么不放,所以只需要考虑第i个物品放或不放的情况:

1)不放:如果第i个物品不放入容量为j的背包",那么问题转化成求"前i-1 个物品放入容为j的背包"的问题;于不放,所以最大价值就等于"前i-1个物品放入容量为j的背包"的最大价值,即dp[i-1][j];

2)放:如果"第i个物品放入容为j的背包",那么问题转化成求"前i-1个物品放入容量为j-c[j]的背包"的问题;那么此时最大价值就等于"前i-1个物品放入容为j-c[i] 的背包"的最大价值加上放入第i个物品的价值,即dp[i-1][j - c[i]] + w[i]
将以上两种情况取大者,就是我们所求的“前i个物品恰好放入容量为j的背包"的最大价值了。

在这里插入图片描述

3、初始状态

我们发现,当状态在进行转移的时候,dp(i, j)不是来自dp(i - 1 , j),就是来自dp(i - 1 , j - c[i]),所以必然有一个初始状态,而这个初始状态就是(0, i),含义是"前i个物品放入一个容量为0的背包",这个状态下的最大价值为0,即dp[0][i] = 0;

img

4、代码实现

铺垫了那么多,我们来实现一下0/1背包的板子。

//#define N 110
//#define M 1010
//int dp[N][M]{0}, c[N]{0}, w[N]{0}, n, m;
//c[i]为第i个物品的重量,w[i]为第i个物品的价值。n、m分别为物品数和背包容量
for (int i = 1; i <= n; i++)
    for (int j = 0; j <= m; j++)
        if (j < c[i])
            dp[i][j] = dp[i - 1][j];
        else
            dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]);

5、滚动数组优化

滚动数组优化为递推问题中的常用空间优化手段,如果每次递推都由上一次状态转移那么我们可以将空间降低一个维度。

我们再看状态转移图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发现每个状态都只跟上一行同一列和上一行左边的状态有关,我们可以开两个一维数组,分别保存上一行和当前行的状态,当然也可以只用一个一维数组然后倒序递推来实现。

二维优化为两个一维

两个一维数组一个保存上一行的状态一个保存当前行状态即可。

#define N 110
#define M 1010
int dp1[M]{0}, dp2[M]{0}, c[N]{0}, w[N]{0}, n, m;
for (int i = 1; i <= n; i++)
    cin >> c[i] >> w[i];
for (int i = 1; i <= n; i++)
{
    for (int j = 0; j <= m; j++)
        if (j < c[i])
            dp2[j] = dp1[j];
        else
            dp2[j] = max(dp1[j], dp1[j - c[i]] + w[i]);
    memcpy(dp1, dp2, sizeof(dp1));
}
cout << dp2[m];
二维优化为一个一维,倒序递推

即然每次状态都只由上一行当前列和当前行左侧列转移,我们发现两个一维优化方案中,dp1其实是也可以优化掉的,假如只剩下一个一维数组dp,如果我们正序递推会怎样?

初始时dp中存储上一次状态转移的数据,如果正序递推则导致我们状态转移需要上一次状态但是由于正序递推覆盖了左侧内容,就无法获取上一次状态了,所以我们选择逆序递推:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#define N 110
#define M 1010
int dp[M]{0}, dp2[M]{0}, c[N]{0}, w[N]{0}, n, m;
for (int i = 1; i <= n; i++)
    cin >> c[i] >> w[i];
for (int i = 1; i <= n; i++)
{
    for (int j = m; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
}
cout << dp[m];

二、0/1背包的通用问题

求最大值

原题链接

[P1048 NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

01背包板子题,读完数据跑一遍板子,输出数据即可。

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <algorithm>
#include <cmath>
#include <functional>
#include <climits>
#include <bitset>
#include <stack>
#include <cstring>
using namespace std;
#define N 110
#define M 1010
int dp[M]{0}, c[N]{0}, w[N]{0}, n, m;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> m >> n;
    for (int i = 1; i <= n; i++)
        cin >> c[i] >> w[i];
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }
    cout << dp[m];
    return 0;
}

[P1060 NOIP2006 普及组] 开心的金明 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

同样是01板子题,只不过物品价值变成了重量乘价值

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <algorithm>
#include <cmath>
#include <functional>
#include <climits>
#include <bitset>
#include <stack>
#include <cstring>
using namespace std;
#define N 30010
#define M 100000000
int dp[M]{0}, c[N]{0}, w[N]{0}, n, m;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> m >> n;
    for (int i = 1; i <= n; i++)
        cin >> c[i] >> w[i], w[i] *= c[i];

    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }
    cout << dp[m];
    return 0;
}


P3985 不开心的金明 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

在不考虑内存的情况下显然是一道01背包板子题,但是你这个1e9的背包内存肯定会爆,内存不爆你时间复杂度也会爆,但是金明妈妈限制了物品重量极差不超过3(感谢金明妈妈!!!!),我们就可以将物品重量离散化到1,2,3,4上,即我们记录最小重量mi,再令每个重量减去(mi - 1),这样物品重量就变为了1,2,3,4

由于物品数目最多100,所以物品总重量最大也就400,我们离散化后再去跑01背包的板子即可

但是!!!

有一个问题,你怎么根据离散化后的重量计算离散化前的重量呢?看我们的板子

for (int i = 1; i <= n; i++)
{
    for (int j = sum; j >= c[i]; j--)
        dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
}

此时j为离散化后的重量了,你怎么确定你上面的状态方程就合法呢?如果一共给了m的空间,你此时的j对应实际重量为j + cnt * mi,cnt为装的物品数目,故而我们需要给dp增加一个维度来记录装入的物品数目

dp[i][j]就代表容量i装入了j个物品的最大价值,此时装入总重量不超过i + j * mi,(不超过是因为i可能大于j个物品的离散重量)

这样一来我们的空间复杂度只有1e2量级,时间复杂度只有1e6量级

代码如下:

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <algorithm>
#include <cmath>
#include <functional>
#include <climits>
#include <bitset>
#include <stack>
#include <cstring>
using namespace std;
#define N 110
#define M 550
int dp[M][N]{0}, c[N]{0}, w[N]{0}, n, m, mi = INT_MAX, sum = 0, ans = 0;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> c[i] >> w[i], mi = min(mi, c[i]);
    mi--;
    for (int i = 1; i <= n; i++)
        c[i] -= mi, sum += c[i];

    for (int i = 1; i <= n; i++)
    {
        for (int j = sum; j >= c[i]; j--)
            for (int k = (m - j) / mi ; k >= 1; k--)
                ans = max(ans, dp[j][k] = max(dp[j][k], dp[j - c[i]][k - 1] + w[i]));
    }
    cout << ans;
    return 0;
}


求最小值

原题链接

[P1049 NOIP2001 普及组] 装箱问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

本质还是求最大值。

在本题内,重量就是体积,价值也是体积,我们求出能装的最大体积,容量减去最大体积就是答案。

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <algorithm>
#include <cmath>
#include <functional>
#include <climits>
#include <bitset>
#include <stack>
#include <cstring>
using namespace std;
#define N 50
#define M 20010
int dp[M]{0}, c[N]{0}, w[N]{0}, n, m;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> m >> n;
    for (int i = 1; i <= n; i++)
        cin >> c[i];
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + c[i]);
    }
    cout << m - dp[m];
    return 0;
}

求方案数

原题链接

P1164 小A点菜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

对于本道题我们的dp[i][j]就变成了j元恰好买i道菜的方案数

对于初始状态即0元购0菜方案为1,其他0元购都是0

那么我们同样对于每道菜可以选择买或不买,可以选择用j元恰好买i - 1道菜也可以选择用j元恰好买i道菜,那么转移方程就变成了

dp[i][j] = dp[i - 1][j] + dp[i - 1][j - c[i]]

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <algorithm>
#include <cmath>
#include <functional>
#include <climits>
#include <bitset>
#include <stack>
#include <cstring>
using namespace std;
#define N 110
#define M 10010
int dp[M]{0}, c[N]{0}, w[N]{0}, n, m;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> c[i];
    dp[0] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= c[i]; j--)
            dp[j] += dp[j - c[i]];
    }
    cout << dp[m];
    return 0;
}

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

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

相关文章

ESP32运行MicroPython——环境搭建

1、准备工作 硬件&#xff1a;ESP32-DevKitC V4 开发板、USB串口线 软件&#xff1a; flash_download_tool_3.9.5&#xff08;乐鑫烧录工具&#xff09;、官方下载地址 CP210x&#xff08;USB驱动程序&#xff09;、官方下载地址 ESP32_GENERIC-20231005-v1.21.0.bin&#xff…

将html转化成图片

如何将指定html内容转化成图片保存&#xff1f;这个问题很值得深思&#xff0c;实际应用中也很有价值。最直接的想法就是使用canvas&#xff0c;熟悉canvas的同学可以尝试一下。这里不做太多的说明&#xff0c;本文采用html2canvas库来实现。 html2canvas库的使用非常简单&…

JavaWeb笔记之SVN

一、版本控制 软件开发过程中 变更的管理&#xff1b; 每天的新内容;需要记录一下&#xff1b; 版本分支;整合到一起&#xff1b; 主要的功能对于文件变更的追踪&#xff1b; 多人协同开发的情况下,更好的管理我们的软件。 大型的项目;一个团队来进行开发; 1: 代码的整合 2: 代…

GPT2代码运行,个人文本生成助手,不依赖OpenAI API调用

0.前言: 感觉GPT很好玩,所以想要有个自己搭建GPT的写法,不依赖于OpenAI,需要翻墙太麻烦了,近日日本已经结合GPT4和机器,可以让他吓人,做出丰富的表情,如果自己训练的话,会塑造出什么样的机器人尚未可知…抱着好奇的心态,去github openai下载了个gpt2的模型来玩玩(其中遇到了许多…

arm汇编-补充-画图记忆 LDM/STM系列指令

说明 原始文章系列 https://azeria-labs.com/writing-arm-assembly-part-1/ 翻译-进作者的专栏可以翻到所有翻译 系列&#xff1a;https://zhuanlan.zhihu.com/p/109057983 系列&#xff1a;https://www.anquanke.com/post/id/86383 0x00 调试环境 使用arm架构的系统&#…

❀My学习Linux命令小记录之iperf❀

❀My学习Linux命令小记录之iperf❀ 目录 ❀My学习Linux命令小记录之iperf❀ ①功能说明&#xff1a; ②安装iperf&#xff1a; ③iperf的使用及参数介绍&#xff1a; ④注意事项&#xff1a; ⑤实例&#xff1a; iperf测试udp iperf测试tcp ①功能说明&#xff1a; i…

管理 Jenkins 详细指南

目录 系统配置 安全 状态信息 故障 排除 工具和操作 系统配置 系统&#xff0c;配置全局设置和路径&#xff0c;端口更改&#xff0c;下载地址等。 工具&#xff0c;配置工具、其位置和自动安装程序。 插件&#xff0c;添加、删除、禁用或启用可以扩展 Jenkins 功能的插…

Logback简介与配置详解

在开发和维护Spring Boot应用程序时&#xff0c;一个强大而灵活的日志框架是至关重要的。Spring Boot默认集成了Logback&#xff0c;一个高性能的Java日志框架。本文将介绍如何配置Logback以满足你的日志记录需求。 Logback简介 官方网址&#xff1a;https://logback.qos.ch/ …

【JMeter】JMeter控制RPS

一、前言 ​ RPS (Request Per Second)一般用来衡量服务端的吞吐量&#xff0c;相比于并发模式&#xff0c;更适合用来摸底服务端的性能。我们可以通过使用 JMeter 的常数吞吐量定时器来限制每个线程的RPS。对于RPS&#xff0c;我们可以把他理解为我们的TPS&#xff0c;我们就不…

2023年12月GESP Python三、四级编程题真题解析

三、2023年12月GESP Python三级编程题 【三级编程题1】 【试题名称】&#xff1a;小猫分鱼 【问题描述】 海滩上有一堆鱼&#xff0c;N只小猫来分。第一只小猫把这堆鱼平均分为N份&#xff0c;多了i<N条鱼&#xff0c;这只小猫把多的i条鱼扔入海中&#xff0c;拿走了一份…

DBA-MySql面试问题及答案-上

文章目录 1.什么是数据库?2.如何查看某个操作的语法?3.MySql的存储引擎有哪些?4.常用的2种存储引擎&#xff1f;6.可以针对表设置引擎吗&#xff1f;如何设置&#xff1f;6.选择合适的存储引擎&#xff1f;7.选择合适的数据类型8.char & varchar9.Mysql字符集10.如何选择…

Stable Diffusion 基本原理

1 Diffusion Model的运作过程 输入一张和我们所需结果图尺寸一致的噪声图像&#xff0c;通过Denoise模块逐步减少noise&#xff0c;最终生成我们需要的效果图。 图中Denoise模块虽然是同一个&#xff0c;但是它会根据不同step的输入图像和代表noise严重程度的参数选择denoise的…

【接口测试】Postman(三)-变量与集合

一、变量 ​ 变量这个概念相信大家都不陌生&#xff0c;因此在这里我们不介绍了。主要说一下在Postman中有哪几类变量&#xff0c;主要包括以下四类&#xff1a; Global&#xff08;全局&#xff09; Environment&#xff08;环境&#xff09; Local&#xff08;本地&#xf…

安装gnvm,nodejs,npm使用方法

安装gnvm,nodejs,npm使用方法 一、安装gnvm gnvm.exe下载地址&#xff1a; https://download.csdn.net/download/hsg77/88651752 http://ksria.com/gnvm/#download 二、配置gnvm环境变量 新建目录&#xff0c;如&#xff1a;d:/nodejs 并把gnvm.exe存储到此目录 并把d:/node…

【分布式技术专题】「授权认证体系」深度解析OAuth2.0协议的原理和流程框架实现指南(授权流程和模式)

深度解析OAuth2.0协议的原理和流程框架实现指南 背景介绍OAuth1.0协议访问令牌案例分析 OAuth2.0OAuth2.0与OAuth1.0 OAuth2.0协议体系的Roles角色OAuth定义了四个角色资源所有者资源服务器客户端授权服务器 传统的客户机-服务器身份验证模型的问题协议流程 认证授权类型授权码…

Python生成圣诞节词云-代码案例剖析【第17篇—python圣诞节系列】

文章目录 ❄️Python制作圣诞树词云-中文&#x1f42c;展示效果&#x1f338;代码&#x1f334;代码剖析 ❄️Python制作圣诞树词云-英文&#x1f42c;展示效果&#x1f338;代码&#x1f334;代码剖析 &#x1f385;圣诞节快乐&#xff01; ❄️Python制作圣诞树词云-中文 &a…

【编译原理--复习】

知识点整理 第一章 同时伴有表格管理、出错处理 1、词法分析 任务&#xff1a;对构成源程序的字符串进行扫描和分解&#xff0c;识别出单词(如标识符等)符号 输入&#xff1a;源程序 输出&#xff1a;单词符号序列 2、语法分析 任务&#xff1a;根据语言的语法规则对单词符号…

[管理者与领导者-129]:很多人对高情商的误解,工程师要扩展自己的情商吗?工程师如何扩展自己的情商?

目录 前言&#xff1a; 一、什么是高情商&#xff1f; 1.1 什么是高情商 1.2 情商的五大能力 1.3 高情商的层次 1.4 对高情商的误解? 二、工程师需要发展自己的高情商吗&#xff1f; 三、工程师如何扩展自己的情商&#xff1f; 四、什么样的“高情商”的管理者令人讨…

、写入Shellcode到注册表上线

其实本质就是将shellcode写入到注册表中&#xff0c;然后读取注册表中的shellcode&#xff0c;然后创建线程去执行shellcode。 如下图: 写入注册表shellcode 这里将shellcode写入到注册表中&#xff0c;在我们需要的时候再去读取然后执行。 这里用到如下两个Windows API函…

智能硬件(6)之通用引脚(GPIO)

小编带领大家学习的四大开源硬件和智能模块&#xff0c;他们之间是如何通信的&#xff0c;主控芯片是如何控制智能模块&#xff0c;做某些事情呢&#xff1f;有没有小朋友发起疑问呢&#xff1f; 这里&#xff0c;涉及到了特别重要的知识点&#xff0c;就是通用引脚&#xff0c…