前言
Dp优化最难理解了…
题目
思路
ap买入价 bp卖出价 as买入最大股 bs卖出最大股
首先,我们要写出一般形式的Dp。
我们定义:
f
[
i
]
[
j
]
:
第
i
天
此
时
手
上
股
票
数
为
j
,
前
i
天
最
大
获
利
f[i][j]:第i天此时手上股票数为j,前i天最大获利
f[i][j]:第i天此时手上股票数为j,前i天最大获利
①我们先初始化边界,:
f
[
i
]
[
j
]
=
{
0
i==0&&j==0
−
I
N
F
otherwise
f[i][j]=\begin{cases} 0&\text{ i==0\&\&j==0}\\ -INF&\text{otherwise} \end{cases}
f[i][j]={0−INF i==0&&j==0otherwise
②然后我们如果写出
O
(
n
4
)
O(n^4)
O(n4)是极为不优秀的,于是预处理Dp转移还要继续, 考虑只买一次
j
j
j 张股票的最小花费,如果可以买,分为买和不买,取max:
f
[
i
]
[
j
]
=
{
m
a
x
{
−
a
p
[
i
]
∗
j
,
f
[
i
−
1
]
[
j
]
}
j∈[0,as[i]]
−
a
p
[
i
]
∗
j
j∈(as[i],Maxp]
f[i][j]=\begin{cases} max\{-ap[i]*j,f[i-1][j]\}&\text{j∈[0,as[i]]}\\ -ap[i]*j&\text{j∈(as[i],Maxp]} \end{cases}
f[i][j]={max{−ap[i]∗j,f[i−1][j]}−ap[i]∗jj∈[0,as[i]]j∈(as[i],Maxp]
这样我们就可以接下来就少一维枚举i的转移点了。
然后我们考虑转移,我们买和卖分开转移:
①买,考虑从(i-w-1)天转移(由于优化初始值[0,i-w-1)不考虑)
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
w
−
1
]
[
k
]
−
a
p
[
i
]
∗
(
j
−
k
)
}
f[i][j]=max\{f[i-w-1][k]-ap[i]*(j-k)\}
f[i][j]=max{f[i−w−1][k]−ap[i]∗(j−k)}
(
0
<
=
k
<
j
<
=
m
i
n
(
k
+
a
s
[
i
]
,
M
a
x
p
)
)
(0<=k<j<=min(k+as[i],Maxp))
(0<=k<j<=min(k+as[i],Maxp))
②卖,同上
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
w
−
1
]
[
k
]
+
b
p
[
i
]
∗
(
k
−
j
)
}
f[i][j]=max\{f[i-w-1][k]+bp[i]*(k-j)\}
f[i][j]=max{f[i−w−1][k]+bp[i]∗(k−j)}
(
M
a
x
p
>
=
k
>
j
>
=
m
a
x
(
k
−
b
s
[
i
]
,
0
)
)
(Maxp>=k>j>=max(k-bs[i],0))
(Maxp>=k>j>=max(k−bs[i],0))
对于答案我们只需要让ans= m a x { f [ i ] [ 0 ] } max\{f[i][0]\} max{f[i][0]}即可,因为如果剩下股票之前不买显然更优,但是,这是一个 O ( n 3 ) O(n^3) O(n3)的转移,显然是会TLE的
于是我们考虑优化
我们记
p
=
i
−
w
−
1
p=i-w-1
p=i−w−1
就拿买来说吧,
我们对状态转移方程进行变形:
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
w
−
1
]
[
k
]
+
a
p
[
i
]
∗
k
−
a
p
[
i
]
∗
j
}
f[i][j]=max\{f[i-w-1][k]+ap[i]*k-ap[i]*j\}
f[i][j]=max{f[i−w−1][k]+ap[i]∗k−ap[i]∗j}
我们记
g
[
k
]
=
f
[
i
−
w
−
1
]
[
k
]
+
a
p
[
i
]
∗
k
g[k]=f[i−w−1][k]+ap[i]∗k
g[k]=f[i−w−1][k]+ap[i]∗k
我们可以发现,对于i固定时,对于 g [ k ] g[k] g[k]是可以用双端队列维护它的单调性的,队列中存 k 和 g [ k ] k和g[k] k和g[k] 记为cnt和val,队列的 v a l val val是单调递减的
对于队首元素,如果 c n t + a s [ i ] < j cnt+as[i]<j cnt+as[i]<j即j已经不在 [ c n t , c n t + a s [ i ] ] [cnt,cnt+as[i]] [cnt,cnt+as[i]]范围内了,pop掉
再考虑即将入队的
j
和
g
[
j
]
j和g[j]
j和g[j],如果
g
[
j
]
g[j]
g[j]比现在队尾更优,显然队尾直接pop掉,因为队尾比它将来更早出队
然后
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
]
[
j
]
,
Q
.
f
r
o
n
t
(
)
.
v
a
l
−
a
p
[
i
]
∗
j
}
f[i][j]=max\{f[i][j],Q.front().val-ap[i]*j\}
f[i][j]=max{f[i][j],Q.front().val−ap[i]∗j}即可
对于卖倒过来做即可,需注意入队出队条件和范围.
代码
#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<vector>
#include<climits>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
int read(){
int f=1,x=0;char c=getchar();
while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
#define MAXN 2000
#define INF 0x3f3f3f3f
struct node{
int cnt,val;
node(){}
node(int C,int V){cnt=C,val=V;}
};
int f[MAXN+5][MAXN+5];
deque<node> Q;
int main(){//f[i][j]:第i天此时手上股票数为j的前i天最大获利
int ans=-INF;
memset(f,-0x3f,sizeof(f)),f[0][0]=0;
int T=read(),MaxP=read(),w=read();
for(int i=1;i<=T;i++){
int ap=read(),bp=read(),as=read(),bs=read();//买入价,卖出价,买入最大股,卖出最大股
for(int j=0;j<=as;j++) f[i][j]=max(f[i-1][j],-ap*j);
for(int j=as+1;j<=MaxP;j++) f[i][j]=f[i-1][j];
int p=i-w-1;
if(p<0) continue;
Q.clear();
for(int j=0;j<=MaxP;j++){
while(!Q.empty()&&Q.front().cnt+as<j) Q.pop_front();
while(!Q.empty()&&Q.back().val<=f[p][j]+ap*j) Q.pop_back();
Q.push_back(node(j,f[p][j]+ap*j));
f[i][j]=max(f[i][j],Q.front().val-ap*j);
}
Q.clear();
for(int j=MaxP;j>=0;j--){
while(!Q.empty()&&j+bs<Q.front().cnt) Q.pop_front();
while(!Q.empty()&&Q.back().val<=f[p][j]+bp*j) Q.pop_back();
Q.push_back(node(j,f[p][j]+bp*j));
f[i][j]=max(f[i][j],Q.front().val-bp*j);
}
ans=max(ans,f[i][0]);
}
printf("%d\n",ans);
return 0;
}
题外话
一道好题