DP专练4:[SCOI 2010]股票交易(单调队列优化dp)

昨天晚上,初见它时,月黑风高,一个电脑,一支笔,一个人
在这里插入图片描述
今天秋高气爽,再一瞥,回眸间
在这里插入图片描述

我又来了,honey

题目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题解

首先这种 i i i 天与前面 j j j 天有关联,而且让你求最后一天的极值
我们一定会联想到 D P DP DP


先搞定 d p dp dp 定义?

第一维毋庸置疑就是表示 i i i 天,接着思考一下第二维 j j j
1)如果表示 i i i 天买了或者卖了 j j j 股票,就遇到了一个问题, i − W − 1 i-W-1 iW1 天的时候股票数又是多少呢?
难道我要再去用一重循环枚举吗?显然不现实。

2)如果表示 i i i 天手上还拥有着 j j j 股票( i i i 天已经操作完成了)
那么 ± k ±k ±k 就是 i − W − 1 i-W-1 iW1 的股票数了

综上 d p [ i ] [ j ] : i dp[i][j]:i dp[i][j]:i 天手上的股票数为 j j j 时的最大收益。

最后输出 d p [ n ] [ 0 ] dp[n][0] dp[n][0] 就可以了, n n n 天的时候手上没有股票数肯定是最优解了,不然你手上捏着股票又买卖不了,拿来擦屁股吗??
在这里插入图片描述


找到转移方程式?

  • 这一天闲得慌,不买也不卖

    d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ( 0 ≤ j ≤ M a x P ) dp[i][j]=dp[i-1][j] (0≤j≤MaxP) dp[i][j]=dp[i1][j](0jMaxP)

  • 这一天才开始玩股票,只能去购进股票

    d p [ i ] [ j ] = − j ∗ a p i ( j − a s i ≤ k < j ) dp[i][j]=-j*ap_i(j-as_i≤k<j) dp[i][j]=japi(jasik<j)

  • 自由权比较大,手上有点资本,也要购进

    d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] − ( j − k ) ∗ a p i ( j − a s i ≤ k < j ) dp[i][j]=dp[i-W-1][k]-(j-k)*ap_i(j-as_i≤k<j) dp[i][j]=dp[iW1][k](jk)api(jasik<j)

  • 该开始赚钱钱了,卖一点股票出去

    d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] + ( k − j ) ∗ b p i ( j < k ≤ j + b s i ) dp[i][j]=dp[i-W-1][k]+(k-j)*bp_i(j<k≤j+bs_i) dp[i][j]=dp[iW1][k]+(kj)bpi(j<kj+bsi)

在这四种情况下,取一个 m a x max max 即可。

那么为什么i-W-1就是当前最优解呢?

因为我们是一天一天递推过去的,当前最优解会被传递过去,

这也是我们DP需要完成的


分析这个 D P DP DP 的时间复杂度, O ( n 3 ) O(n^3) O(n3)

所以必须对 k k k 优化

我们就去拆一拆 case3 case4 D P DP DP
在这里插入图片描述
d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] − ( j − k ) ∗ a p i ( j − a s i ≤ k < j ) dp[i][j]=dp[i-W-1][k]-(j-k)*ap_i(j-as_i≤k<j) dp[i][j]=dp[iW1][k](jk)api(jasik<j) ⇓ \Downarrow d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] + k ∗ a p i − j ∗ a p i ( j − a s i ≤ k < j ) dp[i][j]=dp[i-W-1][k]+k*ap_i-j*ap_i(j-as_i≤k<j) dp[i][j]=dp[iW1][k]+kapijapi(jasik<j)
我们会发现 j ∗ a p i j*ap_i japi 是固定不变的。

也就是说真正影响 d p [ i ] [ j ] dp[i][j] dp[i][j] 的是 d p [ i − W − 1 ] [ k ] + k ∗ a p i dp[i-W-1][k]+k*ap_i dp[iW1][k]+kapi

