题目类型:DP+单调队列优化
传送门:>Here<
题意:共有\(T\)天,每天可以选择买股票或卖股票。如果有一天买了或卖了则称这一天是进行交易的。任何进行交易的两天之间必须间隔\(W\)天。第\(i\)天最多买进\(as\)股,卖出\(bs[i]\)股。第\(i\)天的进价为\(ap[i]\),卖出价是\(bp[i]\)。且规定任何一天都不可以拥有超过\(maxP\)股。在如上的条件限制下,要求\(T\)天后最多赚多少
解题思路
题目就烦的要命
读了几遍后,自然而然想到\(dp[i][j]\)表示前\(i\)天的最大收益,且第\(i\)天拥有\(j\)股。这样转移就非常方便了,无非分为那么几种情况
不买不卖。选择继承,则有\[dp[i][j]=dp[i-1][j]\]
买进。这里的买进要分两种情况来讨论,在前\(W+1\)天里,只能买进而不能卖出,并且只有一天能够交易。因此买进全都是亏钱的。这个部分可以作为初始化:\[dp[i][j]=-j*AP[i]\]要么就是在后来,可以在已有的基础上选择买进。假设是在拥有\(p\)股的基础上的,则\[dp[i][k]=Max\{dp[i-W-1][p]-(k-p)*AP[i]\}\]注意我们并没有枚举从哪一天转移而来。为什么?因为对于所有\(i<j\),一定满足\(dp[i][x] \leq dp[j][x]\)。因为这之间我们可以选择不买,也就是继承。只要是继承就不可能变小,因此没有必要枚举从哪一天转移来了
卖出。卖出就不可能在前\(W+1\)天了,因此只需要考虑有基础的情况。设原有\(q\)股,则\[dp[i][k]=Max\{dp[i-W-1][q]+(q-k)*BP[i]\}\]
确定了基本方程以后,我们还需要知道各个循环变量的范围。\(i,k\)的范围都显然,关键是\(p,q\)。
先来考虑\(p\)。既然是选择买进,所以一定\(p<k\)。并且那一天最多买进\(AS[i]\),所以有不等式\(k-p \leq AS[i]\)成立,也就是\(p \geq k-AS[i]\)因此\[k-AS[i] \leq p < k\]
那么\(q\)也一样。易得\(q>k\),并且\(q-k \leq BS[i]\)。因此\[k<q \leq BS[i]+k\]
至此,复杂度\(O(n^3)\),能拿到\(70\)分
考虑优化。我们就买进的方程进行讨论,观察方程我们发现,这个方程的实质就是\(p\)在范围\([k-AS[i],k)\)中取\(dp[i-W-1][p]-(k-p)*AP[i]\)的最大值。很容易让我们想到滑动窗口,对吗?因此我们提取出只与\(p\)有关的项,将方程化为\(dp[i-W-1][p]+p*AP[i] - k*AP[i]\)。并且我们发现滑动的正好是扫描\(k\)的那一层,也就是\(i\)是确定的。换句话说\(p\)是\(k\)的过去。因此维护滚动最大值即可!
对于\(q\)也一样,但是\(q\)是需要由大的往小的转移,因此应当倒着扫。
Code
边界条件还是非常难的,还要多加谨慎
/*By DennyQi 2018.8.21*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define r read()
#define Max(a,b) (((a)>(b)) ? (a) : (b))
#define Min(a,b) (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int MAXN = 2010;
const int INF = 1061109567;
inline int read(){
int x = 0; int w = 1; register int c = getchar();
while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
if(c == '-') w = -1, c = getchar();
while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar(); return x * w;
}
int T,maxP,W,P,Q;
int AP[MAXN],BP[MAXN],AS[MAXN],BS[MAXN];
int dp[MAXN][MAXN],q[2][MAXN],h[2],t[2];
int main(){
T = r, maxP = r, W = r;
memset(dp, -0x3f, sizeof dp);
for(int i = 1; i <= T; ++i){
AP[i] = r, BP[i] = r;
AS[i] = r, BS[i] = r;
}
dp[0][0] = 0;
for(int i = 1; i <= T; ++i){
for(int j = 0; j <= AS[i]; ++j) dp[i][j] = -j * (AP[i]);
for(int k = 0; k <= maxP; ++k) dp[i][k] = Max(dp[i][k], dp[i-1][k]);
h[0] = h[1] = 1, t[0] = t[1] = 0;
if(i-W-1 < 0) continue;
for(int k = 0; k <= maxP; ++k){
while(h[0] <= t[0] && q[0][h[0]] < k - AS[i]) ++h[0];
while(h[0] <= t[0] && dp[i-W-1][q[0][t[0]]] + q[0][t[0]]*AP[i] <= dp[i-W-1][k]+k*AP[i]) --t[0];
q[0][++t[0]] = k;
if(h[0] <= t[0]) dp[i][k] = Max(dp[i][k], dp[i-W-1][q[0][h[0]]] - (k-q[0][h[0]]) * AP[i]);
}
for(int k = maxP; k >= 0; --k){
while(h[1] <= t[1] && q[1][h[1]] > BS[i]+k) ++h[1];
while(h[1] <= t[1] && dp[i-W-1][q[1][t[1]]] + q[1][t[1]]*BP[i] <= dp[i-W-1][k]+k*BP[i]) --t[1];
q[1][++t[1]] = k;
if(h[1] <= t[1]) dp[i][k] = Max(dp[i][k], dp[i-W-1][q[1][h[1]]] + (q[1][h[1]]-k) * BP[i]);
}
}
int ans = 0;
for(int i = 0; i <= maxP; ++i){
ans = Max(ans, dp[T][i]);
}
printf("%d", ans);
return 0;
}