acwing 动态规划dp 0 1背包问题

news2024/11/18 20:44:36

                                                               前言

         hello小伙伴们,最近由于个人放假原因颓废了一段时间很长时间没有更新CSDN的内容了,唉,毕竟懂得都懂寒暑假静下心来学习的难度远比在学校里大的多。

        但是,也不是毫无办法克服,今天我来了我们当地的一家自习室来学习,感觉效果比在家强很多,趁机写一下博客分享一下最近的收获。

        今天没写蓝桥杯备赛系列因为我感觉这块内容应该是蓝桥杯的一个重点考察方向,所以我想先讲知识点然后过渡讲蓝桥杯系列,包括dfs、bfs那块内容也是这个套路,尽量是能让我和大家收获最大为好。不多bb上内容。

                                                                DP 01背包模型

DP(动态规划) 内容核心讲解

 

状态表示:用一个数组f[][](数组可能是一维也可能是二维,根据具体题目具体分析)来表示某个集合,这个集合表示所有的做法,集合存的值就是对应做法的属性(一般是max,min,count)(换句话说:f[i][j]表示在限制i,j下做法的属性)
状态转移:本质上是一个优化的过程,就是不断更新状态。

01背包问题

思路

重要变量说明:

f[][]:用于状态表示;

w[]:记录每个物品的价值

v[]:记录每个物品的体积

1. 定义二维数组f[][],其中f[i][j]表示在前i个物品,背包容积为j的限制下所能装下的最大价值。这里的f[i][j]就是做法的集合,f[i][j]的值就是最大价值即属性。
2. 从i=1开始枚举,对于第i个物品,都有选和不选两种选择:
如果不选第i个物品,那么状态转移方程为f[i][j]=f[i-1][j]
如果选择第i个物品,那么状态转移方程为f[i][j]=f[i-1][j-v[i]]+w[i]
3. 我们因为要求最大价值,所以对上面两种情况去max即可

为什么可以转为一维

首先观察状态转移方程 dp[i][j]是由 dp[i-1][jxxxx]推导而来,仅看第一个维度,即i - 1 与 i ,可以发现第i层是由上一层推导而来的。

故我们不必要保存i - 2 层,比如我们计算第三层是只需要第二层的。不需要第一层的数据。

当我们去掉i时,即我们不需要控制第几层,只需要长度为j的数组,保存确认过最新的一层。作为下一层的参考。例如我们计算第三层dp时,此时dp原数据保存的是第二层的结果。

为什么要逆序

首先,通过上一个问题,我们确认了我们目前一维的dp数组,保存的是确认过的最新一层的数据,即上一层的数据。

当我们计算当前层时,对于二维时的状态转移方程有

dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);

可以看到,dp[i - 1][j - v[i]] + w[i] 使用的上一层的原始数据(dp[i - 1]),而我们使用一维的状态转移方程时有

dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

当我们从小到大更新是, 因为j - v[i] 是严格小于j 的,所以我们可以举个例子 dp[3] = max(dp[3], dp[2] + 1); 因为我们是从小到大更新的,所以当更新到dp[3]的时候,dp[2]已经更新过了,已经不是上一层的dp[2]。

而当我们逆序更新时有,举例 dp[8] = max(dp[8], dp[6] + 2)当更新dp[8]时,dp[6]还没有被更新,还是上一层的数据,这样才能保证没有读入脏数据。

#include<iostream>

using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];

int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++)   
        cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)       //从第一个物品开始选,直到最后一个物品结束
        for(int j=1;j<=V;j++)       //从最小的体积开始,直到背包的最大的容积
        {
            if(j-v[i]>=0)       //可以装第i个物品
                f[i][j]=f[i-1][j-v[i]]+w[i];        //状态转移
            f[i][j]=max(f[i][j],f[i-1][j]);     //状态转移,两种情况取最大值
        }
    cout<<f[n][V]<<endl;
    return 0;
}

 

优化1(滚动数组)

注意: 这里优化的的是空间而不是时间

思路

我们注意到其实上面写的f[i][j]其实每次计算只是用到了第i层和第i-1层,所以我们数组的第一维其实只用开两个即可。

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

 

