[Luogu P1886]滑动窗口--单调队列入门
题目描述
现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如:
The array is [1 3 -1 -3 5 3 6 7], and k = 3.
输入输出格式
输入格式:
输入一共有两行,第一行为n,k。
第二行为n个数(<INT_MAX).
输出格式:
输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值
这题是单调队列入门题。题意清晰明了,求区间最大(小)。第一反应是线段树或者RMQ,但是数据范围爆炸。这道题和普通的区间的区别就在于它的区间长度是固定的。所以使用时间复杂度为O(n)的单调队列来解决。
什么是单调队列呢?单调队列是一种特殊的双端队列,其内部具有单调性(有点像优先序列,但有着本质区别)。
如何实现插入?从队尾插入,若破坏了单调性,则删除队尾元素(这个删除决定了什么题能用什么题不能用),直到找到一个不影响的位置。
如何实现输出?访问队首(真是方便)
如何解决这道题?首先设一个区间为(l,r),则有max(l+1,r+1)=max(a[r+1],max(a[l+1],a[l+2]...a[r])),那么max(l+1,r+1)与max(l,r)其实是有很大一部分重叠的,那么在问题实现的时候就只需要先删除单调队列中不在区间里的数(a[l]),再插入新数(a[r+1]),剩余的不变,就可以解决了。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 inline int read(){ 6 int res=0,f=1; 7 char ch=getchar(); 8 while(ch<'0'||ch>'9'){ 9 if(ch=='-')f=-1; 10 ch=getchar(); 11 } 12 while(ch>='0'&&ch<='9'){ 13 res=res*10+(ch-'0'); 14 ch=getchar(); 15 } 16 return res*f; 17 } 18 const int N=1e6+5; 19 int nsf[N],nbf[N],que[N],number[N],a[N]; 20 int n,k,head,tail; 21 inline void INT(){ 22 n=read();k=read(); 23 for(int i=1;i<=n;++i)a[i]=read(); 24 } 25 inline void findmin(){ 26 head=1;tail=0;//队头、尾初始化 27 for(int i=1;i<=n;++i){//插入a[i]到单调序列 28 while(number[head]<i-k+1&&head<=tail)++head; 29 //从队首开始找,“过时”的删除 30 while(a[i]<=que[tail]&&head<=tail)--tail; 31 //插入时从队尾插入,维护单调上升性质 32 number[++tail]=i;que[tail]=a[i]; 33 //number保存插入时的“时间戳”,que表示值 34 nsf[i]=que[head];//当前队列中最小值 35 } 36 } 37 inline void findmax(){ 38 head=1;tail=0; 39 for(int i=1;i<=n;++i){ 40 while(number[head]<i-k+1&&head<=tail)++head; 41 while(a[i]>=que[tail]&&head<=tail)--tail; 42 number[++tail]=i;que[tail]=a[i]; 43 nbf[i]=que[head]; 44 } 45 } 46 int main(){ 47 INT();//输入 48 findmin();//动态规划求单调队列最小值 49 findmax(); 50 for(int i=k;i<=n;++i)printf("%d ",nsf[i]); 51 cout<<endl; 52 for(int i=k;i<=n;++i)printf("%d ",nbf[i]); 53 return 0; 54 }
第一次写随笔还有点小兴奋呢~