【暖*墟】 #队列# 单调队列:子段最大值 + 最大子序和

本文详细介绍了STL中的队列(queue)及优先队列(priority_queue)的基本操作和使用方法,并深入探讨了单调队列的概念、原理及其在算法问题中的应用。通过三个实例展示了如何利用单调队列解决子段最大值、最小总和等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STL 中队列的使用(queue)

一.基本操作:

push(x) 将x压入队列的末端  //不能push_back

pop() 弹出队列的第一个元素(队顶元素),注意此函数并不返回任何值

front() 返回第一个元素(队顶元素)

back() 返回最后被压入的元素(队尾元素)

empty() 当队列为空时,返回true

size() 返回队列的长度

头文件:#include <queue>

声明方法: 1、普通声明 queue<int>q;

2、结构体 struct node{ int x, y; };  queue<node>q;

二.STL 中优先队列的使用方法(priority_queue)

优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素

但是它有一个特性,就是队列中最大的元素总是位于队首,

所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。

这点类似于给队列里的元素进行了由大互小的顺序排序。

元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。  

基本操作:empty() 如果队列为空返回真

pop() 删除对顶元素  push() 加入一个元素

top() 返回优先队列对顶元素

在默认的优先队列中,优先级高的先出队。

在默认的int型中先出队的为较大的数。

头文件:#include <queue>

1.普通方法:priority_queue<int>q;  //通过操作,按照元素从大到小的顺序出队

priority_queue<int,vector<int>,greater<int> >q; 

//通过操作,按照元素从小到大的顺序出队

2、自定义优先级: priority_queue<int, vector<int>, cmp>q; //定义方法

//其中,第二个参数为容器类型。第三个参数为比较函数。

 

单调队列

即所有在队列里的数都必须按递增(或递减)的顺序列队的队列。

用法:在决策队列中,及时排除一定不是最优解的选择。

【例题1】(来源:caioj 1172)[ 子段最大值---单调递减队列 ]

给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。

解法1*** 如果按照常规方法,我们在求f[i]即i~i+m-1区间内的最值时,

要把区间内的所有数都访问一遍,时间复杂度约为O(nm)。

解法2*** 上个算法找当前的f(i)的时候,i的前面k-1个数其它在算f(i-1)的时候我们就比较过了。

保存上一次的结果?当然主要是保存i的前k-1个数中的最大值了。要用到单调递减队列。

使用单调队列就涉及到去头和删尾***

1、队列的头一定是在一段时间前就加入了队列,现在的队列头会不会离开了我们处理的区间呢?

如果它离我们正在处理的i太远了,我们就要把它去掉,去除冗杂的信息。

2、为了保证队列的递减性,在从列队尾新插入元素v时,要考虑队列尾的值是否大于v,

如果是,队列呈现 队列尾-1的值 > 队列尾的值 > v ,此时队列递减性没有消失;

如果不是,队列呈现 队列尾-1的值 > 队列尾的值 < v ,队列递减性被打破。

为了维护递减性,我们做如下考虑:v是最新值,它的位置是目前最靠后的,

它可成为以后的最大值,必须留下;队列尾-1的值与v大小不定,不能冒然删去它;

队列尾的值夹在v和队列尾-1之间,它不但不是最大值,对于以后的情况又不如v优

因为v相比队列尾更靠后(v可以影响到后m个值,队列尾只能影响到从它的位置往后数m-1个值),

而且值更大,所以删队列尾是必定的。

の 在维护好一个 区间正确、严格递减 的单调递减队列后,队列头就是当前区间的最大值了。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;

/*【caioj 1172】单调队列找max
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。 */

//队列中存之后有可能成为最大值的数,每次入队时,清除不可能的队头和队尾。

struct node{
    int x,p; //价值 和 位置
}list[201000];

int a[201000];

int main(){
    int n,m; scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    int head=1,tail=1; //手写优先队列
    list[1].x=a[1]; list[1].p=1; //初始点为1
    for(int i=2;i<=n;i++){
        while(head<=tail && list[tail].x<=a[i]) tail--; 
        //↑↑新数比前几个大,前几个不可能再成为最大值(可能不止一个)
        tail++; list[tail].x=a[i]; list[tail].p=i; //a[i]加入队尾
        while(list[head].p<i-m+1) head++; //p的作用:判断区间长度
        if(i>=m) printf("%d\n",list[head].x); //每一次的队头都是当前段最大值
        //单调递减队列:如果后方有数更大,前面就删除;所以队列一定单调递减
    }
    return 0;
}

【例题2】caioj 1173

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;

/*【caioj 1173】单调队列 [减少继承的状态]
有N个数(N<=100000),在连续M(M<=N)个数里至少要有一个数被选择。 
求选出来数的最小总和。  */

//队列中存之后有可能成为最大值的数,每次入队时,清除不可能的队头和队尾。

struct node{
    int x,p; //价值 和 位置
}list[201000];

int a[201000],f[201000]; //f[i]选择i,且保证1~i符合题意的最小价值

