斜率优化bzoj 1597

题目链接:

https://www.lydsy.com/JudgeOnline/problem.php?id=1597

看到题目,时间复杂度至少是 O ( N l o g N ) O(NlogN) O(NlogN) O ( N s q r t ( N ) ) O(Nsqrt(N)) O(Nsqrt(N))
前者的概率较大,此题有计算内乘积,应该与分块无关(早就知道是斜率优化

假设长为 a a a值,宽为 b b b
观察题目, 可以猜想一下决策会不会有连续性,猜想决策可能与排序有关,先按长 ( a a a) 排序。在思考一会,就会发现该猜想是对的,因为每个总方案中,如果存在一组土地 a a a值不连续,那该方案一定不是唯一的最优解。

证明:假设存在一个方案使得将土地按 a a a值升序排列后存在 i , j , k i,j,k i,j,k使得 a i < a j < a k a_{i}<a_{j}<a_{k} ai<aj<ak i , k i,k i,k分在一组中,而 j j j在另一组中。若 b j < b i b_j<b_i bj<bi,则将 i , j , k i,j,k i,j,k分在一组肯定会更优,因为其他不变,把 j j j移动到 i , k i,k i,k这一组, j j j原来的那组值会更小,而 i , j , k i,j,k i,j,k这一组不会变,因为 j j j的长和宽在这一组中均不为最大值。同理,可证明另一种情况。(有兴趣可以自行证明)

这样,我们就可以先按 a a a值排序。又因为如果某一块土地 b b b值比任何一块在它右边的土地小,那么一个最优解一定是将这块土地与那块土地合并,因为这样不会产生任何影响,就可以去除满足这个条件的土地

在这一步后,整个数组也就是 a a a值递增, b b b值递减,那么简单看一下就知道可以dp了
f [ i ] f[i] f[i]为前 i i i块土地的最小花费
方程就是 f [ i ] = f [ j ] + b [ j + 1 ] ∗ a [ i ] f[i]=f[j]+b[j+1]*a[i] f[i]=f[j]+b[j+1]a[i]
看到乘积,自然想到斜率优化, p i ( − b [ j + 1 ] , f [ i ] ) , k i = a [ i ] p_{i}(-b[j+1],f[i]),k_{i}=a[i] pi(b[j+1],f[i])ki=a[i]
就可以愉快的斜率优化了,时间复杂度 O ( N ) O(N) O(N)

C o d e : Code: Code:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=50000;
struct w{
	int a,b;
}a[MAXN+10],b[MAXN+10];
struct po{
	int x,y;
}s[MAXN+10];
int f[MAXN+10],q[MAXN+10],fr,ed;
inline bool cmp(const w&,const w&);
inline bool cmp_slope(po,po,int);
inline bool cmp_slope2(po,po,po);
inline int read();

signed main(){
	freopen ("1597.in","r",stdin);
	freopen ("1597.out","w",stdout);
	int n=read();
	for (register int i=1;i<=n;++i){
		a[i].a=read();
		a[i].b=read();
	}
	stable_sort(a+1,a+n+1,cmp);
	for (register int i=n;i>=1;--i){
		if (a[i].b > a[q[fr]].b)	q[++fr]=i;	
	}
	n=fr;	fr=0;
	for (register int i=1;i<=n;++i)	b[i]=a[q[n-i+1]];
	for (register int i=1;i<=n;++i)	a[i]=b[i];
	s[0].y=0;	s[0].x=-a[1].b;
	for (register int i=1;i<=n;++i){
		int k=a[i].a;
		while (fr>ed && cmp_slope(s[q[ed]],s[q[ed+1]],k))	++ed;
		f[i]=f[q[ed]]-s[q[ed]].x*a[i].a;
		s[i].y=f[i];	s[i].x=-a[i+1].b;
		while (fr>ed && cmp_slope2(s[q[fr-1]],s[q[fr]],s[i]))	--fr;
		q[++fr]=i;
	}
	printf("%lld\n",f[n]);
	return 0;
}

inline int read(){
	int x=0;
	char c=getchar();
	while (!isdigit(c))c=getchar();
	while (isdigit(c))x=(x<<3)+(x<<1)+(c&15),c=getchar();
	return x;
}

inline bool cmp(const w &x,const w &y){
	return x.a<y.a;
}

inline bool cmp_slope(po x,po y,int k){
	return ((double)(y.y-x.y))/((double)(y.x-x.x)) <= (double)k;
}

inline bool cmp_slope2(po x,po y,po z){
	return ((double)(y.y-x.y))/((double)(y.x-x.x)) >= ((double)(z.y-y.y))/((double)(z.x-y.x));	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值