当前位置: 代码迷 >> 综合 >> bzoj1855: [Scoi2010]股票交易
  详细解决方案

bzoj1855: [Scoi2010]股票交易

热度:23   发布时间:2023-10-29 11:08:07.0

Description

最近lxhgww又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。 通过一段时间的观察,lxhgww预测到了未来T天内某只股票的走势,第i天的股票买入价为每股APi,第i天的股票卖出价为每股BPi(数据保证对于每个i,都有APi>=BPi),但是每天不能无限制地交易,于是股票交易所规定第i天的一次买入至多只能购买ASi股,一次卖出至多只能卖出BSi股。 另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔W天,也就是说如果在第i天发生了交易,那么从第i+1天到第i+W天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过MaxP。 在第1天之前,lxhgww手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,T天以后,lxhgww想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?
Input

输入数据第一行包括3个整数,分别是T,MaxP,W。 接下来T行,第i行代表第i-1天的股票走势,每行4个整数,分别表示APi,BPi,ASi,BSi。
Output

输出数据为一行,包括1个数字,表示lxhgww能赚到的最多的钱数。
Sample Input

5 2 0

2 1 1 1

2 1 1 1

3 2 1 1

4 3 1 1

5 4 1 1

Sample Output

3

HINT

对于30%的数据,0 < =W 对于50%的数据,0 < =W 对于100%的数据,0 < =W
对于所有的数据,1 < =BPi < =APi < =1000,1 < =ASi,BSi < =MaxP

题解

这题就直接DP一下就好了
f[i][j]表示前i填手里有j个股票的最优代价
然后每一次就枚举上一次有多少就好了
裸DP是这样子的(我拆了式子)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int N=2505;
const int MAX=1<<30;
int n,P,w;//天数 最大股票数 至少隔w天 
struct qq
{int a1,b1;//买卖钱数int a2,b2;//买卖数量 
}s[N];
int f[N][N];//最后一次交易在前i天,然后手上有j支股票的最大价值 
int main()
{scanf("%d%d%d",&n,&P,&w);for (int u=1;u<=n;u++)scanf("%d%d%d%d",&s[u].a1,&s[u].b1,&s[u].a2,&s[u].b2);for (int u=0;u<=n;u++)for (int i=0;i<=P;i++)f[u][i]=-MAX;f[0][0]=0;for (int u=1;u<=n;u++)//最后一天在哪一天交易{for (int i=0;i<=P;i++)//手里有多少股票 {int a=-MAX;for (int j=max(0,i-s[u].a2);j<i;j++)//上一次有多少 a=max(a,f[max(u-w-1,0)][j]+j*s[u].a1);a-=(s[u].a1*i);f[u][i]=max(f[u][i],a);a=-MAX;for (int j=i+1;j<=min(i+s[u].b2,P);j++)//上一次有多少 买的a=max(a,f[max(u-w-1,0)][j]+s[u].b1*j); a-=(s[u].b1*i);f[u][i]=max(f[u][i],a);f[u][i]=max(f[u][i],f[u-1][i]);}}int ans=0;for (int u=0;u<=P;u++) ans=max(ans,f[n][u]);printf("%d\n",ans);return 0;
}

不难可以发现,这个式子绝对是可以单调队列的。。于是我就很高兴地开打了
于是我得到了下面这个代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=2505;
const int MAX=1<<30;
int n,P,w;//天数   最大股票数   至少隔w天 
struct qq {int a1,b1;//买卖钱数int a2,b2;//买卖数量 }s[N];
int f[N][N];//最后一次交易在前i天,然后手上有j支股票的最大价值
struct qt
{int x,y;//权值   位置 bool operator < (qt a) const{return x<a.x;};
};
int q[N];//单调队列
int q1[N];//单调队列 
int main()
{scanf("%d%d%d",&n,&P,&w);for (int u=1;u<=n;u++)scanf("%d%d%d%d",&s[u].a1,&s[u].b1,&s[u].a2,&s[u].b2);for (int u=0;u<=n;u++)for (int i=0;i<=P;i++)f[u][i]=-MAX;f[0][0]=0;for (int u=1;u<=n;u++)//最后一天在哪一天交易{int k=max(u-w-1,0);int st=1,ed=0;//第一个东西的单调队列int st1=1,ed1=0;//第二个东西的单调队列for (int i=0;i<=P;i++)//手里有多少股票{int a=-MAX;int ooo=max(0,i-s[u].a2),ooo1=i-1;while (st<=ed&&q[st]<ooo) st++;if (ooo1>=0)while (st<=ed&&f[k][q[ed]]+q[ed]*s[u].a1<=f[k][ooo1]+ooo1*s[u].a1) ed--;q[++ed]=ooo1;if (st<=ed) a=f[k][q[st]]+q[st]*s[u].a1;a-=(s[u].a1*i);f[u][i]=max(f[u][i],a);a=-MAX;ooo=i+1,ooo1=min(i+s[u].b2,P);//下界while (st1<=ed1&&q1[st1]<ooo) st1++;if (ooo1>=ooo) {while (st1<=ed1&&f[k][q1[ed1]]+q1[ed1]*s[u].b1<=f[k][ooo1]+ooo1*s[u].b1) ed1--;q1[++ed1]=ooo1;}if (st1<=ed1) a=f[k][q1[st1]]+q1[st1]*s[u].b1;a-=(s[u].b1*i);f[u][i]=max(f[u][i],a);f[u][i]=max(f[u][i],f[u-1][i]);}}int ans=0;for (int u=0;u<=P;u++) ans=max(ans,f[n][u]);printf("%d\n",ans);return 0;
}

看起来很对是不是,然而对拍就是过不了QAQ
检查发现,是q1的单调队列GG了。。
要是有谁能马上反应出为什么,那他肯定是大佬,比我高到不知道哪里去了

于是我就调这个傻逼题调了非常非常久

最后发现,q1当i=0的时候,你有许多点都没有入队
因此要加上下面这段话
在进入到第二重循环里面之前

for (int ooo1=1;ooo1<min(s[u].b2,P);ooo1++){while (st1<=ed1&&q1[st1]<1) st1++;while (st1<=ed1&&f[k][q1[ed1]]+q1[ed1]*s[u].b1<=f[k][ooo1]+ooo1*s[u].b1) ed1--;q1[++ed1]=ooo1;}

吧一些点可行的点入队,才可以过