DP单调队列优化

引入

当你DP推出的状态转移方程类似这样的时候: d p [ i ] = m i n { d p [ j ] + a [ i ] } dp[i]=min\{dp[j]+a[i]\} dp[i]=min{dp[j]+a[i]},并且每一个 j j j都被固定在同样大小 k k k的一个区间 i i i~ i  ⁣ +  ⁣ k i\!+\!k i+k内,那么,通过下图可以发现,其实整个DP的过程类似单调队列滑动窗口。

由于普通思想需要枚举 j j j,时间复杂度为 O ( n 2 ) O(n^2) O(n2),只要题目把数据稍微卡大一点,就会:
在这里插入图片描述
所以,我们可以考虑用单调队列来优化枚举 j j j的循环,优化后时间复杂度为 O ( n ) O(n) O(n)

单调队列优化

对于当前元素 i i i,先按照题目计算出价值 x i x_i xi,然后放入单调队列中。
单调队列在运行到 i i i的时候,维护过区间 i  ⁣ −  ⁣ k i\!-\!k ik~ i i i,所以答案通过队头就可以 O ( 1 ) O(1) O(1)完成查询。
枚举 i i i的时间复杂度是 O ( n ) O(n) O(n),单调队列循环次数一共是 n n n次,综合复杂度 O ( n ) O(n) O(n)

例题1

题目传送门

普通思路

我们定义 d p i dp_i dpi为只有前 i i i头牛的情况。由于一段的连续长度不能超过 k k k,所以我们考虑在 i − k i-k ik i i i枚举 j j j(一共 k + 1 k+1 k+1个元素),不选第 j j j头牛,刚好剩下 k k k头,满足条件。
我们对 e e e求一个前缀和数组 s s s
因此:
d p i = m i n { d p j − 1 + s i − s j } ( i − k ≤ j ≤ i ) dp_i=min\{dp_{j-1}+s_i-s_j\}(i-k\le j\le i) dpi=min{dpj1+sisj}(ikji)

单调队列优化思路

对于 i i i s i s_i si是定值,不用枚举,而对于剩下的 d p j − 1 − s j dp_{j-1}-s_j dpj1sj可以考虑用单调队列维护最大值。
由于 i − k < i ≤ i i-k<i\le i ik<ii,所以前区间的最小值一定能维护到,方案可行。

//https://www.luogu.com.cn/problem/P2627
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,k;
int a[N];
int dp[N];
int q[N];
int f[N];
int tail,head;
signed main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++)a[i]=a[i-1]+read();
	for(int i=1;i<=n;i++){
		f[i]=dp[i-1]-a[i];
		while(head<=tail&&f[q[tail]]<=f[i])tail--;
		q[++tail]=i;
		while(head<=tail&&q[head]<i-k)head++;//不在可行区间内
		dp[i]=f[q[head]]+a[i];
	}
	print(dp[n]);
}

例题2

题目传送门
定义 d p i dp_i dpi为到第 i i i个格子的最大值。
对于一个点 i i i,最远可以从 i − r i-r ir过来,最近可以从 i − l i-l il过来。
那么,对于当前点的最大值就是 i  ⁣ −  ⁣ r i\!-\!r ir~ i  ⁣ −  ⁣ l i\!-\!l il中的最大值加上 a i a_i ai
有:
d p i = m i n { d p j } + a i ( i − r ≤ j ≤ i − l ) dp_i=min\{dp_j\}+a_i(i-r\le j\le i-l) dpi=min{dpj}+ai(irjil)
考虑对 d p j dp_j dpj进行单调队列优化。
将离 i i i最近的可行点 i − l i-l il加入单调队列中,维护最大值( i − r i-r ir在之前 i + l − r i+l-r i+lr这个点已经加入单调队列了,不必再加)。

//https://www.luogu.com.cn/problem/P1725
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n;
int l,r;
int a[N];
int q[N];
int head,tail;
int dp[N];
signed main(){
	n=read(),l=read(),r=read();
	for(int i=0;i<=n;i++)a[i]=read(),dp[i+1]=-1e18;//记得初始化
	for(int i=l;i<=n;i++){
		while(head<=tail&&dp[q[tail]]<=dp[i-l])tail--;
		q[++tail]=i-l;
		while(head<=tail&&q[head]<i-r)head++;
		dp[i]=dp[q[head]]+a[i];
	}
	int mx=-1e18;
	for(int i=n-r+1;i<=n;i++)mx=max(mx,dp[i]);//可以到达对岸的点
	print(mx);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值