滑动窗口求区间最小值–单调队列
题目如下----转自洛谷
题目描述
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
输入输出格式
输入格式:
第一行两个数n,m。
第二行,n个正整数,为所给定的数列。
输出格式:
n行,第i行的一个数ai,为所求序列中第i个数前m个数的最小值。
输入输出样例
输入样例#1:
6 2
7 8 1 4 3 2
输出样例#1:
0
7
7
1
1
3
【数据规模】
m≤n≤2000000
ai≤3×107a_i\leq 3\times 10^7ai≤3×107
这道题作为一道标准的模板题,做法并不少暴力的话可以直接一层层搜索,但是时间复杂度尾,可以用线段树,rmq等等,但是身为蒟蒻一枚的我都不会,怎么办怎么办?经过了H巨佬的点播,我明白了,原来这玩意可以不用涉及到什么高级的算法,只需要一个简简单单的队列就可以搞定,而且时间复杂度为O(n),是不是很神奇,但是,理想很丰满,现实很骨感,实现这个操作并非易事(对于蒟蒻的我来说),所以特写博客一篇作为纪念。
首先,要找滑动区间的最值,为了避免多次的重复搜索,我们就需要一个状态数组来记录以前的搜索记录,首先想到的就是记录下扫描到每一个点时候的区间最小值,但这样做有一个问题——我们并不知道存下来的最小值是否已经在范围m之外,这样一来,又需要继续扫描来判定上一个元素的最值是否有效,否则重新搜索,这样一来时间复杂的就回到了n方 ,离O(n)的现实还有那么一点远。亡羊补牢,为时不晚,换一个思路,发现如果我们对这种状态有“保质期”的元素存储下标的话,那么就会很容易地判断此元素是否在范围m之外,并且通过下标提取元素也十分方便,那么如何在最小的时间取到最小值呢?我们可以使用单调队列让队列的末端永远都是在保质期内的最小值,保质期过了就把它踢掉,这样一来问题就很好解决了。
附上代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
deque <int> q;
deque <int>::iterator it;
int a[12345678],b[12345687];
int main()
{
// freopen("testdata.in","r",stdin);
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
printf("0\n");
for(int i=0;i<n;i++)
{
while(!q.empty() && a[q.back()] >= a[i]) q.pop_back();
q.push_back(i); //下标入队
// printf("i: %d q.front: %d\n",i,q.front());
b[i] = a[q.front()];
if(q.front() <= i-k+1 && i>=k-1)
q.pop_front();
}
for(int i=0;i< n-1;i++)
printf("%d\n",b[i]);
return 0;
}
perfect!