优化2(一维数组)


我们可以把二维数组优化到一维数组
为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态

思路


定义f[]表示状态,f[j]表示在N个物品,背包容积为j下所能装下的最大价值。
也还是从i开始枚举,表示选与不选第i个物品。
注意
我们要逆序枚举背包容积,即每次循环从背包的最大容积开始枚举。
**原因如下:**在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
**简单来说:**简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

来个例子

    如果 j 层循环是递增的: 
    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]);
        }
    }
 

 经典代数法模拟运行过程
    当还未进入循环时:
    f[0] = 0;  f[1] = 0;  f[2] = 0;  f[3] = 0;  f[4] = 0;  
    f[5] = 0;  f[6] = 0;  f[7] = 0;  f[8] = 0;  f[9] = 0; f[10] = 0;
    当进入循环 i == 1 时:
    f[4] = max(f[4], f[0] + 5); 即max(0, 5) = 5; 即f[4] = 5;
    f[5] = max(f[5], f[1] + 5); 即max(0, 5) = 5; 即f[5] = 5;
    f[6] = max(f[6], f[2] + 5); 即max(0, 5) = 5; 即f[6] = 5;
    f[7] = max(f[7], f[3] + 5); 即max(0, 5) = 5; 即f[7] = 5;
    重点来了!!!
    f[8] = max(f[8], f[4] + 5); 即max(0, 5 + 5) = 10; 即f[8] = 10;
    这里就已经出错了
    因为此时处于 i == 1 这一层,即物品只有一件,不存在单件物品满足价值为10
    所以已经出错了。

    如果 j 层循环是逆序的:
    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]);
        }
    }
 

代数法模拟过程
    当还未进入循环时:
    f[0] = 0;  f[1] = 0;  f[2] = 0;  f[3] = 0;  f[4] = 0;  
    f[5] = 0;  f[6] = 0;  f[7] = 0;  f[8] = 0;  f[9] = 0; f[10] = 0;
    当进入循环 i == 1 时:w[i] = 5; v[i] = 4;
    j = 10:f[10] = max(f[10], f[6] + 5); 即max(0, 5) = 5; 即f[10] = 5;
    j = 9 :f[9] = max(f[9], f[5] + 5); 即max(0, 5) = 5; 即f[9] = 5;
    j = 8 :f[8] = max(f[8], f[4] + 5); 即max(0, 5) = 5; 即f[8] = 5;
    j = 7 :f[7] = max(f[7], f[3] + 5); 即max(0, 5) = 5; 即f[7] = 5;
    j = 6 :f[6] = max(f[6], f[2] + 5); 即max(0, 5) = 5; 即f[6] = 5;
    j = 5 :f[5] = max(f[5], f[1] + 5); 即max(0, 5) = 5; 即f[5] = 5;
    j = 4 :f[6] = max(f[4], f[0] + 5); 即max(0, 5) = 5; 即f[4] = 5;
    当进入循环 i == 2 时:w[i] = 6; v[i] = 5; 
    j = 10:f[10] = max(f[10], f[5] + 6); 即max(5, 11) = 11; 即f[10] = 11;
    j = 9 :f[9] = max(f[9], f[4] + 6); 即max(5, 11) = 5; 即f[9] = 11;
    j = 8 :f[8] = max(f[8], f[3] + 6); 即max(5, 6) = 6; 即f[8] = 6;
    j = 7 :f[7] = max(f[7], f[2] + 6); 即max(5, 6) = 6; 即f[7] = 6;
    j = 6 :f[6] = max(f[6], f[1] + 6); 即max(5, 6) = 6; 即f[6] = 6;
    j = 5 :f[5] = max(f[5], f[0] + 6); 即max(5, 6) = 6; 即f[5] = 6;
    当进入循环 i == 3 时: w[i] = 7; v[i] = 6; 
    j = 10:f[10] = max(f[10], f[4] + 7); 即max(11, 12) = 12; 即f[10] = 12;
    j = 9 :f[9] = max(f[9], f[3] + 6); 即max(11, 6) = 11; 即f[9] = 11;
    j = 8 :f[8] = max(f[8], f[2] + 6); 即max(6, 6) = 6; 即f[8] = 6;
    j = 7 :f[7] = max(f[7], f[1] + 6); 即max(6, 6) = 6; 即f[7] = 6;
    j = 6 :f[6] = max(f[6], f[0] + 6); 即max(6, 6) = 6; 即f[6] = 6;
