土地征用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;
}
posted @ 2019-10-30 20:59  with_hope  阅读(190)  评论(0编辑  收藏  举报