• [SCOI2010] 股票交易


    题目类型: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]=-jAP[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;
    }
    
  • 相关阅读:
    atitit.面向过程的编程语言异常处理 c语言 asp vbs 的try catch 实现
    Atitit.ide代码块折叠插件 eclipse
    Atitit.注重细节还是关注长远??长远优先
    atitit.安全的签名摘要 算法attilax总结
    Atitit.各种 数据类型 ( 树形结构,表形数据 ) 的结构与存储数据库 attilax 总结
    Atitit. 委托的本质 c#.net java php的比较
    Atitit.注册跟个登录功能的实现attilax总结obo
    atitit..sql update语法的词法分析,与语法ast构建
    atitit.GUI图片非规则按钮跟动态图片切换的实现模式总结java .net c# c++ web html js
    Atitit.数据库事务隔离级别 attilax 总结
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9509526.html
Copyright © 2020-2023  润新知