土地征用Land Acquisition(斜率优化DP)
题目链接:土地征用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}}\times 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]\times (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;
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。