单调队列优化DP

单调队列优化DP:顾名思义,就是拿单调队列对DP进行优化。可以把N维的DP降低到N-1维。

先来看一道例题:

//---------------------------------------------------------------------------------------------------------------//

输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。(n<=300000)


例如 1,-3,5,1,-2,3

当m=4时,S=5+1-2+3=7
当m=2或m=3时,S=5+1=6

//----------------------------------------------------------------------------------------------------------------//

对于这道题,很明显可以用DP来解决,DP方程为:

f[i] = sum[i] - max(sum[j] | i-M<=j<=i)

答案为max(f[i] | 1<=i<=n)。

这样做的话,时间复杂度为O(n^2),超时。

注意到max(sum[j]),这时求静态区间的最大值,可以用RMQ进行优化。

时间复杂度为O(nlogn)=O(300000 * 18)=O(5400000)。对于本题,还可以承受。

可如果n再大一点的话,O(nlogn)的时间是无法接受的。对此,就可以用单调队列来进行优化。

对于每一个i:

1,如果sum[队首]>sum[i],则删除队首。

2,插入i

3,若对尾不在范围内(对于本题,即对尾

这样的话:就可以保证队列元素(即i)的单调性和队列优先级(即sum[i])的单调性,证明略。

如果上面还看不懂,那就看程序吧。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
#define Maxn 300010

int n,m;

list h;

LL sum[Maxn];
LL ans;

int main()
{
	scanf("%d%d",&n,&m);
	
	int x;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		sum[i] = sum[i-1] + (LL)x;
	}
	
	ans = max(0LL,sum[1]);//全部是负数的情况
	h.push_front(1);
	for(int i=1;i<=n;i++)
	{
		for(;( !h.empty() ) && sum[h.front()] > sum[i]; h.pop_front() );//1
		h.push_front(i);//2
		for(;( !h.empty() ) && i-m > h.back(); h.pop_back() );//3
		ans = max(ans,sum[i] - sum[h.back()]);//计算答案
	}
	printf("%I64d\n",ans);
	return 0;
}

所以,每当遇到形如f[i] = max or min(a[k] | p[i]<=k<=q[i]) + b[i] (q[i],p[i]单调,b[i]与k无关)这样的DP方程的时候,就可以用如上方法进行单调队列优化。


转载于:https://www.cnblogs.com/ouqingliang/p/9245255.html

你可能感兴趣的:(单调队列优化DP)