动态规划之单调队列优化

先上一道单调队列动态规划的模板题 https://www.luogu.com.cn/problem/P1725
这道题很明显是一道dp题,可以通过两个for循环就能得到结果,但是N,L,R太大了,两层for循环就超时了,所以我们得用单调队列来优化。

动态规划一般类如
dp[ i ]=min( dp[ j ] + f(j)) (0 < j < i ),
因为j是从0开始的,使用我们可以用一个变量记录dp[ j ] + f(j)的最小值

而dp[ i ]=min( dp[ j ] + f(j)) ( i-k< j < i ), 则是一个区间内的,那么我们不能只用一个变量去估计它,得用单调队列。
单调队列不同于优先队列,它存的对应数组的值是单调的,和优先队列一样,但是它存的数组的下标也是递增的。单调队列我们可以从队头弹出队列,也可以从队尾弹出。
比如单调队列我们维护的是一个递减的队列,那么我们的队头就是区间内最大的,我们用head记为队头,tail为队尾。那么每次新的变量就和队尾比较,若新的变量大,那么我们则不断把队尾的元素剔除,最后把元素加进来。对于队头来说,如果它已经出了界线,那就把它删除队列。

先上一道单调队列(没有包含动态规划)的题https://www.luogu.com.cn/problem/P1886

下面是琪露诺的代码
因为我们要求的是大于N就行了,所以我们只要求n-right+1到n就行了,因为比如我们跳到n-1,我们还没大于N,但是我们再跳一次也没有意义了,因为大于N没有那个冰冻指数了。

`#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
int a[200200];
int dp[200200];
int q[200200];
int head=1,tail=0,cur=0; 
int main()
{
	std::ios::sync_with_stdio(false);
	int n,left,right;
	while(cin>>n>>left>>right){
		for(int i=0;i<=n;i++)
			cin>>a[i];
		memset(dp,-1,sizeof(dp));
		dp[0]=0;
		int ans=0;
		for(int i=1;i<=n+right;i++){
			while(i-cur>=left){
				//每次改变值的时候保持head<=tail
				while(head<=tail&&dp[cur]>=dp[q[tail]]) tail--;//如果当前的值大于队尾,则tail--
				q[++tail]=cur++;
				while(head<=cur&&i-q[head]>right) head++;//如果head超出区间了,则删除队头
				//状态转移
				if(head<=tail)
					dp[i]=dp[q[head]]+a[i];
			}
		}
		for(int i=n-right+1;i<=n;i++)
			ans=max(ans,dp[i]);
		cout<<ans<<endl;
	}
}

还有一些题,用到求最大值和最小值,那么就用两次(单调递增,单调递减)。或者是左边区间和右边区间都要求,那么就正着来一次,反着来一次,分别做标记就行了,如拥挤的奶牛S

最后总结:单调队列就是把结果先排好,再来用的时候就直接O(1)查询了,就可以把复杂度O(n^2)降到O(n)了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值