洛谷题解P1886 滑动窗口 P2032 扫描/【模板】单调队列 暨 浅谈单调队列
\({\tt P1886}\) 原题传送门
\({\tt P2032}\) 原题传送门
\({\tt Solution}\)
这两道都是单调队列非常经典且模板的题目。
借此来浅析单调队列(Monotone queue)
概念
单调队列是一种特殊的队列,它在满足队列所有性质的同时,也满足以下特点 :
- 单调递增 \(||\) 单调递减 \(||\) 自定义的单调性
- 队首和队尾都可以出队,但只有队尾可以入队。
- 单调队列的队头一定是当前队列中的最值 (\(max\ or\ min\))
分析
单调队列的操作主要用到的是两个数组,作用如下 :
- 一个 \(p[Maxn]\) 数组,用来记录当前队中的元素
- 一个 \(pos[Maxn]\) 数组,用来记录当前队中的元素在初始序列中的下标
\(\text{Example}:\)
以此题样例为例 :
Input:
8 3
1 3 -1 -3 5 3 6 7
Output:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
模拟一下操作过程 : (以找最小值,单调递增的单调队列为例)
- \(a[1](1)\) 入队,\(p=\{1\}\ ,\ pos=\{1\}\)
- \(a[2](3)\) 入队,保持单调性,\(p=\{1,3\}\ ,\ pos=\{1,2\}\)
- \(a[3](-1)\) 入队,破坏了单调性,且 \(-1<1<3\),故 \(1\) 从队头出队,\(3\) 从队尾出队,\(p=\{-1\}\ ,\ pos=\{3\}\)
- \(a[4](-3)\) 入队,破坏了单调性,且 \(-3<-1\),故 \(-1\)从队尾出队,\(p=\{-3\}\ ,\ pos=\{4\}\)
- \(a[5](5)\) 入队,保持单调性,\(p=\{-1,5\}\ ,\ pos=\{4,5\}\)
- \(a[6](3)\) 入队,破坏了单调性,且 \(3<5\),但\(-3<3\),故 \(5\) 从队尾出队,\(p=\{-3,3\}\ ,\ pos=\{4,6\}\)
- \(a[7](6)\) 入队,保持单调性,\(p=\{-3,3,6\}\ ,\ pos=\{4,6,7\}\),但样例中 \(k=3\),最多只能选长度 \(3\) 的窗口,故 \(-3\) 从队头出队,故应为\(p=\{3,6\}\ ,\ pos=\{6,7\}\)
- \(a[8](7)\) 入队,保持单调性,\(p=\{3,6,7\}\ ,\ pos=\{6,7,8\}\)
在上述过程中,除 \(a[7]\) 入队时出现了窗口长度的问题特别说明外,其余各元素进队时也应比较窗口长度,保证合法。
即题目描述中的这个过程 :
(从POJ搬来的实锤了)
所以说可以得到以下代码 :
inline void min(){
head=1;tail=0; //队头与队尾的指针
for(int i=1;i<=n;i++){ //i表示当前滑动窗口的右端点的下标
while(head<=tail && q[tail]>=a[i]) tail--; //求最小值,单调递增。
//故当队尾元素比当前入队元素大时,不满足"单调递增",队尾出队
q[++tail]=a[i]; //入队
pos[tail]=i; //记录位置
while(pos[head]<=i-k) head++; //i从1开始,i-k表示当前窗口的左端点的下标
//如果当前的窗口的长度无法到达队头,队头出队
if(i>=k) printf("%d ",q[head]); //至少能从1位置放下一个滑动窗口,即合法时输出队头(最小值)
}
printf("\n");
}
这里可能有一个问题,为什么要
head=1;tail=0;
原因如下 :
\(head\) 要严格对应首元素, \(tail\) 要严格对应尾元素,因为是 \(head<=tail\) ,故当队列中加入一个元素时,\(head=1,tail=0\to head=1,tail=1\) 满足 \(head<=tail\) ,就可以表示有元素。
但是,这种赋值方法不一定,视具体题目而定
最大值同理(实际上只改了一个符号而已······)
\({\tt Code\ -\ P1886}\)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline void read(int &x){
int f=1;
char ch=getchar();
x=0;
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
x*=f;
}
struct Monotone_queue{
int n,k;
int a[1000010];
int pos[1000010],q[1000010];
int head,tail;
inline void read_do(){
read(n);read(k);
for(int i=1;i<=n;i++) read(a[i]);
}
inline void min(){
head=1;tail=0;
for(int i=1;i<=n;i++){
while(head<=tail && q[tail]>=a[i]) tail--;
q[++tail]=a[i];
pos[tail]=i;
while(pos[head]<=i-k) head++;
if(i>=k) printf("%d ",q[head]);
}
printf("\n");
}
inline void max(){
head=1;tail=0;
for(int i=1;i<=n;i++){
while(head<=tail && q[tail]<=a[i]) tail--;
q[++tail]=a[i];
pos[tail]=i;
while(pos[head]<=i-k) head++;
if(i>=k) printf("%d ",q[head]);
}
}
}m;
int main(){
m.read_do();
m.min();
m.max();
return 0;
}
\({\tt P2032}\) 的代码基本相似,我们这里维护的是一个单调递减的单调队列。
\({\tt Code\ -\ P2032}\)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int Maxn=2e6+10;
inline void read(int &x){
int f=1;
char ch=getchar();
x=0;
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+(ch&15);
ch=getchar();
}
x*=f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,k;
int a[Maxn];
int main(){
read(n);read(k);
for(int i=1;i<=n;i++) read(a[i]);
int q[Maxn],l=1,r=1;
for(int i=1;i<=n;i++){
while(l<=r&&q[l]<=i-k) l++;
while(l<=r&&a[q[r]]<=a[i]) r--;
q[++r]=i;
if(i>=k) write(a[q[l]]),putchar('\n');
}
return 0;
}
本文欢迎转载,转载时请注明本文链接