斜率优化 笔记

说道斜率优化,我就想起今年下半年 湖南2008年的一个题。相信大家也不陌生(如果您是第一次学斜率优化,那估计挺陌生的)。题目叫:HNOI2008 玩具装箱TOY。题意大概是这个样子:

题意简述

给你一个序列和一个常数。请你把这个序列分成若干段。定义:一个段需要占用的空间就等于,然后需要的花费就是。其中这个没有限制,珂以无限长(只不过不是最优就是了)。请求出所有段的花费的最小值。

暴力思路

如果您来看斜率优化的话,相信您也不难退出这个题的暴力。设表示前个数分成一些段使得总花费最小。那么,。其中表示从这些数在一段的花费。为了稍微快一点,设表示的前缀和。那么就等于

但是这个的。能不能再快一点呢?(严格来讲,你必须要再快一点,要不然这个时间就卡不过去)

正片开始:斜率优化

把关于的项和关于的项单独提出来。变成:

。那么原式等于$(a(i)-b(j))^2$。

稍稍改变的定义,令是满足条件中最优的

那么:(推式子警告)

把这个式子放到平面直角坐标系上,以

对于每个来说,点就是一个决策点了。

而且,显然这是一个直线的关系,它的斜率就是,截距是。我们把直线向上平移,就是最小的。由于的值是确定的(我们在找的过程中,是不会变的),所以在同一个里面,我们相当于要让截距最小。

这样,对于每个(注意,和上面不一样,不在同一个里面了),珂能作为最优点选择出来的点应该组成一个下凸包(因为是单调递增的,所以斜率单调递增,即组成下凸包)。

的单调递增性我就不讲了)

为啥?考虑这种情况:

是珂供选择的三个决策点)

当前的话,如果还没滚蛋,应该是优的。如图

随着斜率的增加,也许会出现都优的情况。如图:

blog3.jpg

容易证明,此时一枝独秀,都珂以滚蛋了。这两种情况有一个共同点,就是都不能再优了。anyway,滚蛋吧。

还有一种情况(肯定还有,只有刚刚那一个是没法保证正确性的),就是最前面两个元素还要满足一个条件:第一个点和第二个点之间的斜率要,否则第一个就再也不珂能作为最优解出现了,因为此时随着斜率的单调递增,后面的斜率肯定会更大,在当下的的斜率中第二个都比第一个优,后面的话就不知道优到哪里去了。所以第一个就再起不能,滚了。

如图:

blog4.jpg

还是刚刚那张图,不过是另一种滚蛋方法。此时红色直线的斜率==。当红色直线斜率为的时候,就没有优了。如果再高一点,显然,还是没有优。所以以后这个都不珂能比更优了。就珂以滚蛋了。

说道这里,我们发现,这个东西珂以用一个单调队列维护。维护完一个正确的队列之后,就是最优解。

设队列为,首尾分别叫

总结一下,就是要维护两个关系:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 112345
#define int long long

#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define Tra(i,u) for(int i=G.Start(u);~i;i=G.Next(i))
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
int n,l;
int c[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(l);
F(i,1,n) R1(c[i]),c[i]+=c[i-1];
}

int Q[N];
int dp[N];
int a(int i){return c[i]+i;}
int b(int i){return c[i]+i+l+1;}
int x(int i){return b(i);}
int y(int i){return dp[i]+b(i)*b(i);}
int slope(int i,int j)//计算斜率
{
return (y(i)-y(j))/(x(i)-x(j));
}
void Soviet()
{
int head=1,tail=1;
F(i,1,n)
{
while(head<tail and slope(Q[head],Q[head+1])<2*a(i)) ++head;
//垃圾决策点出队列
dp[i]=dp[Q[head]]+(a(i)-b(Q[head]))* (a(i)-b(Q[head]));
//Q[head]就是最优的决策点。
while(head<tail and slope(i,Q[tail-1])<slope(Q[tail-1],Q[tail])) --tail;
Q[++tail]=i;
//把i加入队列
}
printf("%lld\n",dp[n]);
}
void IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}

还有一些答疑时间(根据作者自身出现的理解问题编写,以后支持评论功能之后珂能会根据评论区中的问题编写。)

Q:既然是最优的,为啥还要存着后面的点呢?
A:后面的点虽然现在不是最优的,但是它们很有潜力,等以后斜率上去之后,也许就滚蛋了,后面那些点就变成了最优。注意上面,我们说的是珂能成为最优的点,而不是现在最优的点。要为未来着想。

w