[HDU3401] [HDUMonthly1005] Trade [单调队列][dp]

本文探讨了在特定约束条件下,通过动态规划与单调队列优化,实现股票买卖策略以最大化最终收益的方法。考虑到买入卖出操作的无后效性及最优子结构特性,采用二维状态转移方程,将问题转化为求解最大收益的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[ L i n k \frak{Link} Link]


从初始状态经过买入卖出操作达到末状态,要使末状态值最大。
很明显满足无后效性和最优子结构。考虑用动态规划来解决这个问题。

-首先思考状态。
 很明显是 Θ ( n 2 ) \frak{\Theta(n^2)} Θ(n2)
  考虑第一维,稍有常识的人直觉来说是枚举天数
  第二维不会是钱数,那应该和“股票的数量”有关——
  股票只有这一种,直接记录当前持有多少支该股。
状态: f [ i ] [ j ] \frak{f[i][j]} f[i][j]表示第 i \frak{i} i天手里有 j \frak{j} j支股票的最大收益

-然后思考转移。
 当然, f [ i ] [ j ] \frak{f[i][j]} f[i][j]的转移只需要考虑 f [ 1 ∼ ( i − W ) ] [ 1 ∼ M a x P ] \frak{f[1\thicksim(i-\mathcal{W})][1\thicksim \mathcal{MaxP}]} f[1(iW)][1MaxP]
 一开始一定要先买入,这是初状态,直接赋值。转移分三种:
 不买入&不卖出的情况: f [ i ] [ j ] = m a x { f [ i ] [ j ] , f [ i − 1 ] [ j ] } \frak{f[i][j]=max\{f[i][j],f[i-1][j]\}} f[i][j]=max{f[i][j],f[i1][j]}
 买入: f [ i ] [ j ] = m a x { f [ 1 ∼ ( i − W − 1 ) ] [ k ] − ( j − k ) × A P i } \frak{f[i][j]=max\{f[1\thicksim(i-\mathcal{W}-1)][\mathcal{k}]-(j-\mathcal{k})×\mathcal{AP_\frak{i}}\}} f[i][j]=max{f[1(iW1)][k](jk)×APi}
 卖出: f [ i ] [ j ] = m a x { f [ 1 ∼ ( i − W − 1 ) ] [ k ] + ( k − j ) × B P i } \frak{f[i][j]=max\{f[1\thicksim(i-\mathcal{W}-1)][\mathcal{k}]+(\mathcal{k}-j)×\mathcal{BP_\frak{i}}\}} f[i][j]=max{f[1(iW1)][k]+(kj)×BPi}
 并且由于有了不买入&不卖出的转移,有:
转移方程:
 初状态(第一次买入): f [ i ] [ j ] = − j A P i \frak{f[i][j]=-j\mathcal{AP_\frak{i}}} f[i][j]=jAPi
 不买入/卖出: f [ i ] [ j ] = m a x { f [ i ] [ j ] , f [ i − 1 ] [ j ] } \frak{f[i][j]=max\{f[i][j],f[i-1][j]\}} f[i][j]=max{f[i][j],f[i1][j]}
 买入: f [ i ] [ j ] = m a x { f [ i − W − 1 ] [ k ] − ( j − k ) × A P i } \frak{f[i][j]=max\{f[i-\mathcal{W}-1][\mathcal{k}]-(j-\mathcal{k})×\mathcal{AP_\frak{i}}\}} f[i][j]=max{f[iW1][k](jk)×APi}
 卖出: f [ i ] [ j ] = m a x { f [ i − W − 1 ] [ k ] − ( j − k ) × B P i } \frak{f[i][j]=max\{f[i-\mathcal{W}-1][\mathcal{k}]-(j-\mathcal{k})×\mathcal{BP_\frak{i}}\}} f[i][j]=max{f[iW1][k](jk)×BPi}


当然这不能作为最终的式子:因为这个式子里面引入了 k \frak{k} k,时间上增加了枚举 δ \frak{\delta} δ的一维。
这样的话复杂度是 Θ ( n 3 ) \frak{\Theta(n^3)} Θ(n3)的,当然不行。

