Week5 作业D - 滑动窗口[POJ - 2823]
题目大意
输入
输出
基本思路
这个题的数据规模较大,k和n最大可以达到1e6,因此如果我们暴力判断所有区间(窗口内元素的范围)中的最大值和最小值一定会超时(复杂度)。
事实上,当k比较大的时候,窗口右移一格,区间内的元素大多数都没变(右侧增加一个,左侧移出一个)。因此,如果我们可以一直维护区间内的元素单调性,我们便可以在的复杂度内知道一个区间的最大值和最小值。
使用单调队列就可以做到这一点。比如,我们可以维护一个从队首到队尾单调递增的队列。当区间在最左侧时,我们将区间内元素从左到右以此入队,不过,要一直保持队列中元素的单调性,也就是说,如果即将入队元素比队尾元素更小的话,那么队尾元素出队;当区间每次右移一格时,我们判断一下队首元素是否还在区间中,如果不在,那么出队,另外,我们将右侧刚刚进入区间的元素入队,同样,要保证队列单调性。这样的话,队首的元素便是当前区间的最小值。同理,我们维护一个从队首到队尾的单调递减队列,队首的值就是每个区间的最大值。
所以,只要我们维护两个单调队列,即可在复杂度完成这个任务。
深究
为什么我们可以从队首和队尾弹出元素而仍然可以准确得到区间的最大值和最小值?
我们首先考虑队首,当队首元素不在区间中,我们将它弹出,这似乎是理所当然的,毕竟我们是要求区间中元素的最值。但是,问题是,当队首元素在区间中时,队列中其他元素一定也在当前区间中吗?答案是肯定的。因为,队列很好的保证了其中元素的次序性,队中其他元素必然比队首元素后入队。而窗口从左向右移动,元素只会从左侧被移出区间,元素从左向右入队,对于队列除队首之外任何一个元素,如果它左侧的元素(队首)仍在区间中,那么这个元素也一定还在区间中。
我们可以再思考一下队尾元素的弹出。假设我们维护了一个从左向右单调递增的队列,要找区间内的最小值。比如我们从队尾弹出元素a,那么这个元素一定满足的条件是:它的右侧一个刚刚要进入区间的元素b比它更小。我们不难发现,元素b比元素a更小,并且b一定比a更晚离开区间,因为我们要求最小值,所以a元素对我们的问题已经没有任何意义,我们当然可以把它丢掉。
注意事项
因为我是直接自己开数组自己维护队列,一定保证队列容量足够大,队首队尾都可能逐渐后移,所以数组长度为k是不够的,为了保险要开的比n更大。
完整代码
#include<iostream>
using namespace std;
int n, k;
int l, r;
int front=0, tail=-1;
int q[1000005]; //这个队列不一定什么时候空间都够用吧
int a[1000005];
int main ()
{
std::ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
r+=k;
for(int j=l;j<r;j++) //装队列
{
while(front <= tail && a[q[tail]] > a[j]) tail--;
q[++tail] = j;
}
cout<<a[q[front]];
l++, r++;
while(r <= n)
{
while(front <= tail && q[front] < l) front++;
while(front <= tail && a[q[tail]] > a[r-1]) tail--;
q[++tail] = r-1;
cout<<" "<<a[q[front]];
l++, r++;
}
cout<<endl;
l = 0;
r = k;
front=0;
tail=-1;
for(int j=l;j<r;j++) //装队列
{
while(front <= tail && a[q[tail]] < a[j]) tail--;
q[++tail] = j;
}
cout<<a[q[front]];
l++, r++;
while(r <= n)
{
while(front <= tail && q[front] < l) front++;
while(front <= tail && a[q[tail]] < a[r-1]) tail--;
q[++tail] = r-1;
cout<<" "<<a[q[front]];
l++, r++;
}
return 0;
}