單調隊列,顧名思義,就是一種隊列。
在進入正文中,我們先來看這么一個問題:傳送門
現在有一堆數字共N個數字(N<=10^6),以及一個大小為k的窗口。現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動后窗口中的最大值和最小值。
例如:
The array is [1 3 -1 -3 5 3 6 7], and k = 3
我們看到題面后,首先一個思路:暴力枚舉!我們只要在\(k-n\)個數中枚舉每一個最大值和最小值,最后取\(min\)或取\(max\)即可,代碼如下
#include<iostream>
using namespace std;
int n,k;
int a[3000003];
int f[3000003];
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int main() {
n=read();k=read();
int l=0;
for(int i=1;i<=n;i++) a[i]=read();
for(int i=k;i<=n;i++) {
int maxn=-0xfffff,minx=0xfffff;
for(int j=i-k+1;j<=i;j++) {
if(a[j]>maxn) maxn=a[j];
if(a[j]<minx) minx=a[j];
}
cout<<minx<<" ";
f[++l]=maxn;
}
cout<<endl;
for(int i=1;i<=l;i++) cout<<f[i]<<" ";
}
然而理想是美好的,但是現實卻是可怕的:
數據范圍
\(50%\)的數據,\(n<=10^5\)
\(100%\)的數據,\(n<=10^6\)
對於這組數據,我們\(O(n*k)\)的方法只能拿\(70\)分(也許是我太蒟了) 所以我們要引進一種新的算法:單調隊列
讓我們先來理解一下單調隊列的工作情況:
對於一個有框的隊列中,我們每次讀入一個數,
\(For\) \(example\)
有這么一組數:
\(1\) \(2\) \(3\) \(2\) \(5\) \(6\) \(7\) \(8\) \(9\) \(10\)
\(k=3\)
我們先讀入\(1\),因為沒有隊首,就將他加入隊。
接着,讀入\(2\),\(2\)比\(1\)大且\(2\)在\(1\)之后\(,\)所以,我們將\(2\)入隊,\(1\)出隊
同理,我們接着讀入\(3\)
可是,到了\(2\)之后,我們發現,\(2\)<\(3\),但是,\(2\)在\(3\)之后出現,所以,我們將\(2\)入隊,所以\(,\)單調隊列中變成了\(3\) \(2\)
接着,我們讀入\(4\),因為\(4>3>2\),所以,我們將\(2\)與\(3\)出隊,隊列中只留下\(4\)
同理,我們后面就是不斷地出隊,入隊的情況。
那有人可能會說,那\(2\)入隊有什么用?
那我們再來看一組數據:
\(1\) \(2\) \(3\) \(2\) \(1\):\(k=2\)
這組數據,前面都與之前一樣,
到\(1\)入隊時,情況就發生了變化:
此時\(3\)由於\(k=2\)出隊
那么,隊首就變成了\(2\),那我們就輸出\(2\),
所以這就是隊首\(>\)讀入的數,但仍要入隊的原因
我們每讀入一個數,就將他與隊首比較\(:\)
如果隊首比他小,那么隊中比這個數小的都出隊(這個數的可持久性比隊中的強且這個數比隊首大)
如果這個數比隊首小,將他入隊:
如果老的數超出范圍,將他出隊:
最終,隊中留下的就是最大的或最年輕的,代碼\(:\)
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 2000003
#define f(i,a,b) for(int i=a;i<=b;i++)
int a[maxn];
struct s{
int id;
int num;
};
s f[maxn];
int l=1;
int r;
int n,m;
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
void write(int x) {
if(x<0) {
putchar('-');
x=-x;
}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int main() {
n=read();m=read();
for(int i=1;i<=n;i++) {
a[i]=read();
}
int sum=0;
f(i,1,n+1) {
if(sum<m) sum++;
else if(l<=r) {
if(f[l].id+m<i) l++;
write(f[l].num);
putchar(' ');
}
while(f[r].num>=a[i]&&l<=r) r--;
f[++r].num=a[i];
f[r].id=i;
}
putchar('\n');
l=1;r=0;sum=0;
memset(f,0,sizeof(f));
f(i,1,n+1) {
if(sum<m) sum++;
else if(l<=r) {
if(f[l].id+m<i) l++;
write(f[l].num);
putchar(' ');
}
while(f[r].num<=a[i]&&l<=r) r--;
f[++r].num=a[i];
f[r].id=i;
}
}
\(PS:read\)和\(write\)可以忽略