『动态规划·斜率优化』土地购买(征用)

题目描述

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

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

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

题解

这道题中,我们首先可以排除一些无用的点。例如,当一个点的长和宽都小于另外的某一个点时,这个点就可以排除。具体实现方法是:

  • 将数组按照长为第一关键字,宽为第二关键字进行排序。
  • 维护一个单调栈,将小于当前节点宽的点都弹出栈;由于按照长来排序,所以可以保证弹出的点长和宽都小于当前点。
  • 然后我们就可以得到一个宽单调递减的栈,其长则单调不下降。

那么一段区间最答案的贡献是由左右端点造成的,因此答案一定是连续的一部分。

我们设 f [ i ] f[i] f[i]表示到了第 i i i块土地的最小经费。则有 f [ i ] = m i n ( f [ j ] + x [ i ] ∗ y [ j + 1 ] ) f[i]=min(f[j]+x[i]*y[j+1]) f[i]=min(f[j]+x[i]y[j+1]).

因为含有i和j的乘积项,因此我们可以大力展开然后斜率优化。

#include <bits/stdc++.h>

#define LL long long

using namespace std;

LL n;
struct node
{
	LL x,y;
	friend bool operator < (node p1,node p2)
	{
		if (p1.x == p2.x) return p1.y < p2.y;
		else return p1.x < p2.x;
	}
} ;
LL top = 0;
LL f[100000];
LL x[100000];
LL y[100000];
LL q[100000];
LL st[100000];
node a[100000];

void init(void)
{
	cin>>n;
	for (LL i=1;i<=n;++i) 
		cin>>a[i].x>>a[i].y;
	sort(a+1,a+n+1);
	for (LL i=1;i<=n;++i)
	    x[i] = a[i].x, y[i] = a[i].y;
	return;
}

void work(void)
{
	for (LL i=1;i<=n;++i)
	{
		while (y[i] >= y[st[top]] && top > 0) top --;
		st[++top] = i;
	}
	for (LL i=1;i<=top;++i) x[i] = x[st[i]];
	for (LL i=1;i<=top;++i) y[i] = y[st[i]];
	return;
}

double k(LL p1,LL p2)
{
	//p1 > p2
	//x(p1) < x(p2)
	#define X(i) (y[i+1])
	#define Y(i) (-f[i])
	double dty = Y(p2)-Y(p1);
	double dtx = X(p2)-X(p1);
	return 1.0*dty/dtx;
}

void dp(void)
{
	n = top;
	LL h = 1, t = 1;
	for (LL i=1;i<=n;++i)
	{
		while (h < t && k(q[h+1],q[h]) < x[i]) h ++;
		f[i] = f[q[h]]+x[i]*y[q[h]+1];
		while (h < t && k(i,q[t]) < k(i,q[t-1])) t --;
		q[++t] = i;
	}
	cout<<f[n]<<endl;
	return;
}

int main(void) 
{
	freopen("acquire.in","r",stdin);
	freopen("acquire.out","w",stdout);
	init();
	work();
	dp();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值