[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不管。考虑如何判断包含关系。
先将所有土地按照长度从大到小排序。然后依次把土地放入一个新数组。需要注意的是,如果当前放入的土地的宽度小于等于数组中的最后一块土地,那么就可以直接舍弃当前土地,不用放入数组(被包含)。
那么观察一下我们得到的新数组,发现其长度是单调递减的,宽度是单调递增的。那么我们可以很容易的想到状态转移方程:
直接暴力转移复杂度(O(n^2))肯定是要炸的。考虑使用斜率优化:
所以问题就转化为了作一条直线(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;
}