单调队列优化DP模型整理

135. 最大子序和(活动 - AcWing)

找一个长度不超过m的连续子序列,但是并未指定这个子序列的长度,所以长度就有很多种选择,要获取任意一段长度的序列的区间和,那么显然要用到前缀和。然后我们来考虑,讨论以每个点作为结尾的序列显然可以将所有情况都不重不漏地考虑进去。那么就是考虑如何获得以某个节点作为结尾的子序列,长度为m,显然有一个思路就是暴力求解,即第一维循环尾节点,第二维循环往前延伸多少,实现是一定可以实现的,而时间复杂度就很高了,大概O(nm),所以我们要考虑考虑还有没有其他的解法。我们看一下求的过程,显然是用尾节点的前缀和减去前面的合法位置的前缀和,如果有一个点的前缀和小于它前面所有合法位置的前缀和,那么显然它前面所有位置的前缀和都不会再被用到,因为用这个点一定更优,所以我们相当于有一个容器来维护当前节点之前的合法位置的前缀和,然后保证最前面的元素一定是最小的,那么就成了滑动窗口问题。

#include<bits/stdc++.h>
using namespace std;
int a[300010],q[300010];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int res;
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]),a[i]+=a[i-1];
        if(1!=i) res=min(a[i],res);
        else res=a[i];
    }
    int hh=0,tt=0;//tt就指向末尾位置,将hh置空
    for(int i=1;i<=n;i++)//以i作为结尾,前面最多用到i-m+1位,但是求前缀和要再往前一位,故而就是i-m
    {
        while(q[hh]<i-m) hh++;//因为i-1肯定被放入了,所以不用担心队列为空,即使最开始0<1也是可以的
        res=max(res,a[i]-a[q[hh]]);//hh>tt的时候相当于弹空,弹空位置是0,刚好
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
        q[++tt]=i;
        //相当于已经放入了0,从1开始放前缀和
    }
    cout<<res;
}

 ps:单调队列自己写清楚就行,不是非得这么模拟。

1088. 旅行问题(活动 - AcWing)

如图,我们对于每个起点都需要判断,同时还需要判断是顺时针走还是逆时针走,只要两者之间有一个满足能够回到起点,那么就是该点就是可以的。 

顺时针的如下考虑:

所以最核心的地方在于在数轴上正确找到我们要求的区间。

哦另外,这个题的数据范围可能会爆int,记得处理一下

#include<bits/stdc++.h>
#define int long long
using namespace std;
int o[1000010],d[1000010],s[2000010],ans[1000010],q[2000010],hh,tt;
signed main()
{
    int n;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&o[i],&d[i]);
    //顺时针
    for(int i=1;i<=n;i++) s[i+n]=s[i]=o[i]-d[i];
    for(int i=1;i<=2*n;i++) s[i]+=s[i-1];
    hh=tt=0;
    for(int i=2*n;i>=0;i--)
    {
        while(hh<tt&&q[hh]>i+n)hh++;
        if(i<n&&s[q[hh]]-s[i]>=0) 
        {
           // printf("1:%d %d\n",i,q[hh]);
            ans[i+1]=1;
        }
        while(hh<tt&&s[q[tt-1]]>=s[i]) tt--;
        q[tt++]=i;
    }
    
    //逆时针
    d[0]=d[n];
    for(int i=1;i<=n;i++) s[i+n]=s[i]=o[i]-d[i-1];
    for(int i=1;i<=2*n;i++) s[i]+=s[i-1];
    hh=0,tt=0;
    for(int i=1;i<=2*n;i++)
    {
        //4-1
        while(hh<tt&&q[hh]<i-n)hh++;
        if(i>n)
        {
            if(s[i]-s[q[hh]]>=0) 
            {
                ans[i-n]=1;
               // printf("2:%d %d %d\n",i,q[hh],i-n);
            }
        }
        while(hh<tt&&s[q[tt-1]]<=s[i]) tt--;
        q[tt++]=i;
    }
    
    for(int i=1;i<=n;i++) 
    {
        if(ans[i]==1) printf("TAK\n");
        else printf("NIE\n");
    }
}

1087. 修剪草坪(活动 - AcWing)

这里首先是要得到最大效益,然后是不能有连续超过k只奶牛被选中。,然后要求最大效益。

