[BZOJ1597]土地购买

[BZOJ1597]土地购买

题面

农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足(1 <= 宽 <

= 1,000,000; 1 <= 长 <= 1,000,000). 每块土地的价格是它的面积,但FJ可以同时购买多快土地. 这些土地的价

格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换. 如果FJ买一块3x5的地和一块5x3的地,则他需要

付5x5=25. FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费. 他需要你帮助他找到最小的经费.

Input

* 第1行: 一个数: N

* 第2..N+1行: 第i+1行包含两个数,分别为第i块土地的长和宽

Output

* 第一行: 最小的可行费用.

Sample Input

4
100 1
15 15
20 5
1 100
输入解释:
共有4块土地.

Sample Output

500
FJ分3组买这些土地:
第一组:100x1,
第二组1x100,
第三组20x5 和 15x15 plot.
每组的价格分别为100,100,300, 总共500.

思路

很显然,如果土地A被土地B包含,那么我们可以直接舍弃土地A不管。考虑如何判断包含关系。

先将所有土地按照长度从大到小排序。然后依次把土地放入一个新数组。需要注意的是,如果当前放入的土地的宽度小于等于数组中的最后一块土地,那么就可以直接舍弃当前土地,不用放入数组(被包含)。

那么观察一下我们得到的新数组,发现其长度是单调递减的,宽度是单调递增的。那么我们可以很容易的想到状态转移方程:

\[f[i]=min(f[j]+length[j+1]*width[i]),1 \leq j < i \]

直接暴力转移复杂度\(O(n^2)\)肯定是要炸的。考虑使用斜率优化:

\[令x=width[i],k=length[j+1],b=f[j] \\f[j]+length[j+1]*width[i]=kx+b \]

所以问题就转化为了作一条直线\(x=f[i]\),求所有直线与之交点中纵坐标最小的那一个点。

总结一下,斜率优化必须符合这几个条件:

  • k具有单调性
    //未完待续

代码

注意,每次while(...){head++;}之后的head不是找到的最优解\(j\)。而是线段对应的编号。因为可能舍去一些线段,所以线段对应的编号是小于等于线段对应的\(j\)的。

另外,写状态转移方程的时候取值范围应该保证\(<i\)而不是\(\leq i\)。因为我们是先查询再插入的,所以在查询时是取不到\(i\)的。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 55000
#define ll long long
struct line{
    ll k,b;
}l[maxn];
struct field{
    int wid,len;
}fie1[maxn],fie2[maxn];
int n,head=1,tail,cntn;
ll f[maxn];
bool cmp(field x,field y){
    if(x.len==y.len){return x.wid>y.wid;}
    return x.len>y.len;
}
bool calc(line l1,line l2,int c){return (double)(l1.b-l2.b)/(l2.k-l1.k)<=c;}
bool calc(line l1,line l2,line now){return
    (double)(l1.b-l2.b)/(l2.k-l1.k)>=(double)(l1.b-now.b)/(now.k-l1.k);}
int main(){
    //freopen("in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d",&fie1[i].wid,&fie1[i].len);
    sort(fie1+1,fie1+1+n,cmp);
    for(int i=1;i<=n;i++){
        if(fie1[i].wid>fie2[cntn].wid){fie2[++cntn]=fie1[i];}
    }
    n=cntn;l[++tail]=(line){fie2[1].len,0};//l[1].k=fie2[1].len;
    for(int i=1;i<=n;i++){
        while(head<tail&&calc(l[head],l[head+1],fie2[i].wid))head++;
        f[i]=l[head].k*fie2[i].wid+l[head].b;
        line now=(line){fie2[i+1].len,f[i]};
        while(head<tail&&calc(l[tail],l[tail-1],now))tail--;
        l[++tail]=now;
    }
    printf("%lld",f[n]);
    return 0;
}
posted @ 2019-05-30 10:15  GavinZheng  阅读(253)  评论(0编辑  收藏  举报