斜率优化详解(一)
花了好長的時間,我終于學(xué)會了斜率優(yōu)化。
說實在的,斜率優(yōu)化其實并不難,但是老師說的和網(wǎng)上的博客寫的不夠詳細(xì),導(dǎo)致我在很長一段時間都無法弄懂。
言歸正傳,我們從一道題講起
【題意】
一個包裝運輸公司,只生產(chǎn)一種容量為L的包裝盒。如果要裝容量為X的物品,則需要花錢修改包裝盒的尺寸,花費為(X - L)^2。
現(xiàn)在有N個物品需要裝入包裝盒,每個物品的容量為Ci。
(1)、可以多個物品裝入一個包裝盒
(2)、同一個包裝盒的物品編號必須是連續(xù)的
(3)、同個包裝盒的物品排成直線,相鄰兩個物品之間要加一塊容量為1的隔板。
目標(biāo):總花費最小。
【輸入格式】
第一行兩個整數(shù),分別為N和L。
下來N個數(shù)字Ci,按編號從小到大輸入每個物品的容量。
1<=N<=50000,1<=L,Ci<=10^7
【輸出格式】
一個整數(shù),總花費的最小值。
【樣例輸入】
5 4
3 4 2 1 4
【樣例輸出】
1
(這道題相信大家都做過了)。
先是推出dp公式
讓 f[i] 表示1~i的最小花費。
只要思考幾分鐘,就能推出dp方程
不過,這個dp的時間復(fù)雜度是O(N^2),對于1<=N<=50000的數(shù)據(jù)是一定會炸掉的
所以我們考慮用單調(diào)隊列維護(hù)
于是把方程拆開得到
f[i] = min ( f[j] + s[i]^2 - 2*s[i]*(s[j]+L) + ( s[j]+L )^2 )當(dāng)我信心十足的開始打單調(diào)隊列時,突然發(fā)現(xiàn)有一個 - 2 * s[i] * (s[j]+L)
也就是說,單調(diào)隊列是用不了的,從而引入斜率優(yōu)化
在我看了無數(shù)博客以后,我歸納總結(jié)出了斜率優(yōu)化的兩種不同推法
一種是利用化簡公式證明決策單調(diào)性,然后推公式,我管它叫公式法
另外一種是將決策點轉(zhuǎn)為(x,y),x和y都是代數(shù)式,并且?guī)肫矫嬷苯亲鴺?biāo)系,利用圖像來進(jìn)行斜率優(yōu)化,我管它叫圖像法
雖然兩種方法的代碼都是大同小異的,但是理解起來會有一些不同
我個人比較喜歡圖像法(簡單易懂)
一、公式法
注:因為用*號會出現(xiàn)一些奇怪的問題,所以我放到代碼里面 & 這一段裝載于caioj1.證明決策單調(diào)性假設(shè)j1<j2<i,在狀態(tài)i處的j2決策不比j1決策差(心里想著淘汰j1),即要滿足:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2 則對于i后的所有狀態(tài)t,是否j2也不比就j1差?(術(shù)語:證明決策單調(diào)性)即f[j2]+(s[t]-s[j2]-L)^2 < f[j1]+(s[t]-s[j1]-L)^2 容易理解s[t]=s[i]+v 所以得到(1)不等式:f[j2]+(s[i]-s[j2]-L+v)^2<f[j1]+(s[i]-s[j1]-L+v)^2 因為已知(2)不等式:f[j2]+(s[i]-s[j2]-L )^2<f[j1]+(s[i]-s[j1]-L )^2 所以化簡(1)不等式:把s[i]-s[j2]-L看成一個整體,v看成一個整體,得到 f[j2]+(s[i]-s[j2]-L )^2 +2*v*(s[i]-s[j2]-L)+v^2 <f[j1]+(s[i]-s[j1]-L )^2+2*v*(s[i]-s[j1]-L)+v^2 比較(2)不等式:左邊多了一部分:2*v*(s[i]-s[j2]-L)+v^2 右邊多了一部分:2*v*(s[i]-s[j1]-L)+v^2 所以我們只需要證: 2*v*(s[i]-s[j2]-L)+v^2<=2*v*(s[i]-s[j1]-L)+v^2 即:(s[i]-s[j2]-L) <= (s[i]-s[j1]-L) 即: -s[j2] <= -s[j1] 即:s[j1]<s[j2]這是肯定的,所以得證。總結(jié):對于當(dāng)前i:j2比j1好,那么對于t(i<t)來說一樣:j2一樣比j1好,所以當(dāng)前i選擇j2,淘汰j1,以后的t也不會在j2存在的時候選擇j1,所以i的時候就可以永久淘汰j1.2.求斜率方程:因為f[j2]+(s[i]-s[j2]-L)^2 < f[j1]+(s[i]-s[j1]-L)^2 展開: f[j2]+(s[i]-L)^2-2*(s[i]-L)*s[j2]+s[j2]^2<f[j1]+(s[i]-L)^2-2*(s[i]-L)*s[j1]+s[j1]^2 即f[j2]-2*(s[i]-L)*s[j2]+s[j2]^2<f[j1]-2*(s[i]-L)*s[j1]+s[j1]^2 即f[j2]+s[j2]^2-2*(s[i]-L)*s[j2]<=f[j1]+s[j1]^2-2*(s[i]-L)*s[j1] 即[ (f[j2]+s[j2]^2)-(f[j1]+s[j1]^2) ] < 2*(s[i]-L)*s[j2]-2*(s[i]-L)*s[j1] 即[ (f[j2]+s[j2]^2)-(f[j1]+s[j1]^2) ] /(s[j2]-s[j1]) < 2*(s[i]-L) 對于j來說:制造的點坐標(biāo) Y=f[j]+s[j]^2X=s[j]我們用隊列l(wèi)ist在存有意義的決策點,list中相鄰兩點的斜率遞增(隊列中的點形成一個下凸殼),而且都大于2*(s[i]-L),那么隊列頭對于i來說就是最優(yōu)決策點。加入決策i時,令隊尾為list[tail],前一個為list[tail-1] 斜率函數(shù)slop(點1,點2)滿足: slop(list[tail-1],list[tail]) > slop(list[tail],i) 時,那么隊尾list[tail]在三者(list[tail-1],list[tail],i)對于未來的t絕對不會是最優(yōu)的策略,所以將其彈出tail--; 最后遇到了:slop(list[tail-1],list[tail]) < slop(list[tail],i),保證了隊列的相鄰兩點的斜率遞增所以加入i: list[++tail]=i;呵呵,這么一大堆文字真是不好懂,慢慢品味吧^_^如果是數(shù)論很好的同學(xué),那么這種方法是很適合你的
二、圖像法
在O(N^2 ) 的暴力代碼中,求f[i]的值要for(1-i),然后繼承最優(yōu)決策點
前面也說過了,繼承的公式是
我們嘗試把這個式子化成 y = kx + b 的形式
先考慮什么是y,k , x 和 b
因為我們需要把決策點1到i-1變成平面直角坐標(biāo)系中的點,所以x和y的代數(shù)式都是含有j的點
x = s[i] + L y = f[j] + ( s[i]+L )^2于是我們就可以順理成章的把原來的公式轉(zhuǎn)化成
f[i]+(s[i]+L)^2 = 2*s[i]*(s[j]+L) + (f[i]- s[i]^2 )其中 y = f[j]+(s[j]+L)^2 k = 2*s[i] x = s[j]+L b = f[i]- s[i]^2所以每一個決策點都可以變?yōu)槠矫嬷苯亲鴺?biāo)系中的點
因為我們要求的是最小的f[i],也就是令b最小,即讓截距最小(b就是截距)
其實這個式子里面還有一個隱藏條件,就是x,y,k都是單調(diào)遞增的
也就是說點1到點i-1的x坐標(biāo)和y坐標(biāo)都是逐漸增大,i點確定的斜率也是逐漸增大的 ( 和公式法的證明決策單調(diào)性是一樣的)
放一張圖
這張圖是i確定的斜率,對于前面每一個決策點的繼承
很明顯,p4是最優(yōu)的
又因為前面已經(jīng)得出了k是單調(diào)遞增的,因為以后k不斷增大,所以p1,p2,p3以后也不會用到,因此在單調(diào)隊列中會踢掉p1,p2,p3
因此我們得出了一個結(jié)論,在隊列中的點依次連接會成一個下凸殼
維護(hù)隊頭:
如果list[head]和list[head+1]的斜率小于i確定的斜率,就踢掉隊首
即while ( head<tail && slop(list[head],list[head+1])<2*s[i] ) head ++ ;
具體如下圖:
1繼承隊首
2踢掉隊首
以上是對隊首的維護(hù)
維護(hù)隊尾:
在我們求出f[i]以后,就要將i放入隊列里面
如果list[tail-1]和list[tail]的斜率小于list[tail]和i的斜率,就踢掉list[tail],因為隨著i確定的斜率不斷增大,list[tail]不會再被使用,如圖
總結(jié)
公式法是將表達(dá)式轉(zhuǎn)變成
y2?y1x2?x1<t\frac{y2-y1}{x2-x1}<tx2?x1y2?y1?<t 的形式
圖像法是將表達(dá)式轉(zhuǎn)化成
y=kx+by=kx+by=kx+b 的形式
例題代碼
#include <cstdio> #include <cstring> #include <iostream>using namespace std ;typedef long long LL ;const int N = 5e4 + 10 ; LL s[N] , f[N] ; int list[N] ;double Y ( int j ) {return double(f[j]+s[j]*s[j]) ; } double X ( int j ) {return double(s[j]) ; } double slope ( int j2 , int j1 ) {return ( Y(j2)-Y(j1) )/( X(j2)-X(j1) ) ; } int main() {LL n , L , x ;scanf ( "%lld%lld" , &n , &L ) ; L ++ ;s[0] = 0LL ;for ( int i = 1 ; i <= n ; i ++ ) {scanf ( "%lld" , &x ) ;s[i] = s[i-1] + x + 1 ;}int head = 1 , tail = 1 ; list[1] = 0 ;for ( int i = 1 ; i <= n ; i ++ ) {while ( head<tail && slope(list[head+1],list[head])<=double(2*(s[i]-L) ) ) head ++ ;int j = list[head] ;f[i] = f[j] + ( s[i]-s[j]-L )*( s[i]-s[j]-L ) ;while ( head<tail && slope(list[tail-1],list[tail])>slope(list[tail],i)) tail --;list[++tail] = i ;}printf ( "%lld\n" , f[n] ) ; return 0 ; }總結(jié)
- 上一篇: 搜索过滤(乐优)
- 下一篇: 调研分析:全球与中国多媒体投影仪镜头市场