您的位置:首页 > 其它

土地购买 [DP斜率优化]

2014-04-06 13:04 183 查看
经典的题目,土地购买:Farmer John需要买下n(≤50000)块长方形的土地,每块的花费就是长宽之积(就是面积)。他可以一次买多块土地,价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换。问Farmer John在合理分组购买土地下,最少需要的花费。
首先,如果一块土地被另一土地包含(比如5*10的土地包含4*6的土地),那么这块土地就不用考虑了,从数据中去掉。然后按土地的宽度排序,可以发现高度必然是递增的:



设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))作为一个点:



注意:点的次序是从右下往左上的,因为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
;
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
;
long long f
;
/* 比较 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
);
}
int main()
{
    freopen("acquire.in", "r", stdin);
    freopen("acquire.out", "w", stdout);
    
    Init();
    Solve();
    
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: