一种神奇的算法——单调队列

单调队列,顾名思义,就是一种队列。

在进入正文中,我们先来看这么一个问题:传送门

现在有一堆数字共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\)可以忽略

posted @ 2019-03-26 13:32  zeroqq  阅读(5957)  评论(1编辑  收藏  举报
Live2D