动态规划 ------ 背包问题

文章目录

  • 1. 01 背包问题
    • 1.二维解决
    • 2. 一维优化
  • 2. 完全背包问题
    • 1.暴力3 for.
    • 2. 二维优化
    • 3. 一维优化
  • 3. 多重背包问题Ⅰ.
    • 1. 二维解决
    • 2. 一维优化
  • 4. 多重背包问题Ⅱ
  • 5. 混合背包问题
  • 6. 二维费用背包问题
  • 7. 分组背包问题

背包问题是动态规划中非常典型的一些题,本篇文章记录总结一下在学习过程中所遇到的一些背包问题。
其实主要就是3种,01,完全,多重背包问题,这三种详细一些,认真搞懂了,剩下的就是这三种的变种了,换汤不换药?

1. 01 背包问题

在这里插入图片描述

01背包问题链接

0 1 背包问题就是说每一件物品只能选择一次,所以我们每次选择的时候,只用两种可能,不选,0和1也就此而来。

1.二维解决

  • 状态表示:
  • 集合: f[i][j]代表前i件物品中体积不超过j的所有价值
  • 属性: 将所有的价值取max
  • 状态计算:
  • 对于当前的这一状态f[i][j]可以由上一个状态转移过来:
  • 0 - 不选选择当前物品 f[i][j] = f[i - 1][j].
  • 1 - 选择当前物品f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])

不选当前物品好理解,就是去直接继承选到前一个物品时候的价值.
而选择当前物品是有条件的,就是说当前背包的容量j 必须大于等于当前的物品的体积v[i]才能选择.j - v[i]说的是当前背包的容量减去当前物品的体积,咱既然要选择这个物品,那么必须得这个物品留出相应的空间。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;const int N = 1010;int n, m;
int v[N], w[N]; //v体积,w价值
int f[N][N];    //f[i][j] 表示前i件物品中体积是j的最大价值.int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);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] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);}printf("%d\n", f[n][m]);return 0;
}

2. 一维优化

我们还可以对其进行进行优化,可以讲二维优化成一维。
f[i]表示体积是i时候的最大价值.

  1. f[i][j] = f[i - 1][j] 变成 f[j] = f[j] 所以可以直接不写
  2. f[i - 1][j - v[i]] 变成f[j - v[i]]但是此变形并不是等价的,因为我们在真正循环的过程中其实是用f[i][j - v[i]] 于前面的不符,需要将其进行逆序。

大家也可看看这这一篇题解

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;const int N = 1010;int n, m;
int v[N], w[N]; //v体积,w价值
int f[N];    //f[i][j] 表示前i件物品中体积是j的最大价值.int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);for (int i = 1; i <= n; i++)for (int j = m; j >= v[i]; j--)f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d\n", f[m]);return 0;
}

2. 完全背包问题

在这里插入图片描述

完全背包问题链接

完全背包问题和01背包问题唯一的区别就是,完全背包中的物品可以使用无限次,
而01背包中的物品只能使用一次。

1.暴力3 for.

  • 状态表示:
  • 集合: f[i][j]表示背包容量是i中所选体积不超过j的所有价值
  • 属性: 对所有的价值取max
  • 状态计算:
  • 因为每一个物品可以选择无限次,前提是满足背包容量.
我们尝试用一个变量 k 来充当第i个物品选取k次,
那么k的范围则是 [0,k] 但是k必须满足 k * v[i] <= j. 
因为选取的体积不能超过背包的总容量。
即: f[i][j] = max(f[i][j],f[i - 1][j - k * v[i]] + k * w[i]);
  • 可以发现上面的方程和01背包问题一摸一样,只是多了一个k而已。如果不理解,将k = 1代入即使01背包问题的动态转移方程。而不选的时候则k = 0.
#include <iostream>
#include <algorithm>
using namespace std;const int N = 1010;int n, m;
int v[N], w[N];
int f[N][N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);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] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);printf("%d\n", f[n][m]);return 0;
}

