HDU 3401 Trade
题目:http://acm.hdu.edu.cn/showproblem.php?pid=3401
这个题在http://www.cnblogs.com/ka200812/archive/2012/07/11/2585950.html中先看了一遍,但是自己写的时候一直WA,原来是预处理没有做好……题目还是要自己做一遍才能理解啊,光看别人代码是看不懂的
思考过程:
- 首先看出这是个dp题,然后想状态:肯定有一个表示天数的状态,用i表示;接着我的第一反应是有一个表示买入或卖出的状态,但这个状态的上下限是不定的(跟i有关),接着注意到每天拥有的最多股数是MaxP,所以用就j表示这天拥有的股数。
- 列dp方程:三个:
1、从前一天不买不卖:dp[i][j]=max(dp[i-1][j],dp[i][j])
2、从前i-W-1天买进一些股:dp[i][j]=max(dp[i-W-1][k]-(j-k)*AP[i],dp[i][j]) j-AS[i]<=k<j
3、从i-W-1天卖掉一些股:dp[i][j]=max(dp[i-W-1][k]+(k-j)*BP[i],dp[i][j]) j<k<=j+BS[i]
这里只考虑第i-W-1天的买入卖出情况即可,因为之前的情况都可以包含在i-W-1天中
还需要注意的是k的范围,注意有没有等号
3. 根据k的范围看出可以用单调队列优化。对第二个方程,由于k<j,所以要把j从小到大循环;第三个方程k>j,要把j从大到小循 环,k的可变范围是AS[i]和BS[i]。
这题还是用的自己写的单调队列模板,而且发现自己以前的写的不完整,在判断队列内元素范围时需要加上绝对值,否则对于类似题中把j从大到小循环的情况就错了(为了找这个错误花了我好长时间啊!)。
先贴代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <string.h> 4 #include <cmath> 5 using namespace std; 6 7 const int MAX = 2001; 8 9 struct MQ 10 { 11 int pos[MAX], val[MAX]; 12 int head, tail, winlen; 13 void reset(const int& _winlen) 14 { head = 0, tail = -1, winlen = _winlen; memset(pos, 0, sizeof(pos));memset(val, 0, sizeof(val)); } 15 void push_back(const int& index,const int& _val) 16 { 17 while(head <= tail && val[tail] < _val) 18 tail--; 19 pos[++tail] = index; 20 val[tail] = _val; 21 while(abs(index - pos[head]) >= winlen) 22 head++; 23 } 24 int top() 25 { return val[head]; } 26 }; 27 28 int dp[MAX][MAX], AP[MAX], BP[MAX], AS[MAX], BS[MAX]; 29 MQ q; 30 int t, T, MaxP, W; 31 32 int main() 33 { 34 cin >> t; 35 while(t--) 36 { 37 cin >> T >> MaxP >> W; 38 memset(dp, 0, sizeof(dp)); 39 for(int i=1 ; i<=T ; i++) 40 cin >> AP[i] >> BP[i] >> AS[i] >> BS[i]; 41 for(int i=0;i<=T;i++) 42 for(int j=0;j<=MaxP;j++) 43 dp[i][j]=-0xfffffff; 44 dp[0][0]=0; 45 for(int i=1;i<=W+1;i++) 46 for(int j=0;j<=(min(MaxP,AS[i]));j++) 47 dp[i][j]=-AP[i]*j; 48 for(int i=1 ; i<=T ; i++) 49 { 50 for(int j=0 ; j<=MaxP ; j++) 51 dp[i][j] = max(dp[i-1][j], dp[i][j]); 52 if(i - W - 1 >= 0) 53 { 54 q.reset(AS[i]); 55 q.push_back(0, dp[i-W-1][0]); 56 for(int j=1 ; j<=MaxP ; j++) 57 { 58 dp[i][j] = max( q.top() - j * AP[i], dp[i][j]); 59 q.push_back(j, dp[i-W-1][j]+j * AP[i]); 60 } 61 q.reset(BS[i]); 62 q.push_back(MaxP, dp[i-W-1][MaxP]+MaxP * BP[i]); 63 for(int j=MaxP-1 ; j>=0 ; j--) 64 { 65 dp[i][j] = max( q.top() - j * BP[i], dp[i][j]); 66 q.push_back(j, dp[i-W-1][j]+j * BP[i]); 67 } 68 } 69 } 70 int res = -0xfffffff; 71 for(int j=0 ; j<=MaxP ; j++) 72 res = max(res, dp[T][j]); 73 cout << res << endl; 74 } 75 }
有这么几个比较重要的地方要注意(导致我WA了一上午):
- 开始把dp初始化为0了,WA,因为每天的获利当然可以是负的,应该初始为负无穷
- 45行的预处理很重要,没有也会WA,自己没有想到,在网上对照别人的代码才填上。那为什么会有这个预处理呢?因为下面对买入和卖出的条件都是if(i - W - 1 >= 0),表示第j天我们假设是进行过交易的,而对于第一次的买入是没有时间限制的,所以先预处理。没有想到啊……
- 对于第二个dp方程,k的范围是j-AS[i]<=k<j,这表示队列中应不包括第j个数据,所以我们先把第j=0的情况放入队列,之后循环时先取出队列中的最大值,然后再插入本次的j。第三个方程同理,但注意要插入的是MaxP的情况,而且我写的队列模板中在判断队列范围时必须加绝对值。
哎……这题花了我好长时间,有点挫败感。不过也值了,毕竟加深了对单调队列的理解
浙公网安备 33010602011771号