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

文章目录

  • 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.mfbz.cn/a/593812.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ctfshow——SSRF

文章目录 web 351web 352web 353web 354web 355web 356web357web 358web 359web 360 SSRF(Server-Side Request Forgery&#xff1a;服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是从外网无法访问的内部系统…

同向双指针(滑动窗口)算法

209. 长度最小的子数组 这里的更新结果就题来定 class Solution {public int minSubArrayLen(int target, int[] nums) {int sum 0;int len 0;int f 0;for(int left 0, right 0; right < nums.length;){//求和sum nums[right];while(sum > target){//lenint t ri…

分布式锁之-redis

什么是分布式锁&#xff1f; 即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题&#xff0c;而分布式锁&#xff0c;就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是&#xff0c;分布式系统中竞争共享资源的最小粒度从线程升级成了…

JVM垃圾回收器G1大总结-详解

一、介绍: 1.停顿时间模型?? 作为CMS收集器的替代者和继承人&#xff0c;G1是基于“停顿时间模型”诞生的的垃圾收集器&#xff0c;停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段内&#xff0c;消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标. 2.G1摒…

进程与线程(进程)

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

使用AIGC生成软件类图表

文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存&#xff1f;mermaid.liveDraw.io Mermaid可以画什么图&#xff1f;流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…

W801学习笔记二十:宋词学习应用

前三章完成了唐诗的应用&#xff0c;本章将实现宋词的学习应用。 宋词与唐诗的区别不大&#xff0c;马上开始。 1、我们需要参考前面唐诗的方式&#xff0c;把宋词文本下载下来&#xff0c;并进行格式整理。 W801学习笔记十七&#xff1a;古诗学习应用——上 2、在菜单中添加…

电脑上的视频在电视上播放

视频右键->播放到设备->客厅电视 海信电视测试成功

BI不等同数据分析,别搞错了!

✅作者简介&#xff1a;《数据运营&#xff1a;数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 &#x1f338;公众号&#xff1a;风姑娘的数字视角&#xff0c;免费分享数据应用相…

【Transformer系列(1)】self-attention自注意力

一、self-attention流程 自注意力机制和注意力机制的区别在于&#xff0c;注意力机制中的Q&#xff08;查询向量&#xff09;&#xff0c;K&#xff08;键向量&#xff09;&#xff0c;V&#xff08;值向量&#xff09;是同源的&#xff0c;而一般的注意力机制&#xff0c;Q和…

谈谈Tcpserver开启多线程并发处理遇到的问题!

最近在学习最基础的socket网络编程&#xff0c;在Tcpserver开启多线程并发处理时遇到了一些问题&#xff01; 说明 在linux以及Windows的共享文件夹进行编写的&#xff0c;所以代码中有的部分使用 #ifdef WIN64 ... #else ... #endif 进入正题&#xff01;&#xff01;&…

50个前端实战项目之04:隐藏的搜索小组件

大家好&#xff0c;我是宝哥。 今天讲50个前端实战项目之04&#xff1a;隐藏的搜索小组件。 源码下载地址 https://github.com/bradtraversy/50projects50days/tree/master/hidden-search 前端实战项目系列正在更新&#xff1a;04/50 01&#xff1a;可展开卡片02&#xff1a;进…

C语言中的goto语句

goto label; C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。 #include <stdio.h> int main(){goto first;printf("我是你好\n");first:printf("nihao\n");second:printf("This is 2\n");return 0; } 使用goto会…

西门子1200脉冲轴【PTO】

原理图&#xff1a; PTO (Pulse Train Output) 脉冲串输出 PLC的电压是DC24v的&#xff0c;所以驱动器的信号电压是否支持&#xff0c;以免烧坏。 台达A2&#xff1a; //L型机&#xff1a; //&#xff08;方向&#xff1a;3524v&#xff0c;393.3v&#xff0c;37-GN…

力扣 647. 回文子串

题目来源&#xff1a;https://leetcode.cn/problems/palindromic-substrings/description/ C题解1&#xff1a;暴力解法。不断地移动窗口&#xff0c;判断是不是回文串。 class Solution { public:int countSubstrings(string s) {int len s.size();int res 0;for(int i 0;…

【JAVA项目】基于个人需求和地域特色的【外卖推荐系统】

技术简介&#xff1a;采用B/S架构、ssm 框架、Java技术、MySQL等技术实现。 系统简介&#xff1a;统权限按管理员&#xff0c;商家和用户这三类涉及用户划分。(a) 管理员&#xff1b;管理员使用本系统涉到的功能主要有&#xff1a;首页&#xff0c;个人中心&#xff0c;用户管理…

GDPU unity游戏开发 碰撞器与触发器

砰砰叫&#xff0c;谁动了她的奶酪让你的小鹿乱撞了。基于此&#xff0c;亦即碰撞与触发的过程。 碰撞器与触发器的区别 通俗点讲&#xff0c;碰撞器检测碰撞&#xff0c;触发器检测触发&#xff0c;讲了跟没讲似的。碰撞器是用来检测碰撞事件的&#xff0c;在unity中&#xff…

JavaWeb请求响应概述

目录 一、请求响应流程-简述 二、深入探究 三、DispatcherServlet 四、请求响应流程-详细分析 一、请求响应流程-简述 web应用部署在tomcat服务器中&#xff0c;前端与后端通过http协议进行数据的请求和响应。前端通过http协议向后端发送数据请求&#xff0c;就可以访问到部…

Golang日志管理:使用log/slog实现高级功能和性能优化

Golang日志管理&#xff1a;使用log/slog实现高级功能和性能优化 简介基础使用初始化和配置日志级别 高级技巧自定义日志格式器条件日志处理 实战案例场景一&#xff1a;API请求日志记录场景二&#xff1a;错误跟踪和用户通知 性能优化优化日志记录的性能异步日志处理选择合适的…

算法设计与分析——期末1h

目录 第一章 算法的定义 算法的三要素 算法的基本性质 算法的时间复杂度数量级&#xff1a; 第二章 兔子繁殖问题&#xff08;递推法&#xff09; 猴子吃桃问题&#xff08;递推法&#xff09; 穿越沙漠问题&#xff08;递推法&#xff08;倒推&#xff09;&#xff09; 百钱百…
最新文章