题目描述:
最近 Lxhgww 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。
通过一段时间的观察,Lxhgww 预测到了未来 T 天内某只股票的走势,第 i 天的股票买入价为每股 APi ,第 i 天的股票卖出价为每股 BPi 数据保证对于每个i,都有 APi ≥ BPi ),但是每天不能无限制地交易,于是股票交易所规定第 i 天的一次买入至多只能购买 ASi 股,一次卖出至多只能卖出 BSi 股。
另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 W 天,也就是说如果在第 i 天发生了交易,那么从第 i+1 天到第 i+W 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 MaxP 。
在第 1 天之前,Lxhgww 手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,T 天以后,Lxhgww 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?
输入:
输入数据第一行包括3个整数,分别是 T,MaxP,W 。
接下来 T 行,第 i 行代表第 i-1 天的股票走势,每行 4 个整数,分别表示 APi , BPi , ASi , BSi 。
输出:
输出数据为一行,包括 1 个数字,表示 Lxhgww 能赚到的最多的钱数。
样例输入:
5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1
样例输出:
3
【数据范围】
对于 30% 的数据,0≤W<T≤50;1≤MaxP≤50 ;
对于 50% 的数据,0≤W<T≤2000;1≤MaxP≤50 ;
对于 100% 的数据,0≤W<T≤2000;1≤MaxP≤2000 ;
对于所有的数据,1≤BPi≤APi≤1000;1≤ASi,BSi≤MaxP 。
题目分析:
读完题,我们可以根据题意将每天的情况分为3类,分别是:
①、及不买也不卖
②、买进一些股票
③、卖出一些股票
因为求解最优解,我们可以定义 dp[ i ][ j ]表示第 i 天,有 j 只股票的最优解,同样分三种情况来讨论转移方程(分别对应上面三种情况):
①、dp[ i ][ j ] = dp[ i - 1 ][ j ]
②、dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ r ][ k ] - AS[ i ] * ( j-k ) )
③、dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ r ][ k ] +BS[ i ]* ( k -j ) )
这个转移 ,我们每次要枚举 i , j , r , k 才能转移,考虑降维度;
首先我们想到,因为 r 必须满足 r<=i-w-1,对于i-w-1之前都可以通过方程①转移到 i-w-1天,所以我们就不需要枚举 r 转移了,每次记录一个前缀最大值即可;
然后我们将方程②拆开:dp[ i ][ j ] =max(dp[ i ][ j] , dp[ i-w-1 ][ k ]+k*AS[i]-AS[i] * j,我们发现 dp[ i-w-1 ][ k ]+k*AS[i] 是一个与 j 完全无关的量,那么我们就可以每次计算的时候将 dp[ i-w-1 ][ k ]+k*AS[i] ,加入一个单调队列里维护对首,对于队尾小于它的就可以直接弹出队列,因为那些数答案既没有它优,而且对后面状态的影响更小,然后每次更新 dp[i][j]是就可以直接取出对首更新。
这样转移变成了O(1)的,总时间复杂度为O( n2 ),可以解决这道题;
实际上单调队列优化DP就是像本题中找到一个与‘ j ’ (不同的题不同) 无关的量用单调队列维护,每次取出对首即为最优解
#include
using namespace std;
#define inf 0xfffffff
#define min(a,b) a
#define max(a,b) a>b?a:b
struct node
{
int pos;
int f;
};
int AP[2001],BP[2001],AS[2001],BS[2001];
int dp[2001][2001];
node q[2001];
int head,tail;
int n,MaxP,W;
int main()
{
int i,j,cas,nowf;
scanf("%d%d%d",&n,&MaxP,&W);
for(i=1;i<=n;i++)
{
scanf("%d%d%d%d",&AP[i],&BP[i],&AS[i],&BS[i]);
}
for(i=0;i<=n;i++)
for(j=0;j<=MaxP;j++)
dp[i][j]=-inf;
for(i=1;i<=W+1;i++)
{
for(j=0;j<=(min(MaxP,AS[i]));j++)
dp[i][j]=-AP[i]*j;
}
dp[0][0]=0;
for(i=1;i<=n;i++)
{
for(j=0;j<=MaxP;j++)
dp[i][j]=max(dp[i][j],dp[i-1][j]);
if(i<=W+1)
continue;
int pre=i-W-1; //队列里保存的是第i-W-1天的信息
head=tail=0;
for(j=0;j<=MaxP;j++) //因为是买,所以肯定越来越多,求DP[i][j]的时候,之前的0~~j-1的信息就存在在队列了
{
nowf=dp[pre][j]+j*AP[i];
while(headq[tail-1] .fq[tail].f=nowf;q[tail++].pos=j;
while(headq[head].pos+AS[i]q[head].f-j*AP[i]);
}
head=tail=0;
for(j=MaxP;j>=0;j--) //因为是卖,道理同上
{
nowf=dp[pre][j]+j*BP[i];
while(headq[tail-1].fq[tail].f=nowf;q[tail++].pos=j;
while(headq[head].pos-BS[i]>j)
head++;
dp[i][j]=max(dp[i][j],q[head].f-j*BP[i]);
}
}
int ans=0;
for(i=0;i<=MaxP;i++)
ans=max(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}