一维代码优化后 
#include<iostream>

using namespace std;
const int N=1010;
int f[N],v[N],w[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>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]);
    cout<<f[m]<<endl;
    return 0;
}

今天就讲完了全部内容,但是我从如下几个方面总结一下今天讲的内容

首先讲了dp问题的大思路,然后给大家放了一下acwing上面的那个例题,然后给大家讲解了一下为什么二维可以转一维。

下次讲全部背包问题,讲完dp问题上蓝桥杯习题课系列,到时候就不讲知识点了。

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

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

相关文章

《WebKit 技术内幕》学习之四(3): 资源加载和网络栈

3. 网络栈 3.1 WebKit的网络设施 WebKit的资源加载其实是交由各个移植来实现的&#xff0c;所以WebCore其实并没有什么特别的基础设施&#xff0c;每个移植的网络实现是非常不一样的。 从WebKit的代码结构中可以看出&#xff0c;网络部分代码的确比较少的&#xff0c;它们都在…

Mybatis 全局配置文件(三)

文章目录 第一章&#xff1a;概述第二章&#xff1a;properties (了解)第三章&#xff1a;settings第四章&#xff1a;typeAliases (别名处理器)第五章&#xff1a;typeHandlers (类型处理器)第六章&#xff1a;plugins(插件)第七章&#xff1a;environments (环境)第八章&…

k8s-基础知识(Service,NodePort,CusterIP,NameSpace,资源限制)

Node Node 是 Pod 真正运行的主机&#xff0c;可以是物理机&#xff0c;也可以是虚拟机。 Annotations 原文链接 Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象&#xff0c;Annotations 则是用来记录一些附加信息&#xff0c;用来辅助应…

深入理解JS语法与变量

深入理解JS语法与变量 前言初识JavaScriptJavaScript的语言风格和特性 JavaScript的书写位置认识输出语句学会处理报错REPL 环境 变量定义变量改变变量变量的合法命名变量的默认值变量的常见错误等号表示赋值同时声明多个变量 变量声明提升注意事项 结语 前言 在现代Web开发中…

Vue构建项目断点调试过程问题总结

Vue构建项目断点调试过程问题总结 问题背景 前端开发过程中&#xff0c;碰到问题时需要debug&#xff0c;快速分析和解决问题。一般除了console.log的方式打印日志外&#xff0c;更方便直观的方式就是打断点debug。本文对vue项目debug过程可能碰到的问题进行总结&#xff0c;…

Kafka(二)原理详解

一 、kafka核心总控制器&#xff08;Controller&#xff09; 在Kafka集群中会有一个或者多个broker&#xff0c;其中有一个broker会被选举为控制器&#xff08;Kafka Controller&#xff09;&#xff0c;它负责管理整个集群中所有分区和副本的状态。 作用&#xff1a;leader副…

FireAlpaca:轻量级、免费的Mac/Win绘图软件,让你的创意如火燃烧!

FireAlpaca是一款轻量级、免费的绘图软件&#xff0c;适用于Mac和Win系统&#xff0c;让你的创作过程更加快捷、简便。无论是绘制漫画、插图、设计作品还是进行简单的图片编辑&#xff0c;FireAlpaca都能满足你的需求。 首先&#xff0c;FireAlpaca具有直观友好的用户界面&…

国辰智企资产管理系统:实现资产精细化管理的首选

在市场竞争日益激烈、金融环境不断变化的背景下&#xff0c;有效的资产管理已成为企业保持竞争优势和实现财务目标的关键。传统资产管理方法已显不足以适应现代经济环境的快速变化。为了迎接这一挑战&#xff0c;越来越多企业纷纷采用先进的资产管理系统&#xff0c;以提高效率…

