题目链接:点击打开链接
题目大意:
就是有一个人知道每一天的股票的购入和卖出的价钱(注意只有一种股票),并且每天能够购入和卖出的股票的数量也是有限制的。第 i 天买的股票也只能第 i+w+1 天卖出。但是无论何时,他手中持有的股票的数量不能超过m、问他本金无限的情况下最多盈利多少。
题意解析:
很容易分析出来是一道dp题目 每一天的状态可以由他 i-w-1 天前的状态推出来,再枚举他今天 买入 或 卖出 多少股票。dp转移方程就不说了。来说一下这道题需要用到的一个技巧,单调队列优化 dp 。
单调队列优化dp,刚开始着实理解不来 。看大佬代码一头雾水。遂决定先去写个超时代码。果然超时代码写出来之后对比着就比较容易理解了。
单调队列dp 适用于 dp[i]=max/min (f[k]) + g[i] (k<i && g[i]是与k无关的变量)(借用一下大佬的)这种情况。
接下来谈我自己对单调队列的理解,当然不一定正确,自己瞎猜的。因为 k 是与 i 有关的,理论上来说 我们若想得出正确答案,最暴力的方法是枚举完 i 再枚举 k 。这样的话就是一个双重循环。但是单调队列可以帮助我们一重循环解决。就是直接将 i 枚举一遍。枚举 i 的同时也同时将 k 作为 i 枚举。但是因为 k 和 i 之间有某种关系 。所以我们需要将枚举出来的 i 先保存起来,维护一个单调队列。那么对于你枚举到后面的 i 的时候,这个单调队列里面的头结点对应的 i 一定是最优解并且他将作为 k 参加dp转移方程。这样就可以一重循环完美的解决问题了。不得不说真的神奇。
下面贴两段代码,一个超时,一个是优化过的。对比着看的话应该更容易理解。
/* 优化版 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int INF = 1e9 + 5;
int n,m,w,ans;
struct node
{
int idx;
int kk;
};
int ap[2005],bp[2005];
int as[2005],bs[2005];
int dp[2005][2005];
node q[2005]; //单调队列
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<=n;i++)
scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
dp[i][j]=-INF;
for(int i=1;i<=w+1;i++) //预处理前w+1天的情况
for(int j=0;j<=min(as[i],m);j++)
dp[i][j]=-j*ap[i];
dp[0][0]=0;
int head=0,tail=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
dp[i][j]=max(dp[i][j],dp[i-1][j]); //不买不卖的情况
if(i<=w+1)
continue ;
int g=i-w-1,gg;
head=0;tail=0;
for(int j=0;j<=m;j++) //买入情况 枚举 j
{
gg=dp[g][j]+j*ap[i];
while(head<tail&&q[tail-1].kk<gg) // 将j不断加入单调队列并维护
tail--;
q[tail].idx=j;q[tail++].kk=gg;
while(head<tail&&q[head].idx+as[i]<j) //将前面部分不符合要求的点去掉
head++;
dp[i][j]=max(dp[i][j],q[head].kk-j*ap[i]); //当前最优状态转移方程
}
head=0;tail=0;
for(int j=m;j>=0;j--) //卖出 同上
{
gg=dp[g][j]+j*bp[i];
while(head<tail&&q[tail-1].kk<gg)
tail--;
q[tail].idx=j;q[tail++].kk=gg;
while(head<tail&&q[head].idx-bs[i]>j)
head++;
dp[i][j]=max(dp[i][j],q[head].kk-j*bp[i]);
}
}
ans=0;
for(int i=0;i<=m;i++)
ans=max(ans,dp[n][i]);
printf("%d\n",ans);
}
}
/* 未优化版 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int INF = 1e9 + 5;
int n,m,w,ans;
int ap[2005],bp[2005];
int as[2005],bs[2005];
int dp[2005][2005];
int q[2005];
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<=n;i++)
scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
dp[i][j]=-INF;
for(int i=1;i<=w+1;i++)
{
for(int j=0;j<=min(as[i],m);j++)
dp[i][j]=-j*ap[i];
}
dp[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
dp[i][j]=max(dp[i][j],dp[i-1][j]);
if(i<=w+1)
continue ;
int g=i-w-1;
for(int j=m;j>=0;j--)
for(int k=0;k+as[i]<j;k++) //重点看k的范围 单调队列要用到
dp[i][j]=max(dp[g][k]-(j-k)*ap[i],dp[i][j]);
for(int j=0;j<=m;j++)
for(int k=min(j+bs[i],m);k>j;k--) //同上
dp[i][j]=max(dp[g][k]+(k-j)*bp[i],dp[i][j]);
}
ans=0;
for(int i=0;i<=m;i++)
ans=max(ans,dp[n][i]);
printf("%d\n",ans);
}
}