算法:单调队列

队列:先进先出

题型:滑动窗口,求滑动窗口里的最大(或最小)值

  例1:P1886 滑动窗口 /【模板】单调队列 求滑动窗口里的最大小值

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=2e6+5;
long long a[N];
int q[N];
int main()
{
int n,k,i,hh,tt;
scanf("%d%d",&n,&k);
for(i=0;i<n;i++){
scanf("%lld",&a[i]);
}
hh=0,tt=-1;
for(i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
q[++tt]=i;
if(i-k+1>=0) printf("%lld ",a[q[hh]]);
}
printf("\n");
hh=0,tt=-1;
for(i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
q[++tt]=i;
if(i-k+1>=0) printf("%lld ",a[q[hh]]);
}
return 0;
}

找滑动窗口里最小的值:

hh是队头,tt是队尾,hh一开始是0,tt一开始是-1.然后q[++tt]代表在队尾放入元素(在这里是放入数组元素下标),然后队列元素为q[0],q[1],q[2],以此类推。

滑动窗口每次滑动一格(用i来表示滑动窗口末端位置,i每次+1,正对应着滑动窗口每次移动一格),滑动窗口里的数组成了一个队列,重点判断什么时候队头弹出,什么时候队尾弹出。

 当队列不为空时,且滑动窗口的起点下标比队头元素下标大时,则说明该队头元素不应在滑动窗口里了,那么队头元素弹出

 当队列不为空时,且队尾元素大于等于下一个准备放入队列的元素时,那么该队尾元素绝对不可能成为答案,就没必要仍然在队列里面待着了,直接弹出,再让下一个元素进来。这样坐下来,则队列里的元素时单调递增的,那么队头元素就是该滑动窗口里面的最小值。比如1 2 5 3 4 2 2,首先1进队列,2进队列,3进队列,输出最小值1,然后1弹出,此时队列里有 2 5,然后3准备进队列,发现5比3大,则5弹出,3进来,会发现5不可能成为答案(不管是在2 5 3这个滑动窗口里还是5 3 4这个滑动窗口里,只是因为3比5小),此时队列为2 3,对应着滑动窗口2 5 3 ,只不过5不可能成为答案,于是5早已被弹出,而队列成单调递增,则该滑动窗口中的最小值即为队头元素,同理,滑动窗口再移动一格,队列为3 4,对应着滑动窗口的5 3 4,而5不可能成为答案早已被弹出。当滑动窗口为4 2 2时,有两个2,队列只需要1个2就可以将答案输出,于是队尾元素与要放入元素相等时也弹出队尾元素,则队列为2,输出即为队头元素2.

 当滑动窗口起点大于等于0时,开始输出第一个答案,滑动一格输出一个答案

求最大值同理,只需将a[q[tt]]>=a[i]改为a[q[tt]]<=a[i]

例2:P1440 求m区间内的最小值 求m区间最小值(滑动窗口)

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=3e7+5;
int a[N],q[N];
int main()
{
int n,m,i;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
}
printf("%d\n",0);
int hh=1,tt=0;
for(i=1;i<=n-1;i++){
if(hh<=tt&&i-m+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
q[++tt]=i;
printf("%d\n",a[q[hh]]);
}
return 0;
}

该题也是滑动窗口问题,只不过该滑动窗口的末端位置是从第一个数前面一个开始,往后一格一格移动,直到移到最后一个数前面一个位置。当滑动窗口末端在第一个数前面一个时,直接输出0,单独分开考虑,然后从滑动窗口末端为第一个数时开始滑动,这样末端下标即为1到n-1,若i为滑动窗口末端下标,则i-m+1为滑动窗口顶端下标,这题不需要管滑动窗口框住了几个数,因为题目中说若前面的数不足m,则从第一个数开始,意味着不需要在滑动窗口框住m个数才输出。 

以上重点是找到滑动窗口末端,下标一般用i表示,再表示出滑动窗口顶端下标

另外,队列q数组下标与a数组下标一点关系都没有,因此可以将hh改为0,tt改为-1.

例3:P1714 切蛋糕  前缀和+单调队列 传送门

#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+5;
long long int sum[N],q[N];
long long max1;
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){cin>>sum[i];sum[i]+=sum[i-1];}
int hh=1,tt=1;
q[1]=0;
for(int i=1;i<=n;i++)
{
while(hh<=tt&&i-m>q[hh])hh++;
max1=max(max1,sum[i]-sum[q[hh]]);
while(hh<=tt&&sum[q[tt]]>=sum[i])tt--;
q[++tt]=i;
}
cout<<max1;
return 0;
}

求连续k个数(k不定,k<=m),求和的最大值

首先区间和想到前缀和,于是利用sum[i]=sum[i-1]+a[i],这里因为原数列用不到了,所以输入一个sum[i],直接sum[i]=sum[i-1]+sum[i]

然后求某区间的值,则用区间右端点的前缀和减去左端点前一个的前缀和,要找和的最大值,用i表示滑动窗口末端右边一个位置,我们可以遍历每个sum[i],从sum[1]开始,然后滑动窗口的长度为m,一开始滑动窗口的末端在1的前一格,求出滑动窗口里的最小值,用sum[i]减去该最小值即到i为止所能获得的最大值,i从1到n遍历,那么所有的最大值都求出来了,最后要的是所有最大值的最大值。

而滑动窗口里的最值就要用到单调队列,单调队列就是找一组无序的数在一定区间里内的最大值或最小值

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值