这道题目用这个代码是会超时的,但是思路肯定是对的,理解思路才能理解下面的优化。

2. 二维优化

我们将上述的动态转移方程展开

2v = 2 * v && 2w = 2 * w;
f[i][j] = max(f[i-1][j-0v],f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i - 1][3v]+3w,...)
f[i][j-v] = max(f[i-1][j-v-0v]+0w,f[i-1][j-v-v]+w,f[i-1][j-2v]+2w+....)

在这里插入图片描述
我们可以发现,f[i][j]展开的这一大堆于f[i][j - v]展开的这一大堆是仅仅相差一个w
所以:
f[i][j] = max(f[i - 1][j], f[i][j - v] + w).

#include <iostream>
#include <algorithm>
using namespace std;const int N = 1010;int n, m;
int v[N], w[N];
int f[N][N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);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] = max(f[i][j], f[i][j - v[i]] + w[i]);}printf("%d\n", f[n][m]);return 0;
}

这个代码于01背包问题二维进仅仅差1个数字.
在这里插入图片描述

3. 一维优化

既然只差那一个数字,所以完全背包也可以将其进行优化成一维的。
我们之间在优化01背包的时候提到过:

  • f[i][j] = f[i- 1][j] 转化成f[j] = f[j]直接可以省略掉。
  • 01背包因为f[i - 1][j - v[i]] 转化成f[j]因为是i - 1所以需要逆序体积遍历。
  • 完全背包f[i][j - v[i]]转化成f[j]因为是i所以不需要进行逆序
#include <iostream>
#include <algorithm>
using namespace std;const int N = 1010;int n, m;
int v[N], w[N];
int f[N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);for (int i = 1; i <= n; i++)for (int j = v[i]; j <= m; j++)f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d\n", f[m]);return 0;
}

3. 多重背包问题Ⅰ.

在这里插入图片描述

多重背包问题Ⅰ链接

多重背包问题是将物品的个数做了限制,在给定的这些物品中,选择总价值不超过所给背包体积的最大总价值。

1. 二维解决

我们在上面的完全背包问题中已经学会了0~k次的选择一个物品,至于k的范围则是使k * v[i] <= j将其最大化。而本题中则可以直接获取。
前两种背包问题真的搞懂之后,这道题只需要加一个条件即可。
在这里插入图片描述

#include <iostream>
#include <algorithm>using namespace std;const int N = 110;int n, m;
int f[N][N];
int v[N], w[N], cnt[N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d%d", &v[i], &w[i], &cnt[i]);for (int i = 1; i <= n; i++)for (int j = 0; j <= m; j++)for (int k = 0; k <= cnt[i] && k * v[i] <= j; k++)f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);printf("%d\n", f[n][m]);return 0;
}

2. 一维优化

同之前的优化一样,直接去掉一个维度。
他是01背包问题的变种,删除前是i - 1与删除后并不是等价的,所以需要逆序。

#include <iostream>
#include <algorithm>using namespace std;const int N = 110;int n, m;
int f[N];
int v[N], w[N], cnt[N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%d%d", &v[i], &w[i], &cnt[i]);for (int i = 1; i <= n; i++)for (int j = m; j >= v[i]; j--)for (int k = 0; k <= cnt[i] && k * v[i] <= j; k++)f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);printf("%d\n", f[m]);return 0;
}

4. 多重背包问题Ⅱ

