题目:题目链接
分析:
这道题可以使用dp来解。我们令一个dp[i][j]表示第i天,手中仍持有j支股票时能获得的最大利润。
对于状态转移,有以下几种情况:
1.没有股票地买入:只有支出,盈利dp[i][j]=-ap[i]*j,j为买入的数量,。
2.不买也不卖:手中拥有的股票数量j不变,直接继承上一天的,dp[i][j]=max(dp[i][j],dp[i-1][j])。
3.已持有股票的基础上卖出:因为有交易冷却,两次相邻的交易要间隔w天,故上一次的交易是在i-w-1天时发生。假设在i-w-1天时,手上持有股票数为k,那么dp[i][j]的状态相对于dp[i-w-1][k]购入股票数为(j-k),因此,其中k的取值范围满足
且
,因此dp[i][j]的取值便是dp[i-w-1][k]-(j-k)*ap[i]这个式子在
这一范围内的最大值。
4.卖出:显然要卖出之前手上必须有股票。我们同样假设在i-w-1天时手上有k支股票,那么我们卖出的股票数为(k-j),,此时k的取值范围为
,dp[i][j]的值同样是这个式子在这个区间中的最大值。
由于我们要找的是一个区间内的表达式的最大值,我们可以考虑使用单调栈/单调队列优化一下找最大值的过程。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,maxp,w;
int dp[2005][2005];
int ap[2005],bp[2005],as[2005],bs[2005];
deque<int> q;
signed main() {
cin>>t>>maxp>>w;
for (int i=0;i<2005;i++)
for (int j=0;j<2005;j++)
dp[i][j]=INT_MIN;//也可以使用memset(dp,128,sizeof(dp));赋出来也是负的inf,若是127则是正的
for (int i=1;i<=t;i++)
cin>>ap[i]>>bp[i]>>as[i]>>bs[i];
for (int i=1;i<=t;i++) {
for (int j=0;j<=as[i];j++)
dp[i][j]=-ap[i]*j;//直接买
for (int j=0;j<=maxp;j++)
dp[i][j]=max(dp[i][j],dp[i-1][j]);//不买不卖
if (i-w-1<0)continue;
//买入
q.clear();//在开始之前一定要清空队列,否则会出错
for (int j=0;j<=maxp;j++) {
while (!q.empty()&&q.front()<j-as[i])
q.pop_front();
while (!q.empty()&&dp[i-w-1][q.back()]+q.back()*ap[i]<=dp[i-w-1][j]+j*ap[i])
q.pop_back();
q.push_back(j);
if (!q.empty())//单调队列使最大取值在队列头部
dp[i][j]=max(dp[i][j],dp[i-w-1][q.front()]+q.front()*ap[i]-j*ap[i]);
}
//卖出
q.clear();
for (int j=maxp;j>=0;j--) {
while (!q.empty()&&q.front()>j+bs[i])
q.pop_front();
while (!q.empty()&&dp[i-w-1][q.back()]+q.back()*bp[i]<=dp[i-1-w][j]+j*bp[i])
q.pop_back();
q.push_back(j);
if (!q.empty())//单调队列使最大取值在队列头部
dp[i][j]=max(dp[i][j],dp[i-w-1][q.front()]+q.front()*bp[i]-j*bp[i]);
}
}
int MAX=LONG_LONG_MIN;
for (int i=0;i<=maxp;i++)//遍历第t天手中有股的所有情况,找最大值
MAX=max(MAX,dp[t][i]);
cout<<MAX<<endl;
}