-如何优化?
 两个转移方程形如: f ( i , j ) = o p t { f ( i , k ) + a ( j , k ) } \frak{f(i,j)=opt\{f(i,\mathcal{k})+a(j,\mathcal{k})\}} f(i,j)=opt{f(i,k)+a(j,k)}
 这不好优化。但是 a ( j , k ) \frak{a(j,\mathcal{k})} a(j,k)可以拆成两部分,一部分只与 ( i , j ) \frak{(i,j)} (i,j)有关,一部分只与 i , k \frak{i,\mathcal{k}} i,k有关。
 这样式子就变成了 f ( i , j ) = o p t { f ( i , k ) + a ( i , j ) } \frak{f(i,j)=opt\{f(i,\mathcal{k})+a(i,j)\}} f(i,j)=opt{f(i,k)+a(i,j)}
 注意到单调队列的基本形式是 f ′ ( i ′ ) = o p t { f ′ ( j ′ ) + a ′ ( i ′ ) } \frak{f'(i')=opt\{f'(j')+a'(i')\}} f(i)=opt{f(j)+a(i)}
 现在推广到二维,可以把 ( i , j ) \frak{(i,j)} (i,j)看作 i ′ \frak{i'} i,把 ( i , k ) \frak{(i,\mathcal{k})} (i,k)看作 j ′ \frak{j'} j。方程符合单调队列形式,可以优化。
 最终复杂度 Θ ( n 2 ) \frak{\Theta(n^2)} Θ(n2)

-构造代码
 ·读入
 ·循环枚举天数 i \frak{i} i
  ·清空单调队列
  ·循环枚举股票数 j \frak{j} j
   ·初状态(第一次买入)
   ·不买入也不卖出
   ·买入
    ·更新 f [ i ] [ j ] \frak{f[i][j]} f[i][j]
    ·更新单调队列
     ·删除单调队列中过期部分( j − A S i > k \frak{j-\mathcal{AS_\frak{i}}>\mathcal{k}} jASi>k
     ·在单调队列中加入 f [ i − w − 1 ] [ j ] + j A P i \frak{f[i-w-1][j]+j\mathcal{AP_i}} f[iw1][j]+jAPi
   ·卖出
    ·更新 f [ i ] [ j ] \frak{f[i][j]} f[i][j]
    ·更新单调队列
      ·删除单调队列中过期部分( j + B S i &lt; k \frak{j+\mathcal{BS_\frak{i}}&lt;\mathcal{k}} j+BSi<k
      ·在单调队列中加入$\frak{f[i-w-1][j]+j\mathcal{BP_i}}
      
步骤省略对多组数据的处理


#Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<queue>
#include<cctype>
using namespace std;
int T,N,P,W,AP,BP,AS,BS;
int F[2005][2005]={},q[2005]={},ql=1,qr=0,Ans=0;
int main()
{
	scanf("%d",&T);
	while(T--) 
	{
		memset(F,0xcf,sizeof(F));
		Ans=0;
		scanf("%d%d%d",&N,&P,&W);
		for(int i=1;i<=N;++i) 
		{
			scanf("%d%d%d%d",&AP,&BP,&AS,&BS);
			for(int j=0;j<=AS;++j)F[i][j]=-j*AP;
			for(int j=0;j<=P;++j)F[i][j]=max(F[i][j],F[i-1][j]);
			if(i<=W)continue;
			
			ql=1, qr=0;
			for(int j=0;j<=P;++j) 
			{
				while(ql<=qr&&j-AS>q[ql])++ql;
				while(ql<=qr&&F[i-W-1][q[qr]]+q[qr]*AP<=F[i-W-1][j]+j*AP)--qr;
				q[++qr]=j;
				if(ql<=qr)F[i][j]=max(F[i][j],F[i-W-1][q[ql]]+(q[ql]-j)*AP);
			}
			
			ql=1, qr=0;
			for(int j=P;j>=0;--j) 
			{
				while(ql<=qr&&j+BS<q[ql])++ql;
				while(ql<=qr&&F[i-W-1][q[qr]]+q[qr]*BP<=F[i-W-1][j]+j*BP)--qr;
				q[++qr]=j;
				if(ql<=qr)F[i][j]=max(F[i][j],F[i-W-1][q[ql]]+(q[ql]-j)*BP);
			}
		}
		for(int i=0;i<=P;++i)Ans=max(Ans,F[N][i]);
		printf("%d\n",Ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值