单调队列
今天问了长者有关单调队列的知识,单调队列这种东西其实用途并不是特别的广泛,只是在处理区间上询问的时候比较管用,而且这种询问有限制,比如:
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
这种类似的题目,每次询问前m个数中最小的,显然暴力的话是O(n * m)的每个点枚举一遍m,就是这样,但是暴力显然会TLE,那有什么办法呢?
RMQ是可以的,但是有一个问题,就是MLE,2000000 * 20,会炸空间,线段树显然是可以的,但是还不是最优的。所以我们引出单调队列这种东西,解决序列上滑块的问题(m这个区间在序列上动来动去,就像是个滑块^_^).
单调队列?
我们制作一个严格上升或下降的队列,它的队首是你处理当前区间的最大(最小)值,然后进行删除和加入的操作。
加入: 我们假设有8,1,2,3,7,9,6.这样的一个序列,我们想要一个单调上升的序列,这样就可以保证队首是最小的。我们加入元素的时候我们要存下每个值在原序列中的下标,因为,我们只有处理[i-m,i]这样的一个区间,如果队首元素的下标不在这个区间,显然就毫无意义了不。好,我们开始加入元素,我们每次添加元素,从队尾开始比较,直到找到比它小的数替换掉。
假设没有删除操作
第一次 :8
第二次:1(1 < 8 所以 我们替换掉8)
第三次:1,2.
第四次:1,2,3.
第五次:1,2,3,7.
第六次:1,2,3,7,9.
第七次:1,2,3,6.(跟7,9,比较都小,所以替换,尾指针直到6的位置)。
删除
删除其实很简单,我们比较队首的下标如果不在范围内直接删掉。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_ ; ++i)
inline int read()
{
char c = getchar();register int fg = 1,sum = 0;
while(c < '0' || c > '9')
{
if(c == '-')fg = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
sum =sum * 10 + c - '0';
c = getchar();
}
return fg * sum;
}
const int maxn = 2000000+10;
struct T
{
int val,pos;
};
struct que
{
int l,r;
T a[maxn];
que(){l = 1, r = 0;}
inline void putin(T x)//加入
{
while(r >= l && a[r].val > x.val)r--;
a[++r] = x;
}
inline void pop(int pos)//删除
{
while(r >= l && a[l].pos < pos)l++;
}
void print()
{
for(int i=l;i<=r;i++)
printf("%d ",a[i].val);
printf("\n");
}
inline int top()
{return a[l].val;}
}q;
int n,m,f[maxn];
int main()
{
n = read(),m = read();
REP(i,1,n)f[i] = read();
puts("0");
q.putin((T){f[1],1});
REP(i,2,n)
{ q.print();
if(i-m>=0)q.pop(i-m);
printf("%d\n",q.top());
q.putin((T){f[i],i});
}
return 0;
}