POJ 1821 Fence
题目见此:http://poj.grids.cn/practice/1821
这道题想了一晚上啊,结果连最朴素的dp方程都列不出来……差距额
于是第二天看了很多人的解题报告,看完还是半懂不懂,不过最后看这个看懂了:http://www.abandonzhang.com/archives/554
(下面是脑残型详解)
解题思路:
- 这题看上去比较像背包,不过如果直接套背包模板肯定是不行的。这题比较容易想到的是有两个状态,dp[i][j]表示考虑了i个工人在第j个位置。比较难的是处理j与s[i]的位置关系。首先要明确一点,j表示第j个位置,就是说我们只考虑包括j之前的位置,也就是说,我们要把j看成是我们的视野,我们绝对不去关注j之后的东西。把握住这点,后面就好理解了。
- 如果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]这两种,如果很难想的话,两种都写上然后取合适的值应该也是可以的(这句话不知对不对,求轻拍)。
- 然后我们考虑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])。
- 最后我们来考虑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])。
- 接下来是考虑优化的时候了,我们注意到k的范围是与j有关的,所以应该可以用单调队列优化。但是k随着j的增大是逐渐减小的,也就是说,我们一开始要把所有k放到队列中,然后在一个个pop(注意单调队列必须使push和pop的顺序一致)
- 这样我们在来理一下思路。先枚举每一个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 }
还需要注意的地方:
- 54行时s[i]-l[i]可能小于0,所以我们取0和其最大值;56行同理。
- 如果看过我其他几篇单调队列的博文的人,可能注意到我改了单调队列的模板。这是因为此题必须先将所有数据push,然后一一pop。也就是说push和pop的过程是分离的(之前很多题都是结合在一起的),注意要真正理解单调队列的用法!!其实如果能写出正确的dp方程,再用单调队列优化是不难的,但尼玛我的问题就是写不出dp方程啊……
浙公网安备 33010602011771号