单调队列优化的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)的最值来优化动态规划
HDU3401 Trade
题意
X某预知了接下来
T
T
T天的某只股票情况
第
i
i
i天,买进该股票价格为
A
P
i
AP_{i}
APi,卖出该股票价格为
B
P
i
BP_{i}
BPi,可以买进
S
A
i
SA_{i}
SAi股,可以卖出
S
B
i
SB_{i}
SBi股
手上最多持有
M
a
x
p
Maxp
Maxp股,每次交易(买进、卖出均算一次交易)后,w天不能交易(比如第1天买入股票,要在w+2天才能卖出股票)
本钱无限,问最大收益
分析
状态
相信大家都会想到 d p [ T ] [ m a x p ] dp[T][maxp] dp[T][maxp],第一下标表示第几天,第二下标表示持有股票数
状态转移
令 i i i为当前天数, j j j为当前持有股票数, r r r为 w w w天前的天数 ( r < j − w − 1 ) (r<j-w-1) (r<j−w−1), k k k为 r r r天时手持股票数
- 不买入股票: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j ] ) dp[i][j]=max(dp[i-1][j],dp[i][j]) dp[i][j]=max(dp[i−1][j],dp[i][j])
- 第 i i i天买入 j − k j-k j−k股票: d p [ i ] [ j ] = m a x ( d p [ r ] [ k ] − ( j − k ) ∗ a p [ i ] , d p [ i ] [ j ] ) dp[i][j]=max(dp[r][k]-(j-k)*ap[i],dp[i][j]) dp[i][j]=max(dp[r][k]−(j−k)∗ap[i],dp[i][j])
- 第
i
i
i天卖出
j
−
k
j-k
j−k股票:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
r
]
[
k
]
+
(
j
−
k
)
∗
b
p
[
i
]
,
d
p
[
i
]
[
j
]
)
dp[i][j]=max(dp[r][k]+(j-k)*bp[i],dp[i][j])
dp[i][j]=max(dp[r][k]+(j−k)∗bp[i],dp[i][j])
显然后面两个公式复杂度很高, r r r和 k k k都不确定
由第一个公式可得,
d
p
[
r
]
[
k
]
>
d
p
[
r
−
1
]
[
k
]
dp[r][k]>dp[r-1][k]
dp[r][k]>dp[r−1][k],所以
r
r
r取
i
−
w
−
1
i-w-1
i−w−1即可
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
]
)
dp[i][j]=max(dp[i-1][j],dp[i][j])
dp[i][j]=max(dp[i−1][j],dp[i][j])
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
w
−
1
]
[
k
]
−
(
j
−
k
)
∗
a
p
[
i
]
,
d
p
[
i
]
[
j
]
)
dp[i][j]=max(dp[i-w-1][k]-(j-k)*ap[i],dp[i][j])
dp[i][j]=max(dp[i−w−1][k]−(j−k)∗ap[i],dp[i][j])
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
w
−
1
]
[
k
]
+
(
j
−
k
)
∗
b
p
[
i
]
,
d
p
[
i
]
[
j
]
)
dp[i][j]=max(dp[i-w-1][k]+(j-k)*bp[i],dp[i][j])
dp[i][j]=max(dp[i−w−1][k]+(j−k)∗bp[i],dp[i][j])
但k的值复杂度依旧很高
我们将两个式子变换以下
d
p
[
i
−
w
−
1
]
[
k
]
−
(
j
−
k
)
∗
a
p
[
i
]
=
d
p
[
i
−
w
−
1
]
[
k
]
+
k
∗
a
p
[
i
]
−
j
∗
a
p
[
i
]
dp[i-w-1][k]-(j-k)*ap[i]=dp[i-w-1][k]+k*ap[i]-j*ap[i]
dp[i−w−1][k]−(j−k)∗ap[i]=dp[i−w−1][k]+k∗ap[i]−j∗ap[i]
d
p
[
i
−
w
−
1
]
[
k
]
+
(
j
−
k
)
∗
b
p
[
i
]
=
d
p
[
i
−
w
−
1
]
[
k
]
+
k
∗
b
p
[
i
]
−
j
∗
b
p
[
i
]
dp[i-w-1][k]+(j-k)*bp[i]=dp[i-w-1][k]+k*bp[i]-j*bp[i]
dp[i−w−1][k]+(j−k)∗bp[i]=dp[i−w−1][k]+k∗bp[i]−j∗bp[i]
我们发现
j
∗
a
p
[
i
]
j*ap[i]
j∗ap[i]是固定的,只需要求出
d
p
[
i
−
w
−
1
]
[
k
]
+
k
∗
a
p
[
i
]
(
j
−
a
s
[
i
]
−
1
<
k
<
j
+
1
)
dp[i-w-1][k]+k*ap[i](j-as[i]-1<k < j+1)
dp[i−w−1][k]+k∗ap[i](j−as[i]−1<k<j+1)的最大值即可
同理求出
d
p
[
i
−
w
−
1
]
[
k
]
+
k
∗
b
p
[
i
]
(
j
−
1
<
k
<
j
+
b
s
[
i
]
+
1
)
dp[i-w-1][k]+k*bp[i](j-1<k<j+bs[i]+1)
dp[i−w−1][k]+k∗bp[i](j−1<k<j+bs[i]+1)的最大值
而最大值维护就可以用单调队列维护了
代码详解
预处理 [ 1 , w + 1 ] [1,w+1] [1,w+1]天
void Pre_Work() {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= maxp; j++)
dp[i][j] = -inf;
for (int i = 1; i <= w + 1; i++) {
for (int j = 0; j <= as[i]; j++)
dp[i][j] = max(dp[i - 1][j], -ap[i] * j);
for (int j = as[i] + 1; j <= maxp; j++)
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
}
}
d p [ i − w − 1 ] [ k ] + k ∗ a p [ i ] ( j − a s [ i ] − 1 < k < j + 1 ) dp[i-w-1][k]+k*ap[i](j-as[i]-1<k < j+1) dp[i−w−1][k]+k∗ap[i](j−as[i]−1<k<j+1)的最大值
void Queue_bug(int j) {
int l = 1, r = 0, i = j - w - 1, now;
for (int k = 0; k <= maxp; k++) {
now = k * ap[j] + dp[i][k];
while (r >= l && stack[r].first <= now)r--;
stack[++r] = pii(now, k);
while (r >= l && stack[l].second < k - as[j])l++;
ans[k] = stack[l].first;
}
}
d p [ i − w − 1 ] [ k ] + k ∗ b p [ i ] ( j − 1 < k < j + b s [ i ] + 1 ) dp[i-w-1][k]+k*bp[i](j-1<k<j+bs[i]+1) dp[i−w−1][k]+k∗bp[i](j−1<k<j+bs[i]+1)的最大值
void Queue_sell(int j) {
int l = 1, r = 0, i = j - w - 1, now;
for (int k = maxp; k >= 0; k--) {
now = k * bp[j] + dp[i][k];
while (r >= l && stack[r].first <= now)r--;
stack[++r] = pii(now, k);
while (r >= l && stack[l].second > k + bs[j])l++;
ans[k] = stack[l].first;
}
}
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <iomanip>
using namespace std;
#pragma warning (disable:4996)
typedef pair<int, int> pii;
const int maxn = 2005;
const int inf = 1e9;;
int n, maxp, w;
int ap[maxn], bp[maxn], as[maxn], bs[maxn];
void Read() {
scanf("%d%d%d", &n, &maxp, &w);
for (int i = 1; i <= n; i++)
scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]);
}
int dp[maxn][maxn];
void Pre_Work() {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= maxp; j++)
dp[i][j] = -inf;
for (int i = 1; i <= w + 1; i++) {
for (int j = 0; j <= as[i]; j++)
dp[i][j] = max(dp[i - 1][j], -ap[i] * j);
for (int j = as[i] + 1; j <= maxp; j++)
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
}
}
pii stack[maxn];
int ans[maxn];
void Queue_bug(int j) {
int l = 1, r = 0, i = j - w - 1, now;
for (int k = 0; k <= maxp; k++) {
now = k * ap[j] + dp[i][k];
while (r >= l && stack[r].first <= now)r--;
stack[++r] = pii(now, k);
while (r >= l && stack[l].second < k - as[j])l++;
ans[k] = stack[l].first;
}
}
void Queue_sell(int j) {
int l = 1, r = 0, i = j - w - 1, now;
for (int k = maxp; k >= 0; k--) {
now = k * bp[j] + dp[i][k];
while (r >= l && stack[r].first <= now)r--;
stack[++r] = pii(now, k);
while (r >= l && stack[l].second > k + bs[j])l++;
ans[k] = stack[l].first;
}
}
void Dp() {
for (int i = w + 2; i <= n; i++) {
Queue_bug(i);
for (int j = 0; j <= maxp; j++)
dp[i][j] = max(dp[i - 1][j], ans[j] - j * ap[i]);
Queue_sell(i);
for (int j = 0; j <= maxp; j++)
dp[i][j] = max(dp[i][j], ans[j] - j * bp[i]);
}
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
Read();
Pre_Work();
Dp();
int Max = 0;
for (int i = 0; i <= maxp; i++)
Max = max(Max, dp[n][i]);
printf("%d\n", Max);
}
}