HDU 3401 Trade

题目:http://acm.hdu.edu.cn/showproblem.php?pid=3401

这个题在http://www.cnblogs.com/ka200812/archive/2012/07/11/2585950.html中先看了一遍,但是自己写的时候一直WA,原来是预处理没有做好……题目还是要自己做一遍才能理解啊,光看别人代码是看不懂的

思考过程:

  1. 首先看出这是个dp题,然后想状态:肯定有一个表示天数的状态,用i表示;接着我的第一反应是有一个表示买入或卖出的状态,但这个状态的上下限是不定的(跟i有关),接着注意到每天拥有的最多股数是MaxP,所以用就j表示这天拥有的股数。
  2. 列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 }
View Code

 

有这么几个比较重要的地方要注意(导致我WA了一上午):

  1. 开始把dp初始化为0了,WA,因为每天的获利当然可以是负的,应该初始为负无穷
  2. 45行的预处理很重要,没有也会WA,自己没有想到,在网上对照别人的代码才填上。那为什么会有这个预处理呢?因为下面对买入和卖出的条件都是if(i - W - 1 >= 0),表示第j天我们假设是进行过交易的,而对于第一次的买入是没有时间限制的,所以先预处理。没有想到啊……
  3. 对于第二个dp方程,k的范围是j-AS[i]<=k<j,这表示队列中应不包括第j个数据,所以我们先把第j=0的情况放入队列,之后循环时先取出队列中的最大值,然后再插入本次的j。第三个方程同理,但注意要插入的是MaxP的情况,而且我写的队列模板中在判断队列范围时必须加绝对值。

哎……这题花了我好长时间,有点挫败感。不过也值了,毕竟加深了对单调队列的理解

posted on 2013-06-02 16:52  白~  阅读(135)  评论(0)    收藏  举报

导航