多重背包问题Ⅱ链接
这个问题与上方的区别只是数据范围变了,之所以单独拿出来,是因为,这里涉及到了一个二进制的优化,感觉还是挺重要的。
我们假设去店里面送水果,有13颗苹果,16颗梨,11颗西瓜。需要讲这40个水果搬如店中,我们如果一个一个搬,需要40次,说实话有点捞。。。但凡是个正常人我们都应该讲其分开搬,具体怎么分呢,我们采取二进制的方式将其分开。
1,2,4,8,16,32........等等这样一直分下去,但前提一定得是一样的物品分一起,不然你的体积和价值如何计算?
上述例子:
苹果: 1, 2 ,4, 7.
梨: 1, 2, 4, 8, 1.
西瓜: 1, 2, 4, 4.
我只是举个例子,现实生活中肯定不是二进制的搬,1个苹果占一个箱子?
下面的代码就是向上述例子一样,将其一类一类的分组打包。
然后就会变成了01背包问题。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;const int N = 1e5 + 10, M = 2010;int n, m;
int v[N], w[N];
int f[M];int main()
{scanf("%d%d", &n, &m);int cnt = 0;	//从1开始放。for (int i = 1; i <= n; i++){int a, b, c;scanf("%d%d%d", &a, &b, &c);int k = 1;while (k <= c){v[++cnt] = a * k;	//先++。w[cnt] = b * k;c -= k;k *= 2;}if (c > 0){v[++cnt] = a * c;w[cnt] = b * c;}}n = cnt;for (int i = 1; i <= n; i++)for (int j = m; j >= v[i]; j--)f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d\n", f[m]);return 0;
}

5. 混合背包问题

在这里插入图片描述

混合背包问题链接

混合背包问题就是讲前面所说的3中背包混合在一起。
我们既然已经会了前三种的状态转移方程,那么我们只需要在做的时候,对其进行套用相应的方程即可。
又因为多重背包问题可以将其进行二进制优化转化为01背包问题,所以我们只需要对其进行2个方程各自对应即可。
总的来说也就2个步骤

  • 将多重背包利用二进制优化转化为01背包
  • 将01背包与完全背包各自动态转移方程代入即可。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;const int N = 1010;int n, m;
int f[N];
struct Thing
{int kind;int v, w;
};
vector<Thing> things;int main()
{scanf("%d%d", &n, &m);//第一步:将多重背包利用二进制优化转化为01背包for (int i = 1; i <= n; i++){int v, w, s;scanf("%d%d%d", &v, &w, &s);if (s <= 0) //01背包或者完全背包直接插入即可。things.push_back({s, v, w});else{//多重背包进行二进制优化,分包for (int k = 1; k <= s; k *= 2){s -= k;things.push_back({-1, v * k, w * k});}if (s > 0)things.push_back({-1, v * s, w * s});}}//第二步:将01背包与完全背包各自动态转移方程代入即可。for (auto t : things){if (t.kind == -1)   //01背包的动态转移方程for (int j = m; j >= t.v; j--)f[j] = max(f[j], f[j - t.v] + t.w);else                //完全背包的动态转移方程for (int j = t.v; j <= m; j++)f[j] = max(f[j], f[j - t.v] + t.w);}printf("%d\n", f[m]);return 0;
}

6. 二维费用背包问题

在这里插入图片描述

二维费用背包问题链接

二维费用,只是多了一个书包的承受范围,只需要在01背包的基础上多加一层关于重量的的循环就好了。

#include <iostream>
#include <algorithm>
using namespace std;const int N = 1010;int n, V, M;            //个数,容积, 承受范围
int v[N], m[N], w[N];   //体积,重量,价值
int f[N][N];            //f[i][j]表示容积不超过i,重量不超过j的最大价值int main()
{scanf("%d%d%d", &n, &V, &M);for (int i = 1; i <= n; i++)scanf("%d%d%d", &v[i], &m[i], &w[i]);for (int i = 1; i <= n; i++)for (int j = V; j >= v[i]; j--)         //容积for (int k = M; k >= m[i]; k--)     //承受范围f[j][k] = max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);printf("%d\n", f[V][M]);return 0;
}

7. 分组背包问题

在这里插入图片描述

分组背包问题链接

