什么是单调队列?
单调队列嘛,概念与单调栈相似,就是队列中存放的数据是单调递增或单调递减的,概念很好理解,我们直接从例题对其深入浅出地了解。
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专栏时再写。