我们可以定义dp[i]表示从前i头牛中选的合法方案。

那么第i头牛可以选也可以不选,如果不选的话,那么直接从i-1头牛出转移状态即可

如果选,那么就要注意了,我们要保证不超过连续k头被选

那么这里就需要讨论一下以i为结尾,连续多少头牛被选:

假设连续j头被选:1<=j<=k:

那么我们第i-j+1头牛是被选区间的左边界,i-j头牛一定不能被选,否则就不是连续j头牛了,那么i-j头牛在哪个值中一定不被选呢,很显然是dp[i-j-1],因为这是从前i-j-1头牛中选的合法情况,肯定不包含第i-j头牛,那么转移方程就出来了。另外我们要快速获得连续j头牛的区间和,用前缀和处理一下就好。

状态计算部分代码如下: 

dp[1]=w[1];
    for(int i=2;i<=n;i++)
    {
        dp[i]=dp[i-1];
        for(int j=1;j<=k&&i-j>=0;j++)
        {
            int l=max(i-j-1,0);
            dp[i]=max(dp[i],dp[l]+w[i]-w[i-j]);
        }
    }

显然这个嵌套循环的时间复杂度有点高,很容易超时,那么我们观察一下,往前延伸j个,要求最大值,那么不就是在前面长度为j的窗口中求最大值嘛,用单调队列优化一下即可。我们再来观察下递推的式子:dp[i]=dp[i-j-1]+w[i]-w[i-j],显然与j有关的有两个值,所以我们需要按照这两个值来维护滑动窗口。我们可以定义一个函数来获取dp[i-j-1]-w[i-j]的值,进而维护单调队列。

另外初始的时候需要把0放入队列,因为前k个更新的时候可以延伸到开头,那么计算就要用到0处的值。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll w[100010],dp[100010];
int q[100010],hh,tt;
ll get(int i)
{
    if(!i) return 0;
    return dp[i-1]-w[i];
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&w[i]);
        w[i] += w[i-1];
    }
    hh=0,tt=1;//初始将0放入
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1];
        while(hh<tt&&q[hh]<i-k) hh++;
        dp[i]=max(dp[i],w[i]+get(q[hh]));
        while(hh<tt&&get(q[tt-1])<=get(i)) tt--;
        q[tt++]=i;
    }
    cout<<dp[n];
}

 1089. 烽火传递(活动 - AcWing)

思路:这题是连续m个至少有一个发出信号,然后要求花费最小值。这里如果第i个发出信号,那么前面只要第i-m个发出信号即可,中间的发不发都无所谓,不发最好。如果第i个不发出信号,那么前面[i-m+1,i-1]中必须有一个发出信号。而且我们要确保它发信号,那么就不能笼统的定义从前i个中选,必须要精确到它发不发。可以多加一维表示该点是否发信号,当然也可以定义dp[i]表示第i个点发出信号,那么我们可以来枚举上一个发信号的位置j,显然j应该满足i-m<=j<=i-1,如果j再往前,那么显然就不满足条件了。那么我们的转移方程就出来了。不过很显然,如果要往前枚举的话时间应该会超,而且我们想要的只是前面一段区间中的最小值,所以用单调队列维护即可。

另外为了方便计算,自然要把0放入队列。还有就是答案也不一定就是最后一个位置放,我们需要从最后m个位置中找,有一个位置放即可。

#include<bits/stdc++.h>
using namespace std;
int q[200010],dp[200010],a[200010],hh,tt;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    hh=0,tt=1;
    for(int i=1;i<=n;i++)
    {
        while(hh<tt&&q[hh]<i-m)hh++;
        dp[i]=dp[q[hh]]+a[i];
        //printf("%d %d %d\n",i,q[hh],dp[i]);
        while(hh<tt&&dp[q[tt-1]]>=dp[i]) tt--;
        q[tt++]=i;
    }
    int mi=0x3f3f3f3f;
    for(int i=n-m+1;i<=n;i++)
    {
        mi=min(mi,dp[i]);
    }
    cout<<mi;
}

1090. 绿色通道(1090. 绿色通道 - AcWing题库)

思路:这道题需要求的是在总花费有限制的情况下,至少可以连续多少个不选。