分组背包问题,其实归根结底还是01背包问题,每组只能选择一个物品出来,只不过这一组有很多个物品。
所以我们针对其每一组,将其所有的物品都遍历一遍就好了,在01背包问题上再加一层循环,3for就好了。
01背包问题,同样还是逆序,不过判断j >= v[i]的条件变成了j >= v[k]了。

#include <iostream>
#include <algorithm>
using namespace std;const int N = 110;int n, m;
int f[N], v[N], w[N];int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++){int s;scanf("%d", &s);for (int j = 1; j <= s; j++)scanf("%d%d", &v[j], &w[j]);for (int j = m; j >= 0; j--)for (int k = 1; k <= s; k++)if (j >= v[k])f[j] = max(f[j], f[j - v[k]] + w[k]);}printf("%d\n", f[m]);return 0;
}

🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈

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

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

相关文章

某站戴师兄——Excel学习笔记

1、拿到源数据第一件事——备份工作表&#xff0c;隐藏 Ctrlshift键L打开筛选 UV (Unique visitor)去重 是指通过互联网访问、浏览这个网页的自然人。访问网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只被计算一次。一天内同个访客多次访问仅计算一个UV。 PV …

进程与线程(进程)

进程&#xff1a; 概念&#xff1a;进程是进程实体的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位 PID:当进程被创建时&#xff0c;操作系统会为该进程分配一个唯一的、不重复的“身份证号” 组成&#xff1a; PCB&#xff08;进程控制块&#xff09;&#…

芋道源码的Springboot 项目打包,配置和依赖包分开

Springboot 项目&#xff0c;把依赖包和开发的应用都打在一个jar 里很简单&#xff0c;但有个问题是&#xff0c;修改点东西就要再次全量更新。 这里介绍如何用assembly 来实现不打依赖包。 1、 在主模块中&#xff0c;需要引入 assembly.xml配置&#xff1a; src/main/asse…

我这次没有蹭Oracle发布热度的原因

这次没有去蹭热度&#xff0c;原因有几个。 主观 确实是生病了&#xff0c;身体不舒服&#xff0c;那几个卷王在卷公众号的时候&#xff0c;我在床上卷成一团。 不和这几个打了鸡血的人比了。我卷了一点和他们不一样的。我节日期间看到我初中同班同学发的微博。 对这个就是我…

大学生上班族必备!九个线上兼职秘籍,让你远离失业风险

互联网时代&#xff0c;兼职新风尚&#xff1a;这些靠谱兼职让你轻松增收 随着互联网技术的飞速发展&#xff0c;兼职工作已成为许多人增加收入、提升自我能力的新选择。本文将为您揭秘一些适合大学生和上班族的靠谱兼职工作&#xff0c;助您轻松找到适合自己的兼职机会。 一…

docker系列8:容器卷挂载(上)

目录 传送门 从安装redis说起 什么是容器卷挂载 操作系统的挂载 日志文件一般是"首恶元凶" 挂载命令 容器卷挂载 卷挂载命令 启动时挂载 查看挂载卷信息 容器卷管理 查看卷列表 创建容器卷 具名挂载与匿名挂载 具名挂载 传送门 docker系列1&#xff…

C++ : list类及其模拟实现

目录 一、list的介绍和使用 list的介绍 list的使用 1.list的构造 构造函数 2.list iterator 的使用 3.list capacity 4.list element access 5.list modifiers 6.list的迭代器失效 二、list的模拟实现 要点 list类模拟实现部分接口全部代码展示 一、list的介绍和使…

Docker:centos7安装docker

官网&#xff1a;https://www.docker.com/官网 文档地址 - 确认centos7及其以上的版本 查看当前系统版本 cat /etc/redhat-release- 卸载旧版本 依照官网执行 - yum安装gcc相关 yum -y install gccyum -y install gcc-c- 安装需要的软件包 yum install -y yum-utils- 设置s…

深入学习Linux内核页框回收

