zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    python 学习笔记(四)(流程控制)
    python 写斐波那契数列
    python 部分术语对照表
    python 学习笔记(三)(对前两节的补充)
    python # -*- coding: utf-8 -*-
    写出更好的 JavaScript 条件语句
    PHP消息队列实现及应用
    VUE3.0 路由去掉#号
    php设计模式
    workerman 可能需要用到的函数
  • 原文地址:https://www.cnblogs.com/GavinZheng/p/10948031.html
Copyright © 2011-2022 走看看