题目链接:土地征用Land Acquisition
题面:约翰准备扩大他的农场,眼前他正在考虑购买N块长方形的土地。如果约翰单买一块土 地,价格就是土地的面积。但他可以选择并购一组土地,并购的价格为这些土地中最大的长 乘以最大的宽。比如约翰并购一块3 × 5和一块5 × 3的土地,他只需要支付5 × 5 = 25元, 比单买合算。 约翰希望买下所有的土地。他发现,将这些土地分成不同的小组来并购可以节省经费。 给定每份土地的尺寸,请你帮助他计算购买所有土地所需的最小费用。
题解:我们先以(x)升序排序,对于每一个(i),如果存在一个(j)使得(x_i<=x_j,y_i<=y_j),那么很明显,(i)和(j)一起购买是不会产生价值的,那么我们可以把(i)删去,最后我们就得到一个(i)升序,(y)降序的数组。问题便转化为将序列分为若干份([L_i,R_i]),使得(ans=sum({x_{R_i}} imes y_{L_i}))最小。
我们可以很轻松地得到一个DP方程式:设(f[i])表示到第(i)个结尾时的最小代价,那么转移便是(f[i]=min_{j=0}^{i-1}{f[j]+x[i]cdot y[j+1]})。
很明显这一个方程式是(O(n^2))的,对于(n le 5 cdot 10^4)的数据范围是肯定跑不过的(除非你有十分特殊的卡常技巧)
我们引入(j,k(j<k<i))两个变量,考虑当前在第(i)位上进行动规,取(k)比取(j)更优的情况。
即(f[j]+x[i]*y[j+1] > f[k]+x[i]*y[k+1])
(f[j]-f[k]>x[i] imes (y[k+1]-y[j+1]))因为我们之前已经说明了(y)是降序的,所以(y[k+1]-y[j+1]<0)
所以(frac{f[j]-f[k]}{y[k+1]-y[j+1]}<x[i])
但是,我们的(x[i])是单调递增的。
下面就可以很开心地用单调队列维护啦。
code
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define Maxn 50000
#define ll long long
#define eps 1e-8
struct Node{
int x,y;
friend bool operator <(Node p,Node q){
if(p.x==q.x){
return p.y<q.y;
}
return p.x<q.x;
}
}a[Maxn+5];
int n;
ll f[Maxn+5];
int q[Maxn+5],h,t;
double slope(int x,int y){
return (f[x]-f[y])*1.0/(a[y+1].y-a[x+1].y);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].x,&a[i].y);
}
sort(a+1,a+1+n);
int m=0,maxn=0;
for(int i=n;i>0;i--){
if(a[i].y<=maxn){
a[i].x=(1<<30);
m++;
}
else{
maxn=a[i].y;
}
}
sort(a+1,a+1+n);
n-=m;
h=1;
q[++t]=0;
for(int i=1;i<=n;i++){
while(h<t&&slope(q[h],q[h+1])<=a[i].x+eps){
h++;
}
f[i]=f[q[h]]+a[q[h]+1].y*1ll*a[i].x;
while(h<t&&slope(q[t-1],q[t])>=slope(q[t-1],i)){//这两段似乎可以交叉相乘避免精度问题,不过我懒得那么写了
t--;
}
q[++t]=i;
}
cout<<f[n]<<endl;
return 0;
}