这里问的是至少连续多少个,那么显然答案有很多,而且合法答案是满足单调性的,我们抽象的来看一下,很显然如果空的题多了,那么花费肯定不会超过t,如果空的题少了那么花费肯定又大于t,这里可能会疑惑是否严格满足单调性,但是我们只找最小的方案,那么就是小于这个方案的花费肯定大于t,大于这个方案的花费小于t,那么就可以再往下一点去找。所以我们只需要二分空的长度,然后在check函数里面用动态规划计算最小花费,判断是否小于t即可。

然后现在最关键的就是check函数怎么写,这里显然是要求最多空x长度时,花费最小是多少,那么和烽火台就一样了,我们定义dp[i]表示第i个位置点火,那么去找上一个点火的位置即可。

实际上还是有区别的,上题是m个中至少有一个发送信号,这里实际上可以连续m个空着,那么应该是连续m+1个至少有一个发射信号。

#include<bits/stdc++.h>
using namespace std;
int n,t;
int a[50010],dp[50010],q[50010],hh,tt;
int check(int x)
{
    memset(dp,0,sizeof dp);
    hh=0,tt=1;
    q[hh]=0;
    for(int i=1;i<=n;i++)
    {
        while(hh<tt&&q[hh]<i-x-1) hh++;
        dp[i]=dp[q[hh]]+a[i];
        while(hh<tt&&dp[q[tt-1]]>=dp[i]) tt--;
        q[tt++]=i;
    }
    int mi=0x3f3f3f3f;
    for(int i=n-x;i<=n;i++) mi=min(mi,dp[i]);
   // printf("%d\n",mi);
    if(mi<=t) return 1;
    else return 0;
}
int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    int l=0,r=n;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(check(mid)) r=mid;//空的越少越好
        else l=mid+1;
    }
    cout<<l;
}

1091. 理想的正方形(活动 - AcWing)

 如图,对于一个n*n的矩形,我们将每行的最值累计到最右边,再将这一列的最值累计到右下角,那么预处理后就很容易获得这个区间的最值了。

如图的紫色区域就存了每个n*n矩形的最值。这个思路很简单,我们需要注意的就是下标的处理。

这里还有一个知识点,对于一个二维数组w[][],w[i]实际上可以用作一个一维数组。那么处理行就很简单了。对于每一行统计定长窗口中的最值。

统计列的时候,我们先一列一列的将每一列装进一个空的一维数组,然后再对这个一维数组进行上述区间找最值得操作。因为并不涉及区间与区间之间,所以我们只要对于每一个区间处理明白就行。

#include<bits/stdc++.h>
using namespace std;
int w[1010][1010],rmx[1010][1010],rmi[1010][1010],t1[1010],t2[1010],c[1010],d[1010],hh,tt,q[1010];
int n;
void get_max(int a[],int b[],int s)
{
    hh=tt=0;//可以为空
    for(int i=1;i<=s;i++)
    {
        while(hh<tt&&q[hh]<i-n+1) hh++;
        while(hh<tt&&a[q[tt-1]]<=a[i]) tt--;
        q[tt++]=i;
        b[i]=a[q[hh]];
    }
}
void get_min(int a[],int b[],int s)
{
    hh=tt=0;
    for(int i=1;i<=s;i++)
    {
        while(hh<tt&&q[hh]<i-n+1) hh++;
        while(hh<tt&&a[q[tt-1]]>=a[i]) tt--;
        q[tt++]=i;
        b[i]=a[q[hh]];
    }
}
int main()
{
    int a,b;
    scanf("%d%d%d",&a,&b,&n);
    for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++) 
            scanf("%d",&w[i][j]);
    
    for(int i=1;i<=a;i++) get_max(w[i],rmx[i],b),get_min(w[i],rmi[i],b);

   
    int res=0x3f3f3f3f;
    for(int j=n;j<=b;j++)//列
    {
        for(int i=1;i<=a;i++) t1[i]=rmx[i][j];
        get_max(t1,c,a);
        for(int i=1;i<=a;i++) t2[i]=rmi[i][j];
        get_min(t2,d,a);
        for(int i=n;i<=a;i++)  res = min(res,c[i]-d[i]);
    }
    cout<<res;
}

ps:数组的值传参可以修改数组。

