[Usaco2008 Mar]土地购买

 [USACO Mar08]土地购买


农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000).

每块土地的价格是它的面积,但FJ可以同时购买多块土地. 这些土地的价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换. 如果FJ买一块3x5的地和一块5x3的地,则他需要付5x5=25.

FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费. 他需要你帮助他找到最小的经费.

输入格式:

  • 第1行: 一个数: N
  • 第2..N+1行: 第i+1行包含两个数,分别为第i块土地的长和宽

样例输入

4
100 1
15 15
20 5
1 100

输入解释:

共有4块土地.

输出格式:

  • 第一行: 最小的可行费用.

样例输出

500

输出解释:

FJ分3组买这些土地: 第一组:100x1, 第二组1x100, 第三组20x5 和 15x15 plot. 每组的价格分别为100,100,300, 总共500.




嘛。。斜率优化。。上凸和下凸傻傻分不清楚

那现在我们来看一下这道题= =


首先包含在矩形内部的就不用管它了嘛,总之打包买起来就好了。

那么我们现在得到了x递增而y递减的矩形,如图所示


很容易写出dp方程


f[i]=min(f[j]+y[j+1]*x[i]);

= =  显然(lan),n^2的做法是不能过的


那么斜率优化的公式推法:

假设j的决策比k的决策要好

那么则有f[k]+y[k+1]*x[i]>=f[j]+y[j+1]*x[i]

(y[k+1]-y[j+1])*x[i]>=f[j]-f[k]


那么因为y是单调减少的,所以x[i]的系数一定是整数

则有x[i]>=(f[j]-f[k])/(y[k+1]-y[j+1])

这样就是斜率啦,,


那么下面说一下斜率,拿刚刚的式子举例:

                   f[i]=f[j]+x[i]*f[j+1]

线性规划的式子可以为 P = x  +  k * y

至于为什么。。我也不知道为什么x和y一定要定义成酱紫


那么则有:y=-(x/k)+p/k

这样p/k就代表截距,-1/x[i]就是直线的斜率

例如一条斜率为-1/x[i]的直线在一个二维平面上碰点

我们的最优决策一定是下凸壳上的点(使得截距最小的点)


那么根据式子x[i]>=(f[j]-f[k])/(y[k+1]-y[j+1])

如果j满足这个式子,那么k一定木有j要好,这时候就可以把j删掉

而且我们发现x[i]即斜率是递增的

所以即可以维护一个单调队列,保证斜率递增,删除劣决策,队首即为当前最优决策




以下粘代码:



#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 50010
using namespace std;

int n;
struct Point{
	int x, y;
	bool operator<(const Point& k)const{
		if(x != k.x)return x < k.x;
		return y < k.y;
	}
	bool operator*(const Point& k)const{
		return x <= k.x && y <= k.y;
	}
}a[maxn], b[maxn];

long long dp[maxn];
int q[maxn];

double slop(int j, int k){
	return (double) (dp[j] - dp[k]) / (b[k + 1].y - b[j + 1].y);
}
int main(){
   	freopen("acquire.in", "r", stdin);
	freopen("acquire.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++)
	    scanf("%d%d", &a[i].x, &a[i].y);
	int tot = 0;
	sort(a + 1, a + 1 + n);
	int it = 0;
	for(int i = 1; i <= n; i ++){
		while(tot && (b[tot] * a[i]))
			tot --;
		b[++ tot] = a[i];
	}
	int l = 0, r = 0;
	for(int i = 1; i <= tot; i ++){
		while(l < r && slop(q[l], q[l + 1]) <= b[i].x)
		    l ++;
		dp[i] = dp[q[l]] + (long long)b[q[l] + 1].y * b[i].x;
		while(l < r && slop(q[r - 1], q[r]) > slop(q[r], i))
		    r --;
		q[++ r] = i;
	}
	printf("%lld\n", dp[tot]);
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值