目录 算法 1.选择目标页 2.PFRA设计 3.反向映射 3.1.匿名页的反向映射 3.2.try_to_unmap_anon()函数 3.3.try_to_unmap_one()函数 映射页的反向映射 优先搜索树 try_to_unmap_file()函数 PFRA实现 最近最少使用(LRU)链表 在LRU链表之间移动页 mark_page_accessed(…

Android使用kts发布aar到JitPack仓库

Android使用kts发布aar到JitPack 之前做过sdk开发&#xff0c;需要将仓库上传到maven、JitPack或JCenter,但是JCenter已停止维护&#xff0c;本文是讲解上传到JitPack的方式,使用KTS语法&#xff0c;记录使用过程中遇到的一些坑.相信Groovy的方式是大家经常使用的&#xff0c;…

Java基于Spring Boot框架的课程管理系统(附源码,说明文档)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

基于Springboot的校园疫情防控系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

C# WCF服务(由于内部错误,服务器无法处理该请求。)

由于内部错误&#xff0c;服务器无法处理该请求。有关该错误的详细信息&#xff0c;请打开服务器上的 IncludeExceptionDetailInFaults (从 ServiceBehaviorAttribute 或从 <serviceDebug> 配置行为)以便将异常信息发送回客户端&#xff0c;或打开对每个 Microsoft .NET …

从零开始:Django项目的创建与配置指南

title: 从零开始&#xff1a;Django项目的创建与配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 后端开发 tags: DjangoWebDevPythonORMSecurityDeploymentOptimization Django简介&#xff1a; Django是一个开源的高级Python Web框架&#xff…

C语言之整形提升和算术转换

目录 前言 一、整形提升 二、算术转换 总结 前言 本文主要介绍C语言中的整形提升和算术转换的概念和意义&#xff0c;以及例题帮助理解&#xff0c;了解之后&#xff0c;我们就能知道在C语言中&#xff0c;字符型变量如何计算以及如果变量的类型、字节大小不一致的情况下&am…

golang学习笔记(内存模型和分配机制)

操作系统的存储管理 虚拟内存管理 虚拟内存是一种内存管理技术&#xff0c;它允许操作系统为每个进程提供一个比实际物理内存更大的地址空间。这个地址空间被称为虚拟地址空间&#xff0c;而实际的物理内存则被称为物理地址空间。使用虚拟内存有以下几点好处&#xff1a; 内…

git 第一次安装设置用户名密码

git config --global user.name ljq git config --global user.email 15137659164qq.com创建公钥命令 输入后一直回车 ssh-keygen -t rsa下面这样代表成功 这里是公钥的 信息输入gitee 中 输入下面命令看是否和本机绑定成功 ssh -T gitgitee.com如何是这样&#xff0c;恭喜…

基于51单片机PWM控制直流电机—数码管显示

基于51单片机PWM控制直流电机 &#xff08;仿真&#xff0b;程序&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.L298驱动直流电机&#xff1b; 2.数码管显示转动方向和PWM占空比&#xff08;0-100%&#xff09;&#xff1b; 3.按键控制PWM占空比来加/…

20232803 2023-2024-2 《网络攻防实践》实践八报告

目录 1. 实践内容2. 实践过程2.1 动手实践任务一2.2 动手实践任务二&#xff1a;分析Crackme程序2.2.1 crackme1.exe2.2.2 crackme2.exe 2.3 分析实践任务一2.4 分析实践任务二 3. 学习中遇到的问题及解决4. 学习感悟、思考等 1. 实践内容 动手实践任务一&#xff1a;对提供的r…

R语言实战——中国职工平均工资的变化分析——相关与回归分析

链接: R语言学习—1—将数据框中某一列数据改成行名 R语言学习—2—安德鲁斯曲线分析时间序列数据 R语言学习—3—基本操作 R语言学习—4—数据矩阵及R表示 R语言的学习—5—多元数据直观表示 R语言学习—6—多元相关与回归分析 1、源数据 各行业平均工资变化 各地区平均工资…