虽然但是,实际上只有两种类型直接用上了dp,一种是要选尽可能多,但不能有超过连续k个被同时选,一种是选尽可能少,不能有连续超过k个不被选,dp时都是以i被选作为状态表示,去找上一个被选位置进而解决的。剩下的侧重单调队列,而非dp。

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

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

相关文章

leetcode hot100跳跃游戏

在本题中&#xff0c;我们要模仿整个跳跃过程&#xff0c;当前位置数组元素为nums[i]&#xff0c;那我们就最大能往后跳nums[i]步&#xff0c;可以小于等于这个数。如果我们直接遍历数组&#xff0c;那么我们需要每一步都控制跳跃0——nums[i]步&#xff0c;这样不可能实现。 …

ITSS证书:点亮职业发展的明灯

&#x1f4da;ITSS是中国电子技术标准化研究院推出的权威认证&#xff0c;涵盖IT服务工程师和IT服务经理的系列培训&#xff0c;旨在培养具备专业知识和技能的IT服务人才。 &#x1f31f;证书优势&#xff1a; 1️⃣提升问题解决能力&#xff1a;通过学习ITSS方法论和实践经验&…

【网络】WireShark过滤 | WireShark实现TCP三次握手和四次挥手

目录 一、开启WireShark的大门 1.1 WireShark简介 1.2 常用的Wireshark过滤方式 二、如何抓包搜索关键字 2.1 协议过滤 2.2 IP过滤 ​编辑 2.3 过滤端口 2.4 过滤MAC地址 2.5 过滤包长度 2.6 HTTP模式过滤 三、ARP协议分析 四、WireShark之ICMP协议 五、TCP三次握…

Python笔记15-实战小游戏飞机大战(中)

文章目录 创建第一个敌机创建一群敌机创建多行敌机让敌机移动射杀敌机生成新的敌机群结束游戏有敌机到达屏幕底端游戏结束 在上一篇基础上继续 本示例源码地址 点击下载 创建第一个敌机 在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien 类控制&#xff0c;我们…

C++面试宝典第25题:阶乘末尾零的个数

题目 给定一个整数n,返回n!(n的阶乘)结果尾数中零的个数。 示例 1: 输入:3 输出:0 解释:3! = 6,尾数中没有零。 示例 2: 输入:5 输出:1 解释:5! = 120,尾数中有1个零。 解析 这道题主要考察应聘者对于数学问题的分析和理解能力,以及在多个解决方案中,寻求最优…

从零学习Linux操作系统 第二十部分 mariadb数据库的管理

