引入
当你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
i−k~
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
i−k到
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{dpj−1+si−sj}(i−k≤j≤i)
单调队列优化思路
对于
i
i
i,
s
i
s_i
si是定值,不用枚举,而对于剩下的
d
p
j
−
1
−
s
j
dp_{j-1}-s_j
dpj−1−sj可以考虑用单调队列维护最大值。
由于
i
−
k
<
i
≤
i
i-k<i\le i
i−k<i≤i,所以前区间的最小值一定能维护到,方案可行。
//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
i−r过来,最近可以从
i
−
l
i-l
i−l过来。
那么,对于当前点的最大值就是
i
−
r
i\!-\!r
i−r~
i
−
l
i\!-\!l
i−l中的最大值加上
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(i−r≤j≤i−l)
考虑对
d
p
j
dp_j
dpj进行单调队列优化。
将离
i
i
i最近的可行点
i
−
l
i-l
i−l加入单调队列中,维护最大值(
i
−
r
i-r
i−r在之前
i
+
l
−
r
i+l-r
i+l−r这个点已经加入单调队列了,不必再加)。
//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);
}