区间划定最小值题解--单调队列

滑动窗口求区间最小值–单调队列

题目如下----转自洛谷

题目描述
一个含有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
#include
#include
#include
#include

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!

你可能感兴趣的:(题解)