一.斜率优化的 作用.
斜率优化主要针对这样一类方程进行优化:
f
[
i
]
=
a
i
+
min
j
=
0
i
−
1
{
f
[
j
]
+
b
j
+
c
i
∗
d
j
}
f[i]=a_i+\min_{j=0}^{i-1}\{ f[j]+b_j+c_i*d_j \}
f[i]=ai+j=0mini−1{f[j]+bj+ci∗dj}
其中 min \min min也可以换成 max \max max, a i , b i , c i , d i a_i,b_i,c_i,d_i ai,bi,ci,di均表示一个可以通过 i i i直接得到的项(包括已求出的 f [ i ] f[i] f[i]).
当然这个方程还不是最通用的,有时候还会有更加毒瘤复杂的限制…
二.斜率优化的通用优化方式.
以上述方程为模板,我们可以假设
e
i
=
f
[
i
]
+
b
i
e_i=f[i]+b_i
ei=f[i]+bi,方程变为:
f
[
i
]
=
a
i
+
min
j
=
0
i
−
1
{
e
j
+
c
i
∗
d
j
}
f[i]=a_i+\min_{j=0}^{i-1}\{ e_j+c_i*d_j \}
f[i]=ai+j=0mini−1{ej+ci∗dj}
然后我们求一下从
k
k
k转移到
i
i
i比从
j
j
j转移到
i
i
i更优要满足什么条件:
d
j
≤
d
k
e
j
+
c
i
∗
d
j
≥
e
k
+
c
i
∗
d
k
e
j
−
e
k
≥
c
i
(
d
k
−
d
j
)
e
k
−
e
j
d
k
−
d
j
≤
−
c
i
d_j\leq d_k\\ e_j+c_i*d_j\geq e_k+c_i*d_k\\ e_j-e_k\geq c_i(d_k-d_j)\\ \frac{e_k-e_j}{d_k-d_j}\leq -c_i
dj≤dkej+ci∗dj≥ek+ci∗dkej−ek≥ci(dk−dj)dk−djek−ej≤−ci
也就是说若 d j ≤ d k d_j\leq d_k dj≤dk,则 k k k比 j j j优必然要满足 e k − e j d k − d j ≤ − c i \frac{e_k-e_j}{d_k-d_j}\leq -c_i dk−djek−ej≤−ci.
这有什么用呢?考虑这个式子的几何意义,把 ( d i , e i ) (d_i,e_i) (di,ei)放到直角坐标系上,那么这个式子的意思就是说, j j j比 k k k优的条件是经过 ( d j , e j ) (d_j,e_j) (dj,ej)与 ( d k , e k ) (d_k,e_k) (dk,ek)的直线斜率小于等于 − c i -c_i −ci.
到这一步之后,我们考虑若有这样子三个点:
考虑出现了上凸的情况,直线
A
B
AB
AB的斜率大于直线
A
C
,
B
C
AC,BC
AC,BC,这说明在点
B
B
B优于点
A
A
A之前点
C
C
C就已经优于点
A
A
A,并且此时点
C
C
C一定优于点
B
B
B,也就是说点
B
B
B完全没用了.
那么这个时候候选最优点会呈现怎么样一个形状呢?显然是斜率递增,也就是一个下凸壳.
那么对于一个确定的 c i c_i ci,我们应该怎么在候选点中找到最优点进行转移呢?显然是找到第一条斜率大于 − c i -c_i −ci的线段,选择这条线段更靠左的端点即可.
那么我们用各种方式维护这个下凸壳即可,具体维护因题目而异,但只要理解其本质,那么不管用单调队列、二分、cdq分治、set还是平衡树都是一样的.
三.例题与代码.
题目:BZOJ1010.
题目大意:给定一个长度为
n
n
n的序列
a
i
a_i
ai,现在要求划分为若干段,每段
l
i
,
r
i
l_i,r_i
li,ri贡献为
(
r
i
−
l
i
−
L
+
∑
j
=
l
i
r
i
a
j
)
2
(r_i-l_i-L+\sum_{j=l_i}^{r_i}a_j)^2
(ri−li−L+∑j=liriaj)2,其中
L
L
L是一个常数,求最小贡献和.
1
≤
n
≤
5
∗
1
0
4
1\leq n\leq 5*10^4
1≤n≤5∗104.
设
f
[
i
]
f[i]
f[i]表示前
i
i
i个数划分成若干段的最小贡献和,
s
i
s_i
si为
a
i
a_i
ai的前缀和,可以列出DP方程:
f
[
i
]
=
min
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]=j=0mini−1{f[j]+(si−sj+i−j−1−L)2}
设
b
i
=
s
i
+
i
−
1
−
L
,
c
i
=
s
i
+
i
b_i=s_i+i-1-L,c_i=s_i+i
bi=si+i−1−L,ci=si+i,方程变为:
f
[
i
]
=
min
j
=
0
i
−
1
{
f
[
j
]
+
(
b
i
−
c
j
)
2
}
f
[
i
]
=
min
j
=
0
i
−
1
{
f
[
j
]
+
b
i
2
−
2
b
i
c
j
+
c
j
2
}
f
[
i
]
=
b
i
2
+
min
j
=
0
i
−
1
{
f
[
j
]
−
2
b
i
c
j
+
c
j
2
}
f[i]=\min_{j=0}^{i-1} \{ f[j]+(b_i-c_j)^2 \}\\ f[i]=\min_{j=0}^{i-1} \{ f[j]+b_i^2-2b_ic_j+c_j^2 \}\\ f[i]=b_i^2+\min_{j=0}^{i-1} \{ f[j]-2b_ic_j+c_j^2 \}
f[i]=j=0mini−1{f[j]+(bi−cj)2}f[i]=j=0mini−1{f[j]+bi2−2bicj+cj2}f[i]=bi2+j=0mini−1{f[j]−2bicj+cj2}
设
d
i
=
f
i
+
c
i
2
d_i=f_i+c_i^2
di=fi+ci2,方程变为:
f
[
i
]
=
b
i
2
+
min
j
=
0
i
−
1
{
d
j
−
2
b
i
c
j
}
f[i]=b_i^2+\min_{j=0}^{i-1} \{ d_j-2b_ic_j \}
f[i]=bi2+j=0mini−1{dj−2bicj}
现在就是一个标准的斜率优化形式了,推一下:
j
<
k
⇔
c
j
<
c
k
d
j
−
2
b
i
c
j
≥
d
k
−
2
b
i
c
k
d
j
−
d
k
≥
−
2
b
i
(
c
k
−
c
j
)
d
k
−
d
j
c
k
−
c
j
≤
2
b
i
j<k\Leftrightarrow c_j<c_k\\ d_j-2b_ic_j\geq d_k-2b_ic_k\\ d_j-d_k\geq -2b_i(c_k-c_j)\\ \frac{d_k-d_j}{c_k-c_j}\leq 2b_i\\
j<k⇔cj<ckdj−2bicj≥dk−2bickdj−dk≥−2bi(ck−cj)ck−cjdk−dj≤2bi
发现这个式子是要维护一个下凸壳也就是斜率递增,直接用一些数据结构大力维护即可.
不过容易发现的是, b i b_i bi和 c i c_i ci都是单调递增的,所以我们可以直接用单调队列来维护.
时间复杂度 O ( n ) O(n) O(n).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=50000;
int n,m;
LL a[N+9],b[N+9],c[N+9];
LL dp[N+9],d[N+9];
int q[N+9],hd,tl;
double Query_slope(int p0,int p1){return 1.0*(d[p1]-d[p0])/(c[p1]-c[p0]);}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i){
scanf("%lld",&a[i]);
a[i]+=a[i-1];
b[i]=(c[i]=a[i]+i)-m-1;
}
}
Abigail work(){
q[hd=tl=1]=0;
for (int i=1;i<=n;++i){
for (;hd<tl&&Query_slope(q[hd],q[hd+1])<=2.0*b[i];++hd);
d[i]=(dp[i]=b[i]*b[i]+d[q[hd]]-2*b[i]*c[q[hd]])+c[i]*c[i];
for (;hd<tl&&Query_slope(q[tl-1],q[tl])>=Query_slope(q[tl],i);--tl);
q[++tl]=i;
}
}
Abigail outo(){
printf("%lld\n",dp[n]);
}
int main(){
into();
work();
outo();
return 0;
}