【算法介绍】斜率优化

用图像斜率可以将一些问题由O(n2)降到O(n)的算法。

下面结合一道经典题目介绍斜率优化

(洛谷链接)[HNOI2008]玩具装箱

这是一道典型的斜率优化题目。题目不再赘述,直接说与斜率优化相关的部分。

f [ i ] f[i] f[i]为填装前 i i i个玩具需要的最小花费, s [ i ] s[i] s[i]为前 i i i个物品的总长度。那么可以得到:
f [ i ] = m i n j = 0 i − 1 { f [ j ] + ( s [ i ] − s [ j ] + i − j − 1 − L ) 2 } f[i]=min_{j=0}^{i-1}\{ f[j]+(s[i]-s[j]+i-j-1-L)^2 \} f[i]=minj=0i1{f[j]+(s[i]s[j]+ij1L)2}
如果直接算的话复杂度为O(n2),会超时的,所以需要优化。

我们将这个式子进行整理和移项:
f [ i ] = f [ j ] + [ ( s [ i ] + i ) − ( s [ j ] + j + 1 + L ) ] 2 f[i]=f[j]+[(s[i]+i)-(s[j]+j+1+L)]^2 f[i]=f[j]+[(s[i]+i)(s[j]+j+1+L)]2
f [ i ] = f [ j ] + ( s [ j ] + j + 1 + L ) 2 + ( s [ i ] + i ) 2 − 2 ( s [ i ] + i ) ( s [ j ] + j + 1 + L ) f[i]=f[j]+(s[j]+j+1+L)^2+(s[i]+i)^2-2(s[i]+i)(s[j]+j+1+L) f[i]=f[j]+(s[j]+j+1+L)2+(s[i]+i)22(s[i]+i)(s[j]+j+1+L)
2 ( s [ i ] + i ) ( s [ j ] + j + 1 + L ) + f [ i ] − ( s [ i ] + i ) 2 = f [ j ] + ( s [ j ] + j + 1 + L ) 2 2(s[i]+i)(s[j]+j+1+L)+f[i]-(s[i]+i)^2=f[j]+(s[j]+j+1+L)^2 2(s[i]+i)(s[j]+j+1+L)+f[i](s[i]+i)2=f[j]+(s[j]+j+1+L)2
为了方便观察,我们令 a = s [ i ] + i , b = s [ j ] + j + 1 + L a=s[i]+i,b=s[j]+j+1+L a=s[i]+i,b=s[j]+j+1+L
现在这个式子变为
2 a b + f [ i ] − a 2 = f [ j ] + b 2 2ab+f[i]-a^2=f[j]+b^2 2ab+f[i]a2=f[j]+b2
其中 a a a只与 i i i有关, b b b只与 j j j有关。
由于我们求的是 f [ i ] f[i] f[i],而 j j j的相关信息是之前已经知道的,若把 f [ j ] + b 2 f[j]+b^2 f[j]+b2看做 y y y,把 b b b看做 x x x,我们便得到下面这个直线方程:
y = 2 a x + f [ i ] − a 2 y=2ax+f[i]-a^2 y=2ax+f[i]a2
这样, j j j的信息就被表示成了点。也就是说,我们要从当前 i i i个左右的点中找到一个点,使得 f [ i ] f[i] f[i]最小,这个点就是 j j j点。
点
(图:绿线是边界,黄线是目标直线,其斜率为 2 a 2a 2a

这个方程的斜率是 2 a ( 2 a > 0 ) 2a(2a>0) 2a(2a>0) f [ i ] f[i] f[i]最小时也就是这条直线在 y y y轴上的截距最小时,(和高中学过的线性规划类似)我们把一条斜率 2 a 2a 2a的直线从最下面开始慢慢向上平移,这条直线第一个碰到的点就是 j j j点。

显然,可能是 j j j点的点都在边界上。又因为 2 a 2a 2a是随着i增长的,于是我们可以用单调队列求 f [ i ] f[i] f[i]
(用slope( i i i, j j j)表示两个点的斜率)

1.对head: while(slope(head,head+1)<2a) head++;
2.求出 f [ i ] f[i] f[i]:f[i]=y+a^2-2ax
3.对tail: while(slope(tail-1,tail)>slope(tail-1,i)) tail--;
4. i i i点入队

对于第1步,理由是 2 a 2a 2a是递增的,此时斜率小于 2 a 2a 2a的两点的前一点一定不会是 j j j
对于第3步,理由是若slope(tail-1,tail)>slope(tail-1, i i i),那么tail点会被tail-1和 i i i点包围,这样tail就不再可能是 j j j

编程具体细节还会有需要注意的地方,但由于跟算法无关,在此就不说明了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;

#define a(i) (s[i]+i)
#define b(i) (s[i]+i+L+1)
#define x(i) b(i)
#define y(i) (f[i]+b(i)*b(i))
#define slope(i,j) (y(j)-y(i))*1.0/(x(j)-x(i))

const int N=5e4+10;
int n, L;
LL s[N], f[N];

int q[N], head, tail;

int main()
{
	scanf("%d%d", &n, &L);
	for(int i=1; i<=n; ++i)
	{
		scanf("%lld", s+i);
		s[i]+=s[i-1];
	}
	
	//
	head=tail=0;
	q[tail++]=0;
	
	for(int i=1; i<=n; ++i)
	{
		while(head+1<tail&&slope(q[head],q[head+1])<2*a(i)) head++;
		f[i]=y(q[head])+a(i)*a(i)-2*a(i)*b(q[head]);
		while(head+1<tail && slope(q[tail-2],q[tail-1])>slope(q[tail-2],i)) tail--;
		q[tail++]=i;
	}
	
	//
	printf("%lld\n", f[n]);
	return 0;
}

改进与注意
1. 对 slope(i,j)>slope(u,v)的改进:
( y i − y j ) ( x u − x v ) > ( y u − y v ) ( x i − x j ) (y_i-y_j)(x_u-x_v)>(y_u-y_v)(x_i-x_j) (yiyj)(xuxv)>(yuyv)(xixj)
2. tail-- 部分及 head++ 部分可用二分查找优化。
3. 注意 1. 中的 (xu-xv)>0 和 (xi-xj)>0

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值