1597: [Usaco2008 Mar]土地购买
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 4023 Solved: 1470
[Submit][Status][Discuss]
Description
农夫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
100 1
15 15
20 5
1 100
输入解释:
共有4块土地.
Sample Output
HINT
FJ分3组买这些土地: 第一组:100x1, 第二组1x100, 第三组20x5 和 15x15 plot. 每组的价格分别为100,100,300, 总共500.
【分析】
刚开始没什么思路,但后开就突然开窍了,就是把每块土地的长和宽看做平面直角坐标系里点的横纵坐标
如下:
那么如果你要单独购买一块土地,就是作一个长和宽分别等于这个点的横纵坐标的矩形,其面积就是你要付的费用,我们可以规定矩形的左下角为(0,0),右上角就是这个点。
如果要购买一组土地,题目里说是长和宽分别取最大值然后乘起来,对应到坐标系里就是选最右边的点的横坐标乘以最上边的点的纵坐标。如下:
那么问题就变成了使用面积和最小的若干个矩形(左下角都是(0,0))将所有的点都覆盖。如下是一种随便绘制的方案:
不难发现,如果一个点的横纵坐标都小于另一个点,那么这个点的存在就是没有意义的。因为不管用何种方案把横纵坐标都较大的点覆盖时,这个横纵坐标都较小的点一定被覆盖了。而你的目的是覆盖所有的点,所以这些横纵坐标都较小的点存不存在是不会影响答案的。如下:
我们把这样的点(a,b)叫做红点:存在另一个点(x,y),满足a<=x且b<=y;
把这样的点(a,b)叫做蓝点:对于其它所有的点(x,y),满足a>x或b>y
当我们按照定义把点分类并且把红色的点去掉时,发现剩下的点在一个严格单调下降函数的图像上,如下:
为了方便研究,使用另一张图,如下:
根据蓝点的定义,可以证明这些蓝点在一个严格单调下降函数图像上。
然而这会有没思路了。。。。
回到开始,我们要用面积最小的矩形把这些点覆盖,先随便找几个点,比如我要买图中的第3个点和第6个点所对应的土地,那么我们把它们用矩形框起来。然后我们发现两个东西:
第一:如果要把第i和第j个点框起来,那么中间所有的点都一定被框在里面了(因为单调下降)
第二:框起第i个到第j个点的代价是第i个点的纵坐标乘上第j个点的横坐标(因为单调下降)
如下:
如果有两个矩形重叠了,比如两个矩形覆盖的点的编号分别是a~b,c~d,其中a<c<b<d那么由单调性可以证明选择a~c,b~d这种方案一定更优,如果一个矩形完全在另一个矩形内部,那你可以直接舍弃这个较小的矩形。
综上,在最优解中,所有矩形所覆盖的点构成一些不相交的区间。如下是一种随便绘制的方案:
用f[i]表示覆盖掉前i个点的最小费用,那么进行划分dp,方程为:
f[i]=min(f[j]+w(j+1,i)),(j<i)
其中w(j+1,i)表示覆盖第j+1个点到第i个点的费用
时间复杂度O(N^2)
【优化】
用w(i,j)表示覆盖第i~j个点的费用,那么w(i,j)=y[i]*x[j]。
由单调性有y[i+1]<y[i],x[i+1]>x[i],恒成立
w(i,j)=y[i]*x[j]
w(i+1,j+1)=y[i+1]*x[j+1]
w(i+1,j)=y[i+1]*x[j]
w(i,j+1)=y[i]*x[j+1]
作差:
[
w(i,j)+w(i+1,j+1) ] - [ w(i+1,j)+w(i,j+1) ]
=
y[i]*(x[j]-x[j+1]) + y[i+1]*(x[j+1]-x[j])
=
(y[i]-y[i+1])(x[j+1]-x[j]) <0恒成立
所以w(i,j)+w(i+1,j+1) < w(i+1,j)+w(i,j+1)恒成立
所以此dp具有决策单调性
显然f[i]=min(f[j]+y[j+1]x[i])
然后再搞个斜率优化
方程是(f[j]-f[k])/(y[k+1]-y[j+1])<x[i]
然后维护一个下凸包
#include<cstdio>
#include<algorithm>
#define ll long long
#define DB double
using namespace std;
ll read(){
register ll x=;bool f=;
register char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=;ch=getchar();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getchar();}
return f?x:-x;
}
const int N=5e4+;
struct node{
int x,y;
bool operator < (const node &b)const{
return x==b.x?y<b.y:x<b.x;
}
}a[N];
ll x[N],y[N],f[N];
int n,tot,q[N];
DB slop(int a,int b){
return (DB)(f[b]-f[a])/(y[a+]-y[b+]);
}
int main(){
n=read();
for(int i=;i<=n;i++) a[i].x=read(),a[i].y=read();
sort(a+,a+n+);
for(int i=;i<=n;i++){
for(;tot&&a[i].y>=y[tot];tot--);
x[++tot]=a[i].x;y[tot]=a[i].y;
}
int l=,r=;
for(int i=;i<=tot;i++){
for(;l<r&&slop(q[l],q[l+])<x[i];l++);
f[i]=f[q[l]]+y[q[l]+]*x[i];
for(;l<r&&slop(q[r],i)<slop(q[r-],q[r]);r--);
q[++r]=i;
}
printf("%lld",f[tot]);
return ;
}