一、对于数据库的基本介绍 1.什么是数据库 数据库就是个高级的表格软件 2.常见数据库 Mysql Oracle mongodb db2 sqlite sqlserver … 3.Mysql (SUN -----> Oracle) 4.mariadb (Mysql的一种&#xff09; 数据库中的常用名词 1.字段 &#xff1a;表格中的表头 2.表 &…

MySQL原理(一)架构组成(1)物理文件组成

目录 一、日志文件 1、错误日志 Error Log 1.1、作用&#xff1a; 1.2、开启关闭&#xff1a; 1.3、使用 2、二进制日志 Binary Log & Binary Log Index 2.1、作用&#xff1a; 2.2、开启关闭&#xff1a; 2.3、Binlog还有一些附加选项参数 &#xff08;1&#x…

嵌入式C++必知必会之基础知识-常用关键字(1)

温故而知新 Hello&#xff0c;大家好&#xff01;我是木荣。温故而知新&#xff0c;可以为师矣。作为一名攻城狮&#xff0c;扎实的基本功是解决问题及完成工作中任务的重要前提。没有良好的基本功作为铺垫&#xff0c;一味的追求知识的宽度是毫无意义&#xff0c;知其然更要知…

野火霸道V2学习笔记

STM32F103学习笔记 STM32F103学习笔记说明基础配置配置KeilMDK配置串口下载程序美化Keil界面配置VScode 理论知识STM32命名方式例子 置位与清零GPIOGPIO简介GPIO和引脚的区别引脚的分类 GPIO 框图讲解保护二极管推挽输出推挽输出的含义推挽输出的原理推挽输出的特点推挽输出的应…

力扣题目训练(3)

2024年1月27日力扣题目训练 2024年1月27日力扣题目训练290. 单词规律292. Nim 游戏303. 区域和检索 - 数组不可变91. 解码方法92. 反转链表 II41. 缺失的第一个正数 2024年1月27日力扣题目训练 2024年1月27日第三天编程训练&#xff0c;今天主要是进行一些题训练&#xff0c;包…

大数据Doris(六十一):SQL函数之Bitmap函数

文章目录 SQL函数之Bitmap函数 一、BITMAP_AND(BITMAP lhs, BITMAP rhs)

用C#实现最小二乘法(用OxyPlot绘图)

最小二乘法介绍✨ 最小二乘法&#xff08;Least Squares Method&#xff09;是一种常见的数学优化技术&#xff0c;广泛应用于数据拟合、回归分析和参数估计等领域。其目标是通过最小化残差平方和来找到一组参数&#xff0c;使得模型预测值与观测值之间的差异最小化。 最小二…

小白都会的幻兽帕鲁服务器搭建教程(详细图文)

​  简介&#xff1a;由于幻兽帕鲁游戏的火爆&#xff0c;导致其官方服务器频现游戏卡顿掉线&#xff0c;为了能够正常流畅的体验幻兽帕鲁&#xff0c;有不少人都搭建了幻兽帕鲁服务器(私服)&#xff0c;网上虽然也有很多幻兽帕鲁服务器搭建教程&#xff0c;但内容专业性有点…

qq通讯录怎么关闭?QQ好友删除了怎么恢复?

在QQ中&#xff0c;通讯录是我们管理好友和进行聊天的重要工具&#xff0c;但有时候我们可能需要一些隐私保护&#xff0c;不让一些用户通过手机通讯录添加自己。如果您正在思考qq通讯录怎么关闭以及恢复意外删除的好友&#xff0c;本文将为您详细介绍如何关闭QQ通讯录和恢复被…

Java开发秘籍:实战公众号开发指南

目录 第一章&#xff1a;公众号开发简介 1.1 什么是公众号开发 1.2 公众号开发的意义和应用场景 1.3 公众号开发的技术要求和基础知识 第二章&#xff1a;搭建公众号开发环境 2.1 配置Java开发环境 2.2 下载和安装开发工具 2.3 设置公众号开发所需的配置文件 第三章&a…

2024阿里云和腾讯云的第一战打响:搭建《幻兽帕鲁》私服游戏

为了搭建《幻兽帕鲁》游戏私服&#xff0c; 2024年阿里云 VS 腾讯云的第一场战争开始了…… 事情是这样的&#xff1a; 1月19日&#xff0c;最离谱新游 《幻兽帕鲁》突然爆火了&#xff0c;这是一款日本开发商展耗费4年开发的冒险类游戏&#xff0c;这款戏一推出就迅速俘获了…

优思学院|精益管理如何判定哪些活动是增值或非增值?

“时间就是金钱”——这句老话我们都耳熟能详。但在工作中&#xff0c;我们真正从事的、对组织增加价值的活动有多少呢&#xff1f;我们常常认为自己的每一项任务都是维持运营的关键。然而&#xff0c;当我们从精益管理的视角进行分析&#xff0c;可能会惊讶地发现&#xff0c;…

蓝牙----蓝牙连接建立_连接建立

蓝牙----蓝牙连接建立_连接建立 蓝牙连接建立过程图1.主机扫描到广播包1.1判断是否是自己关心的广播包1.2广播地址添加到扫描列表 2.主机扫描结束&#xff0c;建立连接3.主从连接成功后&#xff0c;执行连接建立后事件3.1.主机将连接句柄和设备地址添加到连接列表3.2.主机进行G…

spring boot学习第八篇:操作elastic search的索引和索引中的数据

前提参考&#xff1a;elastic search入门-CSDN博客 前提说明&#xff1a;已经安装好了elastic search 7.x版本&#xff0c;我的es版本是7.11.1 1、 pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns&q…

C++ QT入门1——记事本基础功能实现(基本控件布局+信号与槽+文件类操作)

C QT入门1——记事本基础功能实现&#xff08;基本控件布局信号与槽文件类操作&#xff09; 一、UI界面的基础设置样式表通用配置通过样式表设置按钮三态以图片作为按钮背景 二、按键响应——☆信号与槽信号与槽基本概念按键QPushButton设置信号与槽自定义信号与槽自定义信号与…
最新文章