int main(){
    int n,m; scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    int head=1,tail=1;
    list[1].x=0; list[1].p=0; //继承0位置
    for(int i=1;i<=n;i++){
        while(head<=tail && i-list[head].p>m) head++; //淘汰超出范围的队头
        f[i]=list[head].x+a[i]; //选择遇到的第一个不能删的队头,可以管理的后面的数最多
        while(head<=tail && list[tail].x>f[i]) tail--; //list中是升序序列
        tail++; list[tail].x=f[i]; list[tail].p=i;
    }
    int ans=(1<<30);
    for(int i=n-m+1;i<=n;i++) //最后一段,任选f[i]都能保证满足
        if(f[i]<ans) ans=f[i];
    printf("%d",ans);
    return 0;
}

【例题3】最大子序和  tyvj - 1305

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;

/*【最大子序和】单调队列  
输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。 */

//先求出前缀和s[i],枚举右端点i,寻找最小的s[j]对应的左端点
//最优策略集合一定是“下标位置递增,对应前缀和s也递增”的序列
//1.判断队头决策和i的距离是否超过m的范围;
//2.此时队头是右端点为i,左端点为j的最优选择;
//3.不断删除队尾决策,直到队尾对应的s值小于s[i],使i入队。
//由此,队列变成了 关于前缀和的 单调递增序列↑↑↑↑↑

/*【分析】维护一个{ 下标递增且前缀和递增 }的队列。
每一次入队一个i,就删除在i前面所有不满足“单调性”的元素,
即当i入队后,这些元素一定不如i,在i的存在下这些元素一定不会被选入最优答案集合中,
也就是类似sj<=si且j<i的元素。删除的时候直接让队尾出队即可。 */

const int MAXN = 300000 + 10;
int q[MAXN],a[MAXN],s[MAXN];

int main() {
    int n,m,ans; scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%d",a[i]);
        s[i]=a[i]+s[i-1]; //前缀和
    }
    int l=1,r=1;
    for(int i=1;i<=n;i++) { //先枚举来固定右端点,问题转化为枚举左端点
        while(l<=r&&q[l]<i-m) l++; //判断队头决策和i的距离是否超过m的范围
        ans=max(ans,s[i]-s[q[l]]); //此时队头q[l]是右端点为i的最优左端点选择
        while(l<=r&&s[q[r]]>=s[i]) r--; //核心,及时弹出[对答案贡献一定不如i]的元素
        q[++r]=i;
    }
    printf("%d", ans);
    return 0;
}

整理归纳单调队列的定义:

1、维护区间最值;
2、去除冗杂状态;
3、保持队列单调(最大值是单调递减序列,最小值是单调递增序列);
4、最优选择在队首。

整理归纳单调队列的使用方法:

1、维护队首(对于上题就是如果你已经是当前的m个之前那你就可以被删了) ;
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态) ;
3、取出需要的最优解(队列头的值即是);
4、借助最优解,得到目前所求的最优解(通常此处插入DP方程)。

单调队列的原理:

在处理f[i]时,去除冗杂、多余的状态,使得每个状态在队列中只会出现一次;

同时维护一个能瞬间得出最优解的队列,减少重新访问的时间;

在取得自己所需的值后,为后续的求解做好准备。

### 滑动窗口算法实现连续最大 滑动窗口算法是一种高效的优化技术,通常用于解决涉及数组或字符串中的区间的计算问题。对于求解连续最大的问题,可以通过动态规划的思想结合滑动窗口来降低时间复杂度。 #### 动态规划的核心思路 在给定的数组 `a` 中,我们需要找到一个连续数组使其最大化。设 `presum[i]` 表示从前到第 `i` 个位置的最大,则状态转移方程可以写成: \[ presum[i] = \max(a[i], presum[i-1] + a[i]) \] 这意味着当前的最大要么是从当前位置重新开始的新,要么是延续之前的并加上当前元素[^2]。 #### 时间复杂度分析 如果直接枚举所有可能的区间 `[l, r]` 并计算其,那么时间复杂度将达到 \( O(n^2) \),这对于较大的输入规模(如 \( n \leq 2 \times 10^5 \))显然是不可接受的。然而,利用上述动态规划的状态转移关系,我们可以在线性时间内完成这一任务,即 \( O(n) \)[^2]。 以下是基于此逻辑的一个 C++ 实现代码: ```cpp #include <iostream> using namespace std; const int N = 2e5 + 5; int n; int a[N]; long long presum[N]; int main(){ cin >> n; for(int i=1;i<=n;i++) { cin >> a[i]; presum[i] = max((long long)a[i], presum[i-1] + a[i]); } long long result = LLONG_MIN; for(int i=1;i<=n;i++) { result = max(result, presum[i]); } cout << result << endl; return 0; } ``` 在这代码中,变量 `result` 被用来记录整个过程中遇到过的最大值。最终输出的结果即是所求的最大[^2]。 #### 单调队列的应用场景扩展 虽然本题并不需要用到单调队列,但在某些更复杂的变种题目中,比如要求限定长度范围内的最小或者最大等问题时,引入单调队列可以帮助进一步提升效率。此时,除了维护普通的滑动窗口外,还需要额外的数据结构支持快速查询窗口内部极值操作[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值