【面试突击】微信亿级朋友圈的社交系统设计

微信亿级朋友圈的社交系统设计 先来说一下业务需求吧&#xff1a; 每个用户可以发朋友圈&#xff0c;可以点赞&#xff0c;评论可以设置权限&#xff0c;不看某些人朋友圈、不让某些人看你的朋友圈可以刷朋友圈中其他人的动态 对于这样的系统设计&#xff0c;主要从业务来考虑…

Springboot项目启动报错:Command line is too long问题解决

启动项目报错:Error running ‘xxxxxxxx’: Command line is too long. Shorten command line for ‘xxxxxxxx’ or also for Application default configuration 方法一 点击提示中的&#xff1a;default&#xff1a;然后在弹出窗口中选择&#xff1a;JAR xxxx xxx&#xff0…

搭建k8s集群实战(一)系统设置

1、架构及服务 Kubernetes作为容器集群系统&#xff0c;通过健康检查重启策略实现了Pod故障自我修复能力&#xff0c;通过调度算法实现将Pod分布式部署&#xff0c;并保持预期副本数&#xff0c;根据Node失效状态自动在其他Node拉起Pod&#xff0c;实现了应用层的高可用性。 …

手势识别MATLAB代码

手势识别是智能设备常用的需求, 下面我们用MATLAB来识别手部的形态: 主程序main.m clc;clear all;close all;%清除命令行和窗口 imimread(DSC05815.JPG); [skin,bwycbcr,w,h] hand_segmentation(im); im1bwycbcr; % se strel(ball,[1 1 1;1 1 1;1 1 1]); im1 imdilate(im…

HarmonyOS鸿蒙学习笔记(22)@Builder实战

Builder标签是一种更轻量的UI元素复用机制&#xff0c;下面通过一个简单的例子来具体说明&#xff1a; 比如如下布局效果&#xff1a;上面是一个轮播的Swiper,下面是一个Grid 布局代码如下&#xff1a; build() {Navigation() {Scroll() {Column({ space: CommonConstants.CO…

html+css浮动小练习

以下是代码&#xff1a; <!DOCTYPE html> <!-- 2024.1.23 --> <html lang"en"> <head><meta charset"UTF-8"><title>浮动布局练习</title><style>.leftfix{float: left;}.rightfix{float:right;}/* 问题…

Leetcode—24. 两两交换链表中的节点【中等】

2023每日刷题&#xff08;八十七&#xff09; Leetcode—24. 两两交换链表中的节点 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x),…

如何进行H.265视频播放器EasyPlayer.js的中性化设置?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

HTML 入门手册(二)

目录 HTML 入门手册(一) 10-表单 11-input标签 11.1文本框 (text) 11.2密码框 (password) 11.3单选按钮 (radio) 11.4复选框 (checkbox) 11.5普通按钮 11.6提交按钮 (submit) 11.7重置按钮 (reset) 11.8隐藏域 (hidden) 11.9文件上传 (file) 11.10数字输入 (numbe…

Power Apps 向Power Automate传一个数组参数

Power Apps传Power Automate数组参数 背景Power Apps传参方法画布开发我们现在power apps中设置一个集合**ArrCollect**准备一个按钮 Power Automate接收总结画布流 背景 我们通常会从Power Apps界面传递参数给Flow中&#xff0c;但是很多时候仅仅是一个字符串类型的已经不适用…

超融合系统疑难故障定位与解决实践 3 例(含信创技术栈)

当 IT 系统出现故障&#xff0c;问题定位往往是运维人员最头疼的环节。尤其是超融合系统&#xff0c;由于整体涉及的技术栈比较复杂&#xff0c;且有越来越多的用户基于信创环境进行部署&#xff0c;非常考验厂商和技术人员的专业能力&#xff1a;厂商研发和售后工程师不仅应能…

web安全学习笔记【07】——非http\https抓包

#知识点&#xff1a; 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OSS&反向&负载均衡等 ----------------------------------- 1、APP架构-封装&原生态&H5&flutter…