单调队列例题
有一个长为 的序列 ,以及一个大小为 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
对于 的数据,
直接思路
如果 ,我们可以用线段树、ST表的方式解决。然而这题的 达到了 ,开数组 就可能超过空间限制。
优化
我们将通过题目的例子 , 来展示优化思路,这里我们求每次滑动后窗口中的最大值。
我们顺次加入数组中的数,
在第一窗口[1 3 -1]中,1在3之前,且1比3小,那么1在接下来的窗口中肯定不会成为最大值。因此,我们会直接删掉1,保留3和-1。保留-1的原因是-1在3的后面,有可能成为某个窗口的最大值。
3 -1 -3 5
顺次加入-3和5后,5比3,-1,-3都大, 3,-1,-3三个值都不可能成为最大值了,因此只保留5。(当然3也因为窗口大小
被移除。)
重新整理思路
最大值
最大值:保留下来的数是递减的,这一过程可以用双端队列来维护。
1、维护队首(就是如果你已经是当前的m个之前那你就可以被删了,head++)
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态)1
最小值
最小值用和最大值类似的思路解决。
代码
/* ***********************************************
Author : VFVrPQ
Created Time : 日 8/11 06:52:43 2019
File Name : luogu1886滑动窗口.cpp
Problem :
Description :
Solution :
Tag :
************************************************ */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <iomanip>
using namespace std;
#define DEBUG(x) cout<<x<<endl;
const int N = 1e6+10;
const int M = 1e9+7;
const int INF = 1e9+7;
int a[N];
int n,k;
int minn[N],maxx[N], id[N];
int main()
{
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++)scanf("%d",&a[i]);
int head=0, tail=0;
for (int i=1;i<=n;i++){
if (head<tail && id[head]<i-k+1) head++; //队首移动
while (head<tail && minn[tail-1]>=a[i]){//升序的队列
tail--;
}
minn[tail] = a[i];
id[tail]=i;
tail++;
if (i>=k) printf("%d ",minn[head]);
}
printf("\n");
head=tail=0;
for (int i=1;i<=n;i++){
if (head<tail && id[head]<i-k+1) head++; //队首移动
while (head<tail && maxx[tail-1]<=a[i]){//降序的队列
tail--;
}
maxx[tail] = a[i];
id[tail]=i;
tail++;
if (i>=k) printf("%d ",maxx[head]);
}
printf("\n");
return 0;
}