单调队列优化的DP
单调队列整理链接
动态规划,我们经常会遇到转移的过程需要和前面 ( L , L + m ) (L,L+m) (L,L+m)的区间转移的操作
- 若每次只需要 ( L , L + m ) (L,L+m) (L,L+m)的最大或最小值
-
m
m
m的大小固定
那么我们就可以用单调队列来取 ( L , L + m ) (L,L+m) (L,L+m)的最值来优化动态规划
P1070 道路游戏
同类题链接
题意
分析
状态
本题动态规划的状态很难设计
- 因为每次机器人用完后,能从任意节点购买机器人
因此,节点的状态几乎没用
我们发现机器人个数也不是很重要
当我们以时间为状态时,
d
p
[
i
]
dp[i]
dp[i]表示第
i
i
i个时刻,机器人已经走完的最大收益
状态转移
d p [ i ] = m a x ( d p [ i ] , d p [ i − 1 ] + w 1 , d p [ i − 2 ] + w 2 , ⋯   , d p [ i − p + 1 ] + w p − 1 ) dp[i]=max(dp[i],dp[i-1]+w_{1},dp[i-2]+w_{2},\cdots,dp[i-p+1]+w_{p-1}) dp[i]=max(dp[i],dp[i−1]+w1,dp[i−2]+w2,⋯,dp[i−p+1]+wp−1)
单调队列优化
我们用前缀和来计算收益
t
t
t表示时间,
j
j
j表示地点
t t t \ j j j | 1 | 2 | 3 |
---|---|---|---|
1 | a [ 1 ] [ 1 ] a[1] [1] a[1][1] | a [ 1 ] [ 2 ] a[1][2] a[1][2] | a [ 1 ] [ 3 ] a[1][3] a[1][3] |
2 | a [ 2 ] [ 1 ] a[2][1] a[2][1] | a [ 2 ] [ 2 ] a[2][2] a[2][2] | a [ 2 ] [ 3 ] a[2][3] a[2][3] |
3 | a [ 3 ] [ 1 ] a[3][1] a[3][1] | a [ 3 ] [ 2 ] a[3][2] a[3][2] | a [ 3 ] [ 3 ] a[3][3] a[3][3] |
以下为前缀和
t t t \ j j j | 1 | 2 | 3 | 1 | 2 | 3 |
---|---|---|---|---|---|---|
1 | a [ 1 ] [ 1 ] a[1] [1] a[1][1] | a [ 1 ] [ 2 ] a[1][2] a[1][2] | a [ 1 ] [ 3 ] a[1][3] a[1][3] | a [ 1 ] [ 1 ] a[1] [1] a[1][1] | a [ 1 ] [ 2 ] a[1][2] a[1][2] | a [ 1 ] [ 3 ] a[1][3] a[1][3] |
2 | a [ 2 ] [ 1 ] a[2][1] a[2][1] | a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] a[1][1]+a[2][2] a[1][1]+a[2][2] | a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] a[1][2]+a[2][3] a[1][2]+a[2][3] | a [ 1 ] [ 3 ] + a [ 2 ] [ 1 ] a[1][3]+a[2][1] a[1][3]+a[2][1] | a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] a[1][1]+a[2][2] a[1][1]+a[2][2] | a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] a[1][2]+a[2][3] a[1][2]+a[2][3] |
3 | a [ 3 ] [ 1 ] a[3][1] a[3][1] | a [ 2 ] [ 1 ] + a [ 3 ] [ 2 ] a[2][1]+a[3][2] a[2][1]+a[3][2] | a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] + a [ 3 ] [ 3 ] a[1][1]+a[2][2]+a[3][3] a[1][1]+a[2][2]+a[3][3] | a [ 1 ] [ 2 ] + a [ 2 ] [ 3 ] + a [ 3 ] [ 1 ] a[1][2]+a[2][3]+a[3][1] a[1][2]+a[2][3]+a[3][1] | a [ 1 ] [ 3 ] + a [ 2 ] [ 1 ] + a [ 3 ] [ 2 ] a[1][3]+a[2][1]+a[3][2] a[1][3]+a[2][1]+a[3][2] | a [ 1 ] [ 1 ] + a [ 2 ] [ 2 ] + a [ 3 ] [ 3 ] a[1][1]+a[2][2]+a[3][3] a[1][1]+a[2][2]+a[3][3] |
- 关于对角线取前缀和
w
j
=
−
c
o
s
t
[
j
−
k
]
+
s
u
m
[
t
]
[
j
]
−
s
u
m
[
t
−
k
]
[
j
−
k
]
w_{j}=-cost[j-k]+sum[t][j]-sum[t-k][j-k]
wj=−cost[j−k]+sum[t][j]−sum[t−k][j−k]
对于固定
i
i
i和
j
j
j,显然
−
c
o
s
t
[
j
−
k
]
−
s
u
m
[
t
−
k
]
[
j
−
k
]
-cost[j-k]-sum[t-k][j-k]
−cost[j−k]−sum[t−k][j−k]是连续的值,我们可以尝试用单调队列维护
t t t \ j j j | 1 | 2 | 3 |
---|---|---|---|
1 | d p [ 0 ] − c o s t [ 1 ] − s u m [ 0 ] [ 0 ] dp[0]-cost[1]-sum[0][0] dp[0]−cost[1]−sum[0][0] | d p [ 0 ] − c o s t [ 2 ] − s u m [ 0 ] [ 1 ] dp[0]-cost[2]-sum[0][1] dp[0]−cost[2]−sum[0][1] | d p [ 0 ] − c o s t [ 3 ] − s u m [ 0 ] [ 2 ] dp[0]-cost[3]-sum[0][2] dp[0]−cost[3]−sum[0][2] |
2 | d p [ 1 ] − c o s t [ 1 ] − s u m [ 1 ] [ 0 ] dp[1]-cost[1]-sum[1][0] dp[1]−cost[1]−sum[1][0] | d p [ 1 ] − c o s t [ 2 ] − s u m [ 1 ] [ 1 ] dp[1]-cost[2]-sum[1][1] dp[1]−cost[2]−sum[1][1] | d p [ 1 ] − c o s t [ 3 ] − s u m [ 1 ] [ 2 ] dp[1]-cost[3]-sum[1][2] dp[1]−cost[3]−sum[1][2] |
3 | d p [ 2 ] − c o s t [ 1 ] − s u m [ 2 ] [ 0 ] dp[2]-cost[1]-sum[2][0] dp[2]−cost[1]−sum[2][0] | d p [ 2 ] − c o s t [ 2 ] − s u m [ 2 ] [ 1 ] dp[2]-cost[2]-sum[2][1] dp[2]−cost[2]−sum[2][1] | d p [ 2 ] − c o s t [ 3 ] − s u m [ 2 ] [ 2 ] dp[2]-cost[3]-sum[2][2] dp[2]−cost[3]−sum[2][2] |
- 我们显然发现,对角线上元素符合 s u m [ t − k ] [ j − k ] sum[t-k][j-k] sum[t−k][j−k]
所以,我们需要维护一个对角线上的队列,对角线上元素 t − j t-j t−j为定值
t t t \ j j j | 1 | 2 | 3 |
---|---|---|---|
1 | 0 0 0 | 1 1 1 | 2 2 2 |
2 | − 1 -1 −1 | 0 0 0 | 1 1 1 |
3 | − 2 -2 −2 | − 1 -1 −1 | 0 0 0 |
我们以 t − j t-j t−j为队列 i d id id的标号,维护队列
- 我们每个时间点将每个 j j j对应的 d p [ t − 1 ] − c o s t [ j ] − s u m [ t − 1 ] [ j − 1 ] dp[t-1]-cost[j]-sum[t-1][j-1] dp[t−1]−cost[j]−sum[t−1][j−1]加入相应的队列
- 并且维护每个队列的长度 ≤ p \leq p ≤p
void push(int t, int j) {
int id = t-j+1000;
int now = dp[t - 1] - cost[j] - sum[t - 1][j - 1];
while (tail[id] >= head[id] && stack[id][tail[id]].first <= now)tail[id]--;
stack[id][++tail[id]] = pii(now, t);
}
void getfront(int t,int id) {
while (tail[id] >= head[id] && stack[id][head[id]].second < t - p + 1)head[id]++;
ans[id] = stack[id][head[id]];
}
void Queue(int t) {
for (int j = 1; j <= n; j++)
push(t, j);
for(int j=1-m+1000; j<=n-1+1000; j++)
getfront(t,j);
}
状态转移
d p [ t ] = s u m [ t ] [ t − j + 1000 ] + a n s [ t − j + 1000 ] dp[t]=sum[t][t-j+1000]+ans[t-j+1000] dp[t]=sum[t][t−j+1000]+ans[t−j+1000]
for (int i = 1; i <= m; i++) {
Queue(i);
for (int j = 1 - m + th; j <= n - 1 + th; j++) {
if (ans[j].second==0 || ans[j].second < i - p+1)continue;
dp[i] = max(dp[i], ans[j].first + sum[i][i + j - th]);
}
}
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 1005;
const int th = 1000;
const int inf = 0X3f3f3f3f;
int a[maxn][maxn], sum[maxn][maxn << 1], cost[maxn];
int dp[maxn];
int n, m, p;
void Read() {
int x;
scanf("%d%d%d",&n,&m,&p);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d",&a[j][i]);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++)
sum[i][j] = sum[i - 1][j - 1] + a[i][j];
for (int j = 1; j <= n; j++)
sum[i][j + n] = sum[i - 1][j + n - 1] + a[i][j];
}
for (int i = 1; i <= n; i++)scanf("%d",cost[i]);
}
inline int getid(int t, int j) {
return j - t + th;
}
pii stack[maxn << 1][maxn], ans[maxn << 1];
int head[maxn << 1], tail[maxn << 1];
void push(int t, int j) {
int id = getid(t, j);
int now = dp[t - 1] - cost[j] - sum[t - 1][j - 1];
while (tail[id] >= head[id] && stack[id][tail[id]].first <= now)tail[id]--;
stack[id][++tail[id]] = pii(now, t);
}
void getfront(int t,int id) {
while (tail[id] >= head[id] && stack[id][head[id]].second < t - p + 1)head[id]++;
ans[id] = stack[id][head[id]];
}
void Queue(int t) {
for (int j = 1; j <= n; j++)
push(t, j);
for(int j=1-m+th; j<=n-1+th; j++)
getfront(t,j);
}
int main() {
Read();
fill(dp, dp + 1 + m, -inf);
dp[0] = 0;
for (int i = 0; i <= th<<1; i++)head[i] = 1;
for (int i = 1; i <= m; i++) {
Queue(i);
for (int j = 1 - m + th; j <= n - 1 + th; j++) {
if (ans[j].second==0 || ans[j].second < i - p+1)continue;
dp[i] = max(dp[i], ans[j].first + sum[i][i + j - th]);
}
}
printf("%d\n", dp[m]);
}