算法竞赛中的单调队列

本文介绍了单调队列这一数据结构,并通过洛谷P1886和AcWing 135题目的解题过程,展示了单调队列在解决滑动窗口最大值/最小值和最长连续子序列最大和问题中的高效应用。通过单调队列,可以将原本时间复杂度为O(n*k)的问题优化至O(n)。
摘要由CSDN通过智能技术生成

什么是单调队列?

单调队列嘛,概念与单调栈相似,就是队列中存放的数据是单调递增或单调递减的,概念很好理解,我们直接从例题对其深入浅出地了解。
1、洛谷P1886 滑动窗口 /【模板】单调队列
题目链接https://www.luogu.com.cn/problem/P1886
题目大意:求长度为n的序列中每一段长度为k的子序列的最大值和最小值。
题目思路:这种滑动窗口问题是典型的单调队列问题。首先,如果我们暴力解决这个问题,也就是对每个窗口的每一个元素进行扫描的话,时间复杂度为O(n*k),在数据量大的测试点中会TLE,因此我们需要一种数据结构来维护数据。如果我们使用单调队列,会很容易把每次窗口内得到最大值/最小值的时间复杂度降至O(1)。以求最小值为例,具体做法就是创建一个队列,队列中存储的数据是所给序列中元素的下标,这个队列从队头(head)到队尾(tail)是单调递增的(这里的递增指的是下标代表的元素),对于每次遍历,若新元素大于队尾元素,则直接入队,若小于队尾元素,则先把队尾元素踢走再入队,只要队头存储的元素合法,我们就输出以此时队头为下标的元素。求最大值同理。
AC代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+1;
int q1[maxn],q2[maxn],a[maxn];
int n,m;
void solve()
{ 
	int h=1,t=0;
	for(int i=1;i<=n;i++)
	{
		while(h<=t && q1[h]+m<=i)//h<=t表示队头始终不能在队尾后面,q1[h]+m>i代表窗口位置合法
			h++;
		while(h<=t && a[i]<a[q1[t]])
			t--;
		q1[++t]=i;
		if(i>=m)
			cout<<a[q1[h]]<<" "; 
	}
	cout<<endl;
	
	int h1=1,t1=0;
	for(int j=1;j<=n;j++)
	{
		while(h1<=t1 && q2[h1]+m<=j)
			h1++;
		while(h1<=t1 && a[j]>a[q2[t1]])
			t1--;
		q2[++t1]=j;
		if(j>=m)
			cout<<a[q2[h1]]<<" ";
	 } 
	 cout<<endl;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    	scanf("%d",&a[i]);
    solve();
    return 0;
}

2、AcWing 135. 最大子序和
题目链接https://www.acwing.com/problem/content/description/137/
题目大意:输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
题目思路:由于题目让求子序列的和,所以我们先把每一项用前缀和表示出来,然后子序列的和就可以用s[i]-s[j]这种形式来表示,对于每个被减数s[i]我们让减数s[j]尽可能的小,这时我们很容易想到用单调队列维护s[j],我们让这个单调队列存储前缀和数组s的下标,整个队列从队头开始单调递增,接下来的做法与滑动窗口相似。
AC代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+1,INF=0x3f3f3f3f;
int n,m;
int s[maxn],q[maxn],ans;
void solve()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		s[i]+=s[i-1];
	}
	ans=-INF;
	int h=0,t=0;
	for(int i=1;i<=n;i++)
	{
		while(q[h]<i-m)//q[h]<i-m说明此时队列太靠左了,不合法
			h++;
		ans=max(ans,s[i]-s[q[h]]);
		while(h<=t && s[q[t]]>s[i])
			t--;
		q[++t]=i;
	}
	cout<<ans<<endl;
}
int main()
{
	solve();
	return 0;
}

至于单调队列优化dp,在这里先咕一咕,等我开dp专栏时再写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值