[USACO]Land Acquisition G「斜率优化DP」

[USACO]Land Acquisition G「斜率优化DP」

题目描述

Farmer John 准备扩大他的农场,眼前他正在考虑购买 \(N\) 块长方形的土地。

如果 FJ 单买一块土地,价格就是土地的面积。但他可以选择并购一组土地,并购的价格为这些土地中最大的长乘以最大的宽。比如 FJ 并购一块 \(3×5\) 和一块 \(5×3\) 的土地,他只需要支付 \(5×5=25\) 元, 比单买合算。

FJ 希望买下所有的土地。他发现,将这些土地分成不同的小组来并购可以节省经费。 给定每份土地的尺寸,请你帮助他计算购买所有土地所需的最小费用。

输入格式

第一行一个整数 \(N\)\(1≤N≤5×10^4\))。

接下来 \(N\) 行,每行两个整数 \(w_i\)\(l_i\),代表第 \(i\) 块土地的长和宽。保证土地的长和宽不超过 \(10^6\)

输出格式

输出买下所有土地的最小费用。

输入输出样例

输入 #1

4 
100 1 
15 15 
20 5 
1 100 

输出 #1

500 

思路分析

  • 显然如果直接按原序列下手会很棘手,所以我们首先要将序列排好序
    • 为了方便合并,所以我们以长为关键字进行排序,这时候好像还是有点无从下手,所以尝试找一些性质
    • 不难想到,如果一块土地的长和宽均小于某一块土地,那这块土地其实是不会对答案产生任何影响的,所以我们在排好序以后可以将其去除,而只留下合并时会改变长宽的
    • 在进行完的排序和去除操作以后,这时候的序列一定满足这样的性质:长是递减的,而宽是递增的(自己模拟一下就好)
  • 那么此时,对于一段区间,我们只需要取两端即可,因为一个长最大,一个宽最大,区间内的自然就一起合并了
    • 由此得出转移方程: \(f[i] = min(f[i],f[j-1]+x_j*y_i)\) \((0<j<i)\)(\(x\) 为长,\(y\) 为宽)
    • 变一下型就是 \(f[j-1]=f[i]-y_i*x_j\),这样就是一个很简单的可以进行斜率优化的式子了,斜率为 \(y_i\)

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50010
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,q[N];
long long f[N];
struct farm{
	int x,y;
}a[N];
inline bool cmp(farm a,farm b){
	return a.x==b.x ? a.y>b.y : a.x>b.x;
}
double slope(int x,int y){
	return (double)(f[x]-f[y])/(double)(a[y+1].x-a[x+1].x);
}
int main(){
	n = read();
	for(int i = 1;i <= n;i++){
		a[i].x = read(),a[i].y = read();
	}
	sort(a+1,a+1+n,cmp);
	int tot = 0;
	for(int i = 1;i <= n;i++)if(a[i].y>a[tot].y)a[++tot] = a[i]; //去除操作
	n = tot;
	int head = 0,tail = 0;
	for(int i = 1;i <= n;i++){ //按转移方程直接上斜率优化即可
		while(head<tail&&slope(q[head],q[head+1])<=a[i].y)head++;
		f[i] = f[q[head]]+1ll*a[q[head]+1].x*a[i].y;
		while(head<tail&&slope(q[tail-1],q[tail])>=slope(q[tail],i))tail--;
		q[++tail] = i;
	}
	printf("%lld",f[n]);
	return 0;
}
posted @ 2020-08-16 16:31  HH_Halo  阅读(220)  评论(3编辑  收藏  举报