[单调队列]
单调队列
特点
是一种主要用于解决 滑动窗口 类问题的数据结构
在长度为 \(n\) 的序列中,求每个长度为 \(k\) 的区间的区间最值,队头为区间最值
时间复杂度是 \(O(n)\) 在这个问题中比\(O(nlogn)\)的ST表和线段树要优
思想
单调队列故队列里面的数是单调递增或单调递减的,如果维护的是区间最小,则应为单调递增,那么考虑将一个数放入队列的时候,如果它比前面的大,那么就直接\(++tail\)然后\(q[tail]=a[i]\),如果它小于等于前面的数,那么就把前面的数移除,因为在这个区间要维护最小值,比它大的就没有意义了,故应该\(tail--\)直到它比前面的大或者队列为空
模板题滑动窗口
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 1e6+7;
int n,k,ans[maxn],a[maxn];
int head,tail;
int Head,Tail;
struct qqq{
int value,pos;
}qmin[maxn],qmax[maxn];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
while(a[i] >= qmax[Tail].value && Tail >= Head) Tail --;//如上所述
while(a[i] <= qmin[tail].value && tail >= head) tail --;
qmax[++Tail].value = a[i];qmax[Tail].pos = i;
qmin[++tail].value = a[i];qmin[tail].pos = i;
while(i - qmax[Head].pos >= k) Head ++;//因为是求长度为k区间的最值,故队列长度不应该超过k
while(i - qmin[head].pos >= k) head ++;
if(i >= k)
{
printf("%d ",qmin[head].value);
ans[i] = qmax[Head].value;//要先输出小的,再输出大的,故存一下答案
}
}
printf("\n");
for(int i=k;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}
洛谷P1725单调队列+dp
就是第\(i\)个格子可以由之前的\([i-R,i-L]\)来转移得到,故状态转移方程用一个单调队列来维护最大值即可
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 2e5+6,inf = 2147483647;
int n,pos[maxn],L,R,dp[maxn];
int maxi = -inf,a[maxn];
int Head,Tail;
int main()
{
memset(dp,128,sizeof(dp));//128自然溢出为最小值,因为有负数所以要初始化为负无穷
scanf("%d%d%d",&n,&L,&R);
for(int i=0;i<=n;i++)
scanf("%d",&a[i]);
dp[0] = 0;//题目要求0处值为0
for(int i=L;i<=n;i++)//因为从0开始跳,最先能到达的位置也是L,故从L开始循环
{
while(dp[i-L] >= dp[pos[Tail]] && Tail >= Head) Tail --;
pos[++Tail] = i-L;
while(i - pos[Head] > R) Head ++;
dp[i] = dp[pos[Head]] + a[i];//区间最大值即为pos[Head]所在位置的值
if(i >= n-R+1)//从这个位置才能跳过终点
maxi = max(maxi,dp[i]);
}
printf("%d",maxi);
return 0;
}