题目链接:点击打开链接
题目大意:
就是有一个人知道每一天的股票的购入和卖出的价钱(注意只有一种股票),并且每天能够购入和卖出的股票的数量也是有限制的。第 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); } }
