[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;
}