学习笔记:DP优化单调队列优化

引入

直接上例题传送门luoguP1886
朴素算法的话,我们需要枚举每一个点,并枚举这个范围内的最大值和最小值。
时间复杂度O(nm),显然不够优秀,那么有什么优化的方法呢?

原理

通俗地理解单调队列,不恰当地比喻就是你在竞赛队伍中,但有另一个人,比你年龄还小,还比你厉害,那你就没有存在的价值了,可以永久抛弃了。
单调队列大概也是这个思想,单调单调,我们肯定要维护它的单调性,那么我们就拿样例1 3 -1 -3 5 3 6 7来说,讨论每个长度为3的区间最小值。
这里我使用的是STL里的deque双端队列。
队列里没有东西,从队尾把1放进去;
再看3,由于3在1后面,尽管3比1大,但有可能1过期后,3可以成为最小的,所以保留,从队尾入队;
再看-1,我们发现-1在3,1后面,而且还比他们小,那么3,1从队首出队,并把-1从队尾入队,此时输出队首的值-1;
再看-3,我们发现-3在-1后面,而且比-1还小,那么-1从队首出队,-3从队尾入队,输出队首的值-3
……以此类推即可。
我们发现在单调队列中,队列里的元素一定是单调的,队首一定是要找的最大或最小值。
代码实现:

#include
using namespace std;

int a[1000005],n,k;

inline void Min()
{
	deque<int>q;
	q.push_back(1);
	if(k==1) cout<<a[1]<<" ";//特判一下
	for(int i=2;i<=n;i++)
	{
		while(!q.empty() && a[i]<a[q.back()])//处理大于而在前面的
			q.pop_back();
		q.push_back(i);
		while(!q.empty() && q.front()<i-k+1)//处理过期的
			q.pop_front();
		if(i>=k)
			cout<<a[q.front()]<<" ";
	}
	cout<<endl;
}

inline void Max()
{
	deque<int>q;
	q.push_back(1);
	if(k==1) cout<<a[1]<<" ";
	for(int i=2;i<=n;i++)
	{
		while(!q.empty() && a[i]>a[q.back()])
			q.pop_back();
		q.push_back(i);
		while(!q.empty() && q.front()<i-k+1)
			q.pop_front();
		if(i>=k)
			cout<<a[q.front()]<<" ";
	}
	cout<<endl;
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	Min();Max(); 	 
	return 0;
}

值得注意的是,在双端队列中,我们存储的是每个元素的下标,因为我们要保证下标单增,且对应的数字单增。
下次将介绍较复杂的DP,并如何使用单调队列优化。

你可能感兴趣的:(学习笔记,动态规划,算法,c++)