《玩具装箱》:题目大意:有 n n n 个玩具,每个玩具有一个代价 c i c_i ci ,你要将玩具分成若干连续段,一个连续段 [ l , r ] [l,r] [l,r] 的代价为 ( s u m r − s u m l + r − l − S ) 2 (sum_r-sum_l+r-l-S)^2 (sumr−suml+r−l−S)2 其中 s u m sum sum 是 c c c 的前缀和, S S S 是一个常量。
设 f i f_i fi 表示以 i i i 为结尾装箱的最小代价。
显然 f i = min j ∈ [ 1 , i − 1 ] ( f j + ( s i − s ( j + 1 ) − 1 + i − ( j + 1 ) − S ) 2 ) = min j ∈ [ 1 , i − 1 ] ( f j + ( s i − s j + i − j − S − 1 ) 2 ) f_i=\min_{j\in[1,i-1]}(f_j+(s_i-s_{(j+1)-1}+i-(j+1)-S)^2)\\=\min_{j\in[1,i-1]}(f_j+(s_i-s_j+i-j-S-1)^2) fi=minj∈[1,i−1](fj+(si−s(j+1)−1+i−(j+1)−S)2)=minj∈[1,i−1](fj+(si−sj+i−j−S−1)2) 。
那么把含 i i i 项和含 j j j 项拎出来:记 A = s i + i , B = s j + j + S + 1 A=s_i+i,B=s_j+j+S+1 A=si+i,B=sj+j+S+1 。
那么方程就变成了: f i = min ( f j + ( A − B ) 2 ) → min ( f j + A 2 − 2 A B + B 2 ) f_i=\min(f_j+(A-B)^2)\rightarrow\min(f_j+A^2-2AB+B^2) fi=min(fj+(A−B)2)→min(fj+A2−2AB+B2) 。
再次把含 i i i 项移到一边,把含 j j j 项移到一边: f j + B 2 − 2 A B = f i − A 2 f_j+B^2-2AB=f_i-A^2 fj+B2−2AB=fi−A2 。
这样看不出什么,但是可以再移一下项: f j + B 2 = 2 A B + f i − A 2 f_j+B^2=2AB+f_i-A^2 fj+B2=2AB+fi−A2 。
于是可以发现一个事情,这个方程可以看成一条一次函数 y = k x + b y=kx+b y=kx+b : f j + B 2 ( y ) , 2 A ( k ) , B ( x ) , f i − A 2 ( b ) f_j+B^2(y),\ \ 2A(k),\ \ B(x),\ \ f_i-A^2(b) fj+B2(y), 2A(k), B(x), fi−A2(b) 。
那这条直线经过了点 ( B , f j + B 2 ) : j ∈ [ 1 , i − 1 ] (B,f_j+B^2):j\in[1,i-1] (B,fj+B2):j∈[1,i−1] ,所以可以考虑把每一个 i i i 都在计算完 f f f 后按照上述坐标映射为坐标系里的一个点。
因为 A A A 是常数,于是我们最小化 f i f_i fi 的愿望就等价于最小化 f i − A 2 f_i-A^2 fi−A2 。也就是最小化截距 b b b 。
考虑这个截距最小在哪里。
这里自己刚开始没想通,截距最小不是 − ∞ -\infty −∞ 吗?
不是,因为直线必须要经过一个点 j : j ∈ [ 1 , i − 1 ] j:j\in[1,i-1] j:j∈[1,i−1] 。
这条红线就是 2 A 2A 2A ,连出来的是 j ∈ [ 1 , i − 1 ] j\in[1,i-1] j∈[1,i−1] 构成的上凸包,相邻两条线之间斜率递增,不断将这个直线左移,这条红线第一个接触到的点记为“最突出的点”。
那么最小的截距就是:经过最“突出”的点、斜率为 2 A 2A 2A 的直线的截距。
显然,对于 i i i 的增大,横坐标 B B B 具有单调性,也就是每次加入的点在坐标系上一定是最靠右的。
所以新加入的点一定处于凸壳,考虑用栈维护这个凸壳。
观察一下要求的 j j j 的条件,记 j . n t j.nt j.nt 为凸壳里 j j j 后面的点, k x → y k_{x\to y} kx→y 表示经过点 x x x 和点 y y y 的直线的斜率。
发现 j j j 就是第一个满足 k j → j . n t > 2 A ( 斜率 ) k_{j\to j.nt}>2A(斜率) kj→j.nt>2A(斜率) 的点。
怎么实现?可以在凸壳的栈上二分,但是还有更优秀的方法。
看见了一件事:这个凸壳中的任意一点与下一个点形成直线的斜率如果有一次不大于 2 A 2A 2A ,那么就再也无法大于 2 A 2A 2A ,因为斜率具有单调性,只会越来越大。
那就可以直接弹掉凸壳全部最靠前并且不大于 2 A 2A 2A 的点,然后剩下的队首就是“最突出的点”。
所以要用队列维护凸壳。计算完以后将新的点加入凸壳。
每个点至多进队一次出队一次,故时间复杂度为 O ( n ) O(n) O(n) 。
x x x 不单调?
参见OI-Wiki。本文留坑。
小结
通过这一道经典的例题,可以发现斜率优化在一些时候能有效地避免劣质子状态的枚举,从而优化时间复杂度。
利用斜率优化解题的关键在于转移方程的化简。
通常来说,把含 i i i 项(常量)、含 j j j 项(变量)以及含 i i i 和 j j j 的项提出来,写成一次函数的形式,然后建立坐标系,利用单调性,从而解题。