斜率優化學習筆記

斜率優化真是大坑……太考驗思惟了……然而彷佛只要懂了以後全部題都能作了……就看你能不能把狀態轉移方程給摳出來……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 }
相關文章
相關標籤/搜索