POJ 1821 Fence

题目见此:http://poj.grids.cn/practice/1821

这道题想了一晚上啊,结果连最朴素的dp方程都列不出来……差距额

于是第二天看了很多人的解题报告,看完还是半懂不懂,不过最后看这个看懂了:http://www.abandonzhang.com/archives/554

(下面是脑残型详解)

解题思路:

  1. 这题看上去比较像背包,不过如果直接套背包模板肯定是不行的。这题比较容易想到的是有两个状态,dp[i][j]表示考虑了i个工人在第j个位置。比较难的是处理j与s[i]的位置关系。首先要明确一点,j表示第j个位置,就是说我们只考虑包括j之前的位置,也就是说,我们要把j看成是我们的视野,我们绝对不去关注j之后的东西。把握住这点,后面就好理解了。
  2. 如果s[i]在j之后,我们是不考虑s[i]对j的影响的。也就是说,即使j>s[i]-l[i](如果不能理解的话,可以考虑当最终j=N时的含义,这时一定是不考虑之后的i的,因为之后也没有i了),所以当j<s[i]时,只需要从上一个状态直接转移即可,即dp[i][j]=dp[i-1][j]。至于其他人博客中写道dp[i][j]=max(dp[i-1][j],dp[i][j-1]),是没有意义的,可以简化为dp[i][j]=dp[i-1][j]。但这也启示我们,这类题对这种直接转移的情况不用考虑太细,直接转移的情况不外乎dp[i-1][j],dp[i][j-1]这两种,如果很难想的话,两种都写上然后取合适的值应该也是可以的(这句话不知对不对,求轻拍)。
  3. 然后我们考虑j>=s[i]的情况。由于s[i]在j之内,所以我们要考虑它,那么我们怎么考虑它呢?这是我们要思考dp[i][j]的意义,我们一定要让i尽量去处理j(即使有时它处理不到),否则j和j-1就没有区别了。也就是说,我们要考虑s[i]可不可以处理到j。如果j >s[i]+l[i],这是i已经管不到j了,如上所说,直接转移dp[i][j]=dp[i][j-1]?错!!从直观上来看,j是比s[i]远大的,因此缩小j是合理的,但是缩小i也是合理的,因为i-1是可能处理到j的。所以当j>s[i]+l[i]时,dp[i][j]=max(dp[i-1][j],dp[i][j-1])。
  4. 最后我们来考虑s[i]<= j <s[i]+l[i]的情况。此时j在s[i]的处理范围之内(注意理解为什么当s[i]-l[i]<j<s[i]时,j不在s[i]的处理范围内,因为我们只关注j之前的东西!!)此时可以分对j处理和不对j处理两种情况来考虑。如果不对j处理,同上:dp[i][j]=max(dp[i-1][j],dp[i][j-1])。如果对j处理:那么就一定会对s[i]到j之间的所有位置都处理(因为必须要每个油漆工必须刷自己的位置),注意我们为什么不能只处理到s[i]和j之间的一个位置呢?因为那种情况我们已经考虑到dp[i][j]=max(dp[i-1][j],dp[i][j-1])中了(也就是不对j处理,注意dp[i][j]表示此时我们只关注j)。这样概念就清楚了,我们可以枚举一个变量k使得j-l[i]<=k<s[i],k表示我们处理的左开边界(注意左边界不能超过s[i],且右边界一定是j)。转移方程为:dp[i][j]=max(dp[i-1][k]+(j-k)*p[i])  (j-l[i]<=k<s[i])。
  5. 接下来是考虑优化的时候了,我们注意到k的范围是与j有关的,所以应该可以用单调队列优化。但是k随着j的增大是逐渐减小的,也就是说,我们一开始要把所有k放到队列中,然后在一个个pop(注意单调队列必须使push和pop的顺序一致)
  6. 这样我们在来理一下思路。先枚举每一个i,然后我们要顺序枚举j了,这是分三种情况分别枚举:1.j<s[i]此时我们不关注s[i]了,因为它在j之后 2.s[i]<=j<s[i]+l[i]单调队列优化(相当于枚举k) 3.s[i]+l[i]<j,此时i管不到j了,直接转移。

贴代码:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <algorithm>
 4 #include <iostream>
 5 using namespace std;
 6 const int MAXK = 101, MAXN = 16001;
 7 struct Worker
 8 {
 9     int l, p, s;
10 }w[MAXK];
11 bool cmp(const Worker& a, const Worker& b)
12 {
13     if(a.s == b.s)
14         return a.l > b.l;
15     return a.s < b.s ;
16 }
17 int N, K, dp[MAXK][MAXN];
18 
19 struct MQ
20 {
21     int pos[MAXN], val[MAXN];
22     int head, tail, winlen;
23     void reset(const int& _winlen)
24     {    head = 0, tail = -1, winlen = _winlen;    }
25     void push_back(const int& index,const int& _val)
26     {
27         while(head <= tail && val[tail] < _val)
28             tail--;
29         pos[++tail] = index;
30         val[tail] = _val;
31     }
32     void pop(const int& index)
33     {
34         while(head <= tail && index - pos[head] >= winlen)
35             head++;
36     }
37     int top()
38     {    return val[head];    }
39 }q;
40 
41 
42 int main()
43 {
44     cin >> N >> K;
45     for(int i=1 ; i<=K ; i++)
46         cin >> w[i].l >> w[i].p >> w[i].s;
47     sort(w+1, w+K+1, cmp);
48     memset(dp, 0, sizeof(dp));
49     for(int i=1 ; i<=K ; i++)
50     {
51         for(int j=1 ; j<w[i].s ; j++)
52             dp[i][j] = dp[i-1][j];
53         q.reset(w[i].l);
54         for(int k=max(0, w[i].s-w[i].l) ; k<w[i].s ; k++)
55             q.push_back(k, dp[i-1][k]-w[i].p*k);
56         for(int j=w[i].s ; j<min(w[i].s+w[i].l, N+1) ; j++)
57         {
58             dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
59             dp[i][j] = max(dp[i][j], q.top()+j*w[i].p);
60             q.pop(j);
61         }
62         for(int j=w[i].s+w[i].l ; j<=N ; j++)
63             dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
64     }
65     cout << dp[K][N] << endl;
66 }
View Code

还需要注意的地方:

  1. 54行时s[i]-l[i]可能小于0,所以我们取0和其最大值;56行同理。
  2. 如果看过我其他几篇单调队列的博文的人,可能注意到我改了单调队列的模板。这是因为此题必须先将所有数据push,然后一一pop。也就是说push和pop的过程是分离的(之前很多题都是结合在一起的),注意要真正理解单调队列的用法!!其实如果能写出正确的dp方程,再用单调队列优化是不难的,但尼玛我的问题就是写不出dp方程啊……

posted on 2013-06-03 19:26  白~  阅读(150)  评论(0)    收藏  举报

导航