很久没有来这个Blog写东西了。之前因为决定去腾讯,所以竞赛的事情放下了很长一段时间,现在重新拾起。

zhk大神要准备为WF拼一次,我和Talker与他并肩奋斗,于是zhk给我们推荐了一些题目去做,于是,就有了这个做到泪奔的Cross the Wall。

闲话少说,该题目为DP中的战斗机——斜率优化,在最简单的斜率优化基础上添加了一维K,总的来说思想还是没太大变化。之前上网找了很多资料,也去各位大神的Blog取过经,花了一周的时间才断断续续地搞清楚。下面就来简单总结一下自己这段时间学习斜率优化的心得。

 

1.DP方程

斜率优化的典型DP方程为dp(i) = min{dp(j) + f(i,j)},对于这道题,f(i,j) = h(j+1)*w(i)。加上挖洞的个数k,完整的DP方程为dp(i, k) = min{dp(j, k-1) + h(j+1) * w(i)};由于dp(i, k)只与dp(j, k-1)有关,则可以使用滚动数组,在此将DP方程简化为dp(i) = min{dp(j) + h(j+1) * w(i)};

若dp(i) == dp(j) + h(j+1) * w(i) (即当j时,能使dp(i, k)最小),则对于j'(0 <= j' < i && j' != j),有dp(j) + h(j+1) * w(i) < dp(j') + h(j'+1) * w(i);移项可得dp(j) - dp(j') < w(i) * (h(j'+1) - h(j+1));把h作为x,dp作为y,则w为斜率。至此,斜率优化的雏形初现。

 

2.决策点维护

由dp(j) - dp(j') < w(i) * (h(j'+1) - h(j+1))分析以及题目需要求最小值可知,决策点必然是所有可能作为决策点的点集中最靠近下方的,换句话说就是要求这个点集的左链即凸包的左半部分。至于这个决策点按照什么顺序产生,如何维护,都是大同小异的。要注意的是,h和w是有单调性的。在这道题中,我是保证h单减,w单增。

用堆栈p来维护左链,这样,若dp(i) = dp(j) + h(j+1) * w(i), (j < i),则在决策点的堆栈中,p(idx)作为决策点,有p(idx) = (h(j+1), dp(j));根据斜率优化的表达式以及凸包的性质可知,在堆顶元素之前,线段的斜率都是小于w(i)的。由于w是递增的,那么在计算dp(i+1)时,决策点不会退化到当前决策点p(idx)之前,这样就可以更快地找到决策点,降低复杂度。

 

3.实例

在分析算法的时候,自己写了个简单的数据来帮助理解。

4 100
99 100
100 99
200 50
201 49

则h[] = {100, 99, 50, 49}, w[] = {99, 100, 200, 201};

 

具体的就不细讲了。写这篇东西一来是自己总结,二来是希望能够给需要的人一些帮助。这道题dp值可能很大,要用int64,在输出的时候要用%I64d,否则会出错。就是这里我用%lld,让我查了一个下午...

太久不写代码始终是有些生疏了,很多问题需要注意,加油吧~

 

HDU 3669 Cross the Wall
  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <algorithm>
  4 
  5 #define DEBUG 0
  6 
  7 using namespace std;
  8 
  9 const int MaxN = 50010, MaxK = 110;
 10 typedef long long i64;
 11 int N, K;
 12 
 13 struct Rect {
 14     int W, H;
 15 }rect[MaxN+10];
 16 
 17 struct Point {
 18     i64 x, y;
 19     Point() {}
 20     Point(const i64 X, const i64 Y): x(X), y(Y) {}
 21     Point operator - (const Point P) const {
 22         return Point(x-P.x, y-P.y);
 23     }
 24     Point operator + (const Point P) const {
 25         return Point(x+P.x, y+P.y);
 26     }
 27 };
 28 
 29 i64 mul(Point a, Point b) {
 30     return a.x*b.y - a.y*b.x;
 31 }
 32 
 33 struct Line {
 34     int sp, i;
 35     Point point[MaxN+10];
 36     Line(){}
 37     void init() {
 38         sp = -1;
 39         i = 0;
 40     }
 41     bool insert(Point P) {
 42         while(sp > 0 && mul(P-point[sp], P-point[sp-1]) < 0)    //维护凸包
 43             sp--;
 44         point[++sp] = P;
 45         return true;
 46     }
 47     i64 optimize(int k) {
 48         while(i < sp && point[i].y + point[i].x*>= point[i+1].y + point[i+1].x*k)    //斜率优化
 49             i++;
 50         return point[i].y + point[i].x*k;
 51     }
 52 }line;
 53 
 54 int x[MaxN], y[MaxN], yn;
 55 
 56 int ID(int num) {
 57     int s = -1, e = yn, mid = (s + e) >> 1;
 58     while(s + 1 < e) {
 59         if(y[mid] == num)
 60             return mid;
 61         if(y[mid] > num)
 62             e = mid;
 63         else
 64             s = mid;
 65         mid = (s + e) >> 1;
 66     }
 67 }
 68 
 69 void optimize() {
 70     for(int i = 1; i <= N; ++i)
 71         y[i-1= rect[i].W;
 72     sort(y, y + N);
 73     yn = unique(y, y + N) - y;
 74     memset(x, 0sizeof(x));
 75     for(int i = 1; i <= N; ++i) {
 76         int idx = ID(rect[i].W);
 77         x[idx] = max(x[idx], rect[i].H);
 78     }
 79     N = yn;
 80     for(int i = 0; i < yn; ++i) {
 81         rect[i+1].H = x[i];
 82         rect[i+1].W = y[i];
 83     }
 84     int sp = 1;
 85     for(int i = 1; i <= N; ++i) {
 86         if(sp == 0) {
 87             rect[++sp] = rect[i];
 88             continue;
 89         }
 90         if(rect[i].W == rect[sp].W)
 91             continue;
 92         if(rect[i].H >= rect[sp].H) {
 93             sp--; i--;
 94         }
 95         else if(rect[i].H < rect[sp].H)
 96             rect[++sp] = rect[i];
 97     }
 98     N = sp;
 99 #if DEBUG
100     for(int i = 1; i <= N; ++i)
101         printf("%d %d\n", rect[i].W, rect[i].H);
102 #endif
103 }
104 
105 i64 dp[2][MaxN+10];
106 
107 void work() {
108     optimize();            //预处理所有矩形
109     int cur = 1, pre = 0;
110     for(int i = 1; i <= N; ++i) {    //初始化dp[pre]为挖一个洞的花费
111         dp[pre][i] = 1LL*rect[i].W * rect[1].H;
112     }
113     int cnt = 0;
114     for(int k = 2; k <= K; ++k) {    //从挖两个洞开始计算
115         line.init();        //左链初始化
116         line.insert(Point(rect[1].H, 0));    //预先插入决策点(h[1], dp[x][0])。
117 #if DEBUG
118         for(int i = 1; i <= N; ++i)
119             printf("%15lld", dp[pre][i]); 
120         printf("\n");
121 #endif
122         for(int i = 1; i <= N; ++i) {
123             dp[cur][i] = line.optimize(rect[i].W);    //计算斜率优化出来的dp值
124             if(i < N) {
125                 line.insert(Point(rect[i+1].H, dp[pre][i]));    //将之前计算的dp值作为可能的决策点插入
126             }
127         }
128         cur = (cur+1)%2;
129         pre = 1 - cur;
130         if(dp[cur][N] == dp[pre][N])
131             cnt++;
132         if(cnt > 5)
133             break;
134     }
135     printf("%I64d\n", dp[pre][N]);
136 }
137 
138 int main() {
139 #if DEBUG
140     freopen("test.in""r", stdin);
141     freopen("test.out""w", stdout);
142 #endif
143     while(scanf("%d%d"&N, &K) == 2) {
144         for(int i = 1; i <= N; ++i)
145             scanf("%d%d"&rect[i].W, &rect[i].H);
146         work();
147     }
148     return 0;
149 }
150