土地购买 [DP斜率优化]

来源:互联网 发布:qq群做淘宝客怎么加入 编辑:程序博客网 时间:2024/05/20 02:30
经典的题目,土地购买:Farmer John需要买下n(≤50000)块长方形的土地,每块的花费就是长宽之积(就是面积)。他可以一次买多块土地,价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换。问Farmer John在合理分组购买土地下,最少需要的花费。

首先,如果一块土地被另一土地包含(比如5*10的土地包含4*6的土地),那么这块土地就不用考虑了,从数据中去掉。然后按土地的宽度排序,可以发现高度必然是递增的:

土地购买 DP斜率优化 - lightsky - lightsky的oi

设f(i)为购买前i块土地所需最少费用,这个只需要枚举最后一组购买的土地设为j是哪些即可:f(i)= min(f(i),f(j - 1)+ w(j)* h(i)),w(i)是第i块土地的宽,h(i)则是高。

这个明显超时,这时就可以用斜率优化了。斜率优化的是利用比较两值的优异来实现快速转移的。设有两种选择分别为:从j到i,和从k到i,不妨设j < k。如果有:f(j - 1)+ w(j)* h(i) < f(k - 1)+ w(k)* h(i),也就是j比k要优。那么,通过转化得:

(f(j - 1)- f(k - 1))/ (w(j)- w(k)) < - h(i)。

通过观察,不等式左边的式子不就是求两点斜率的式子吗。有两个点(x1, y1) 、(x2, y2),那么它们连成的直线的斜率就是(y1 - y2)/(x1 - y1),那么,我们可以在图上对于每个i,以(w(i),f(i - 1))作为一个点:

土地购买 DP斜率优化 - lightsky - lightsky的oi

注意:点的次序是从右下往左上的,因为w是单调下降的,而f的值是单调上升的,这就决定了图像的形状。如此看来,(f(j - 1)- f(k - 1))/ (w(j)- w(k)) 就是指图中点i和点j的斜率。当它们的斜率小于- h(i)时,则j比k要优,否则k比j优。并且,按照这个斜率来,有传递性,不会出现i比j优,j比k优,k又比i优的情况。

可以发现点4、点2,,都是不可能作为最优答案的,为什么呢?假如点4作为最优答案,说明点5和点4的斜率是小于- h(i)的,那么点4与点3的斜率也是必然小于- h(i)的(不然点4就要“凸”出来了),也就是点3比点4优,假设不成立,所以像点4一样“凹”进去的点,是不可能作为最优答案的。

这样,我们只需要维护一个类似“凸包”的东西就可以了,每次的最优值就是队列中的头的编号。同时要注意,当开头两个点的斜率大于- h(i)时,则要把开头的点去掉。注意,-h(i)是单调下降的,所以这样的做法与单调队列有异曲同工之妙。

适用斜率优化的题目,一般动态规划的转移方程有乘除法,然后通过两点的优劣比较,化成斜率的公式。并且需要具有单调性,可以通过排序来寻找单调性。

#include <cstdio>#include <algorithm>using namespace std;const int N = 50007;int n;struct Data{    long long w, h;    bool operator < (Data const &o) const     {        return w > o.w || (w == o.w && h > o.h);    }}d[N];void Init(){    scanf("%d\n", &n);    for (int i = 1; i <= n; i ++)        scanf("%I64d%I64d\n", &d[i].w, &d[i].h);    sort(d + 1, d + 1 + n);    int m = 1;    for (int i = 2; i <= n; i ++)        if (d[i].h > d[m].h)            d[++ m] = d[i];    n = m;}int Q[N];long long f[N];/* 比较 a / b 和 c / d 的大小,由于 a * d 最大达到 1e+18,所以要先判断整数部分。 当然也可以直接除,但可能会造成精度问题 */inline int cmprc(long long a, long long b,                  long long c, long long d){        long long t1 = a / b;    long long t2 = c / d;    if (t1 != t2)         if (t1 < t2) return -1;        else return 1;    a -= b * t1;    c -= d * t2;    t1 = a * d;    t2 = b * c;    if (t1 < t2) return -1;    else if (t1 > t2) return 1;    else return 0;}                 void Solve(){    int lo = 1, hi = 1;    f[1] = d[1].w * d[1].h;    Q[1] = 1;    for (int i = 2; i <= n; i ++)    {        /* 当开头两个点的斜率大于- h(i)时,则要把开头的点去掉。*/        while (lo < hi &&             cmprc(f[Q[lo] - 1] - f[Q[lo + 1] - 1],                   d[Q[lo]].w - d[Q[lo + 1]].w,                   - d[i].h,                   1) >= 0)             lo ++;                f[i] = min(f[i - 1] + d[i].w * d[i].h,            f[Q[lo] - 1] + d[Q[lo]].w * d[i].h);                /* 维护一个类似“凸包”的东西就行 */        while (lo < hi &&            cmprc(f[i - 1] - f[Q[hi] - 1],                  d[i].w - d[Q[hi]].w,                  f[Q[hi] - 1] - f[Q[hi - 1] - 1],                  d[Q[hi]].w - d[Q[hi - 1]].w) >= 0)            hi --;        Q[++ hi] = i;    }        printf("%I64d\n", f[n]);}int main(){    freopen("acquire.in", "r", stdin);    freopen("acquire.out", "w", stdout);        Init();    Solve();        return 0;}


0 0
原创粉丝点击