而且 d p [ i − W − 1 ] [ k ] + k ∗ a p i dp[i-W-1][k]+k*ap_i dp[iW1][k]+kapi 越大越好。

发现这个方程的实质是 k ∈ [ j − a s i , j ) k∈[j-as_i,j) k[jasi,j) 这个范围内取得的且只和 k k k 有关。

d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] + ( k − j ) ∗ b p i ( j < k ≤ j + b s i ) dp[i][j]=dp[i-W-1][k]+(k-j)*bp_i(j<k≤j+bs_i) dp[i][j]=dp[iW1][k]+(kj)bpi(j<kj+bsi) ⇓ \Downarrow d p [ i ] [ j ] = d p [ i − W − 1 ] [ k ] + k ∗ b p i − j ∗ b p i ( j < k ≤ j + b s i ) dp[i][j]=dp[i-W-1][k]+k*bp_i-j*bp_i(j<k≤j+bs_i) dp[i][j]=dp[iW1][k]+kbpijbpi(j<kj+bsi)

我们会发现 j ∗ b p i j*bp_i jbpi 是固定不变的。

也就是说真正影响 d p [ i ] [ j ] dp[i][j] dp[i][j] 的是 d p [ i − W − 1 ] [ k ] + k ∗ b p i dp[i-W-1][k]+k*bp_i dp[iW1][k]+kbpi

而且 d p [ i − W − 1 ] [ k ] + k ∗ b p i dp[i-W-1][k]+k*bp_i dp[iW1][k]+kbpi 越大越好。

发现这个方程的实质是 k ∈ ( j , j + b s i ] k∈(j,j+bs_i] k(j,j+bsi] 这个范围内取得的且只和k有关

结合以上两种情况:就会联想到我们的单调队列了。

维护 k k k 的范围而且从大到小,每次取队头 h e a d head head 进行更值

这样就转化成了 O ( n 2 ) O(n^2) O(n2)


最后注意一下循环顺序

买股票就从小到大 0 ∼ M a x P 0\sim MaxP 0MaxP

卖股票就从大到小 M a x P ∼ 0 MaxP\sim 0 MaxP0

代码实现

#include <cstdio>
#include <iostream>
using namespace std;
#define MAXN 2005
int T, MaxP, W;
int ap, bp, as, bs;
int head, tail;
int deq[MAXN];
int dp[MAXN][MAXN];
int main() {
	scanf ( "%d %d %d", &T, &MaxP, &W );
	++ W;
	dp[0][0] = -MAXN * MAXN;
	for ( int i = 1;i <= MaxP;i ++ )
		dp[0][i] = dp[0][i - 1];
	for ( int i = 1;i <= T;i ++ ) {
		scanf ( "%d %d %d %d", &ap, &bp, &as, &bs );
		
		for ( int j = 0;j <= MaxP;j ++ )
			dp[i][j] = dp[i - 1][j];
			
		for ( int j = 0;j <= as;j ++ )
			dp[i][j] = max ( dp[i][j], -j * ap );
			
		if ( i > W ) {
			head = 1, tail = 0;
			for ( int j = 0;j <= MaxP;j ++ ) {
				while ( head <= tail && deq[head] < j - as )
					head ++;
				while ( head <= tail && deq[tail] * ap + dp[i - W][deq[tail]] <= j * ap + dp[i - W][j] )
					-- tail;
				deq[++ tail] = j;
				dp[i][j] = max ( dp[i][j], dp[i - W][deq[head]] + deq[head] * ap - j * ap );
			}
			
			head = 1, tail = 0;
			for ( int j = MaxP;j >= 0;j -- ) {
				while ( head <= tail && deq[head] > j + bs )
					head ++;
				while ( head <= tail && deq[tail] * bp + dp[i - W][deq[tail]] <= j * bp + dp[i - W][j] )
					tail --;
				deq[++ tail] = j;
				dp[i][j] = max ( dp[i][j], dp[i - W][deq[head]] + deq[head] * bp - j * bp );
			}
			
		}
	}
	printf ( "%d", dp[T][0] );
	return 0;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值