斜率优化
[HNOI2008] 玩具装箱
状态转移方程:
f
i
=
m
i
n
(
f
i
,
f
j
+
(
s
u
m
i
+
i
−
s
u
m
j
−
j
−
L
)
2
)
i
>
j
f_i=min(f_i,f_j+(sum_i+i-sum_j-j-L)^2){i>j}
fi=min(fi,fj+(sumi+i−sumj−j−L)2)i>j
设A为
s
u
m
i
+
i
sum_i+i
sumi+i,B为
s
u
m
j
+
j
+
L
+
1
sum_j+j+L+1
sumj+j+L+1
简化可得:
f
i
=
m
i
n
(
f
i
,
f
j
+
A
2
−
2
A
B
+
B
2
)
f_i=min(f_i,f_j+A^2-2AB+B^2)
fi=min(fi,fj+A2−2AB+B2)
稍微分解一下,有:
f
i
=
f
j
+
A
2
−
2
A
B
+
B
2
f
j
+
B
2
=
2
A
B
+
f
i
−
A
2
f_i=f_j+A^2-2AB+B^2 \\ f_j+B^2=2AB+f_i-A^2
fi=fj+A2−2AB+B2fj+B2=2AB+fi−A2
设
f
j
+
B
2
f_j+B^2
fj+B2 为点
y
y
y,
2
A
2A
2A 为
k
k
k,
B
B
B 为
x
x
x,
f
i
−
A
2
f_i-A^2
fi−A2 为
b
b
b。
考虑一个确定的点 ( B , f j + B 2 ) (B,f_j+B^2) (B,fj+B2), k = 2 A k=2A k=2A 的最小截距。
对于每个确定的 i i i,可令斜率为 h i h_i hi 的直线过每个决策点,都可求得一个截距。根据状态转移方程可知,其中截距最小的直线方程所经过的决策点即为左右决策。
斜率:
先看一张图:
- 斜率(↑↑↑)
斜率就是坡度,是高度的平均变化率,用坡度来刻划道路的倾斜程度,也就是用坡面的切直高度和水平长度的比,相当于在水平方向移动一千米,在切直方向上升或下降的数值,这个比值实际上就表示了坡度的大小。
即:设 ( 0 , 0 ) (0,0) (0,0) 点为 a a a, ( 3 , 0 ) (3,0) (3,0) 点为 b b b,则点 B B B 的斜率为 ∣ b − a ∣ B − b \frac{|b-a|}{B-b} B−b∣b−a∣。
以下称 x j x_j xj 为 x x x 轴的 j j j 点, y i y_i yi 为在 y y y 轴的 i i i 点。
在绝v额集合中筛选出部分决策,使得在
x
j
x_j
xj 递增的顺序下,相邻的决策点所炼成的线段的斜率单调递增。对于任意连续的三个所选决策
j
i
−
1
,
j
i
,
j
i
+
1
j_{i-1},j_{i},j_{i+1}
ji−1,ji,ji+1,都有:
f
j
i
−
f
j
i
−
1
x
j
i
−
x
j
i
−
1
<
f
j
i
+
1
−
f
j
i
f
j
i
+
1
−
f
j
i
\frac{f_{j_{i}}-f_{j_{i-1}}}{x_{j_i}-x_{j_{i-1}}}<\frac{f_{j_{i+1}}-f_{j_{i}}}{f_{j_{i+1}}-f_{j_i}}
xji−xji−1fji−fji−1<fji+1−fjifji+1−fji
在对应坐标系中,相邻点之间连成的线段呈现出“下凸”形态,即为“凸包”。
- 凸包(↑↑↑)
若斜率函数 h i h_i hi 与 x j x_j xj 均为单调递增函数,随着 j j j 的递增,决策点的横坐标也单调递增,新决策必定会出现在整个凸包的最右端。又因为斜率函数具有单调性,所以每次需要求解的直线斜率 h i h_i hi 也单调递增。决策集合仅保留下凸曲线上相邻现代斜率大于 h i h_i hi 的剩余决策点,所以曲线最左端的决策点即为最优决策。
- 最优决策、最优斜率、截距(设B点为最佳决策点)
根据如上性质,我们不难想出,用双端队列 q[l~r]
维护下凸曲线,队列中保存部分决策,对应下凸曲线上的决策点,满足
x
i
x_i
xi 和
h
i
h_i
hi 都递增。
具体实施方案:
- 为了保证队头即为最优决策,仅需保留下凸曲线上斜率大于 h i h_i hi 的点,从队头开始检查决策 q l q_l ql 和后续决策 q l + 1 q_{l+1} ql+1 对应点连接线的斜率。若该斜率小等于 h i h_i hi,则把 q l q_l ql 出队,继续检查寻得队头和后续决策,直至线段斜率大于 h i h_i hi。
- 直接取队头决策 j = q l j=q_l j=ql 为最优决策,进行状态转移。
- 将当前状态 i i i 为新的决策从队尾插入。在插入前需要维护凸曲线性质,即三个决策点 q r − 1 , q r , i q_{r-1},q_r,i qr−1,qr,i 对应的相邻线段需要满足斜率单调递增,否则吧决策 q r q_r qr 出队,继续检查 q r − 2 , q r − 1 , i q_{r-2},q_{r-1},i qr−2,qr−1,i,直至满足要求。设此操作一共进行了 n n n 轮,则最终需要判断的三个状态为 q r − n , q r − n + 1 , i q_{r-n},q_{r-n+1},i qr−n,qr−n+1,i。
时间复杂度: O ( N ) O(N) O(N)。
- 若斜率函数 h i h_i hi 不满足单调性,则 x j x_j xj 为单调递增函数,队头不为最优决策,须保留整个下凸曲线,可在队列中二分查找,求出一个位置 k k k,使得:
∀ p < k ∀ q > k h p < h k < h q \forall\ p\ <\ k \\ \forall\ q\ >\ k \\ h_p<h_k<h_q ∀ p < k∀ q > khp<hk<hq
若满足以上条件,则 k k k 为最优决策。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
AC Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,l;
const ll MAXN=5e4+5;
ll q[MAXN],sum[MAXN],f[MAXN];
ll head=1,tail=1;
ll j;
inline ll x(ll j)//x坐标
{
return sum[j];
}
inline ll y(ll j)//y坐标
{
return f[j]+(sum[j]+l)*(sum[j]+l);
}
inline double slope(ll i,ll j)//计算
{
return (double)(y(j)-y(i))/(x(j)-x(i));
}
inline ll compute(ll i,ll j)//代价公式
{
return (sum[i]-sum[j]-l)*(sum[i]-sum[j]-l);
}
int main(){
scanf("%lld%lld",&n,&l);
l++;//因为一直都是-l-1,干脆直接变为 -(l+1)
for(ll i=1;i<=n;i++)
{
scanf("%lld",&sum[i]);
sum[i]+=sum[i-1]+1;//前缀和
}
q[tail]=0;
for(ll i=1;i<=n;i++)//dp
{
while(head<tail&&slope(q[head],q[head+1])<=(sum[i]<<1))//出队首条件
head++;
j=q[head];//队首
f[i]=f[j]+compute(i,j);//计算
while(head<tail&&slope(q[tail-1],q[tail])>=slope(q[tail-1],i))//出队末条件
tail--;
q[++tail]=i;//最优决策入队
}
printf("%lld\n",f[n]);
return 0;
}