斜率優化真是大坑……太考驗思惟了……然而彷佛只要懂了以後全部題都能作了……就看你能不能把狀態轉移方程給摳出來……html
借鑑的文章比較多……就不一一列舉了……主要是莫名其妙看了兩種方法的說明而後徹底懵逼不知道誰對誰錯,後來才發現兩種是從不一樣的角度去說明的ios
以洛谷P3195 [HNOI2008]玩具裝箱TOY爲例git
首先咱們要知道,對於形如$dp[i]=a[j]+b[j]$的式子,能夠用單調隊列優化到$O(n)$優化
但若是式子變成了形如$dp[i]=a[i]*b[j]+c[i]+d[j]$的時候,由於$a[i]*b[j]$這一項既與$i$有關又與$j$有關,因此直接單調隊列行不通了spa
咱們先來考慮這一道題目,設前綴和爲$sum[i]$轉移方程應該是$$dp[i]=min(dp[j]+(sum[i]+i-sum[j]-j-L-1)^2) (j<i)$$code
那麼咱們要考慮一下進行優化,咱們假設選取$j$會比$k$更優,且$k<j$htm
那麼$$dp[j]+(sum[i]+i-sum[j]-j-L-1)^2<dp[k]+(sum[i]+i-sum[k]-k-L-1)^2$$blog
而後爲了方便一點,令$a[i]=sum[i]+i,b[i]=sum[i]+i+L+1$隊列
那麼上式能夠化爲$$dp[j]+(a[i]-b[j])^2<dp[k]+(a[i]-b[k])^2$$get
而後展開一下$$dp[j]+a[i]^2-2*a[i]*b[j]+b[j]^2<dp[k]+a[i]^2-2*a[i]*b[k]+b[k]^2$$
移項$$dp[j]+b[j]^2-dp[k]-b[k]^2<2*a[i]*b[j]-2*a[i]*b[k]$$
$$\frac {dp[j]+b[j]^2-dp[k]-b[k]^2}{b[j]-b[k]}<2*a[i]$$
而後令$Y[i]=dp[i]+b[i]^2,x[i]=b[i]$
那麼原式可化爲$$\frac {Y[j]-Y[k]}{X[j]-X[k]}<2*a[i]$$
因此若是$j>k$且$\frac {Y[j]-Y[k]}{X[j]-X[k]}<2*a[i]$ 那麼$j$比$k$更優,不然$k$比$j$優
因此證了半天這有啥用麼?沒有
咱們考慮一下,若在平面直角座標系上存在點$k:(X[k],Y[k])$與$j:(X[j],Y[j])$,那麼$\frac {Y[j]-Y[k]}{X[j]-X[k]}$就是兩點的斜率
而後一下咱們用$slope(i,j)$表示$i,j$兩點的斜率,即$\frac {Y[i]-Y[j]}{X[i]-X[j]}$
由於$X[j],X[k],Y[i],Y[k]$都是定值,因此上面兩個點都是定點
而後咱們假設有三個值$i,j,k$(這裏的$i$與上面的無關),其中$k<j<i$
咱們假設$slope(j,k)>slope(i,j)$,也就是說是下面這個樣子
那麼$slope(j,k),slope(i,j),2*a[i]$會有三種大小關係
當$slope(j,k)>slope(i,j)>2*a[i]$時,$j$比$i$優,$k$比$j$優,$j$不是最優
當$slope(j,k)>2*a[i]>slope(i,j)$時,$k$比$j$優,$i$比$j$優,$j$不是最優
當$2*a[i]>slope(j,k)>slope(i,j)$時,$j$比$k$優,$i$比$j$優,$j$不是最優
也就是說,若是存在$slope(j,k)>slope(i,j)$,那麼從$j$轉移不管如何都不多是最優的方案,已經能夠把它給排除了
因此,咱們必須保證維護的是一個下凸包,即$slope(j,k)>slope(i,j)$,以下圖
不難看出下凸包的斜率是永遠單調遞增的,那麼能夠用一個單調隊列來維護,即每一次看一看最末尾的元素$slope(q[t],q[t-1])<slope(q[t],i)$是否成立,若不成立則把$q[t]$彈出(即$--t$)
或者寫成$slope(q[t],q[t-1])<slope(q[t-1],i)$也是能夠的(仔細觀察上圖,不難發現這兩個判斷是等價的)
那麼,在這個單調隊列裏哪個答案纔是最優的呢?
由於只有$slope(j,k)<2*a[i]$時$j$纔會比$k$更優,而由於$a[i]=sum[i]+i$(忘了的能夠回去看看,上面的假設),因此$s[i]$確定是遞增的
那麼若是$slope(j,k)>2*a[i]$,那麼$j$就不可能比$k$更優了,不然$j$必定比$k$優,因此咱們能夠把$k$從單調隊列裏刪掉了,即每一次看看最開頭的元素$slope(q[h],q[h+1])<2*a[i]$是否成立,若成立,則把$q[h]$彈出(即$++h$)
那麼每一次單調隊列的開頭都知足$slope(q[h],q[h+1])>2*a[i]$,即$q[h]$後面的答案永遠不可能比它更優。因此咱們能夠直接用$q[h]$來進行轉移就好了,時間複雜度爲$O(n)$
順便注意一個細節,由於咱們要求的$j$必須小於$i$因此咱們應該先處理完單調隊列開頭並更新完答案,再去處理末尾
而後上本題代碼
1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #define db double 6 #define ll long long 7 using namespace std; 8 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 9 char buf[1<<21],*p1=buf,*p2=buf; 10 inline int read(){ 11 #define num ch-'0' 12 char ch;bool flag=0;int res; 13 while(!isdigit(ch=getc())) 14 (ch=='-')&&(flag=true); 15 for(res=num;isdigit(ch=getc());res=res*10+num); 16 (flag)&&(res=-res); 17 #undef num 18 return res; 19 } 20 const int N=50005; 21 int n,L; 22 db sum[N],dp[N];int h,t,q[N]; 23 inline db a(int i){return sum[i]+i;} 24 inline db b(int i){return sum[i]+i+L+1;} 25 inline db X(int i){return b(i);} 26 inline db Y(int i){return dp[i]+b(i)*b(i);} 27 inline db slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));} 28 //這些都如上面定義,忘了的再去看看 29 int main(){ 30 n=read(),L=read(); 31 for(int i=1;i<=n;++i) sum[i]=read()+sum[i-1]; 32 h=t=1; 33 //一開始要先放一個0,而不能把它設爲空 34 for(int i=1;i<=n;++i){ 35 while(h<t&&slope(q[h],q[h+1])<2*a(i)) ++h; 36 double p=a(i)-b(q[h]); 37 dp[i]=dp[q[h]]+p*p; 38 //更新答案 39 while(h<t&&slope(q[t-1],q[t])>slope(q[t-1],i)) --t; 40 q[++t]=i; 41 } 42 printf("%lld\n",(ll)dp[n]); 43 return 0; 44 }