斜率優化講解
<p align="right"> ——by ysy </p>php
1、簡單的複習
我在這裏給出一個式子,$f[i]=max(g[i]+calc(j))$,這是絕大部分dp式子的最基本的模型,每一道題可能只是將$max$改成$min$,或者是將calc中的東西更改一下,你們思考一下是否是這樣的。c++
若是當calc之中的每一項都只含有$i$或者是$j$,而且這兩個字母沒有相乘的狀況咱們就能夠用單調隊列,這個不難理解,舉個例子,像下面的這個式子:$f[i]=max { f[j]+a∗num[j] }$,就能夠用單調隊列維護,由於整個式子之中只有關於$i$的單獨項和關於$j$的單獨項。函數
可是像這樣的式子就不能夠了:$f[i]=max { f[j]+(sum[i]+sum[j])^2 }$,由於這個式子展開後就會出現關於$i$的式子乘上關於$j$的式子。像這樣的式子就是斜率優化的適用範圍。優化
2、斜率優化
像斜率優化這樣知識點須要一道例題來進行講解。下面咱們來看一道經典的例題。spa
1.列方程
咱們先想這道題的dp式子,先無論時間複雜度的問題。code
這個式子應該很好想:$f[i]=min { f[j]+( \sum_{k=j+1}^{i} lenth[k] +i−j−l)^{2} }$,咱們來分析一下時間複雜度:$O(n^3)$。blog
想一下優化,咱們是否是能夠將求和部分寫成前綴和的形式?將$\sum$的部分化成$sum[i]$。這樣咱們就能夠將式子轉化成$f[i]=min { f[j]+( sum[i] -sum[j] +i−j−l)^{2} }$,這樣的話時間複雜度就下降成爲$O(n^2)$。時間是更低了,可是仍是過不了啊,這是咱們就要等價地變換式子,使其成爲y=kx+b的形式,這個形式就是斜率優化的核心。隊列
2.轉化式子
$f[i]=min { f[j]+( sum[i] -sum[j] +i−j−l)^{2} } \downarrow$get
$f[i]= f[j] + [ ( sum[i] + i ) - ( sum[j] + j ) - l ]^2 \downarrow$io
令$s[i]=sum[i]+i \downarrow$
$f[i]=f[j]+( s[i] -s[j] - l)^2$
$f[i] = f[j] + s[i]^2 + ( s[j] + l ) ^2 - 2\times s[i] \times ( s[j] + l) \downarrow$
$f[j] + s[i]^2 + ( s[j] + l )^2 = 2 \times s[i] \times ( s[j] +l ) + f[i]$
3.分析式子
$f[j] + s[i]^2 + ( s[j] + l )^2 = 2 \times s[i] \times ( s[j] +l ) + f[i]$
觀察上面的式子,咱們發現這個式子十分像一種函數,y=kx+b,可能你們會有疑問,這個式子和直線的表達是有什麼形似之處呢?
咱們將$f[j] + s[i]^2 + (s[j] + l)^2$這個部分看作一個總體記爲$y$,這個部分能夠當作一個總體的條件是:這個總體中的全部部分都是已求出的,而且當知道$i$和$j$以後能夠$O(1)$求出。顯然這個總體知足。同理咱們將$2 \times s[i]$和$(s[i] + l)$這兩個部分也分別看作總體,並分別記爲$k$、$x$。這樣式子就化爲$y=kx+f[i]$。
下一步,咱們創建以個平面直角座標系,這個平面直角座標系中的每個點的座標$(x,y) $都對應的是上面式子中的$x $和$y $,這樣咱們就可以將每個與$i $有關的東西處理完事以後標到平面直角座標系之中。每個點的座標表成$( s[i],f[i] + (s[i] + l)^2 ) $,可能有人會問爲何縱座標沒有了$s[i]^2 $,而且橫座標沒有了$l $,這個問題下面會解答,請稍做等待。
若是咱們想用$j$來轉移$i$的話,就要讓斜率爲$2 \times s[i]$的直線過點$(s[j],f[j] + (s[j] + l)^2)$,而且此時直線的截距就是新的$f[i]$,由於$f[i]$爲這條直線的$b$。再看下面的圖解,咱們將求過的點都標到平面直角座標系中,咱們能夠發現,咱們想過的這個點必定在咱們維護的大圓包上,像點2這樣的點就不能被用來更新,由於過點3所得截距,必定比過點2所得截距小,那麼咱們能發現當點3求出以後,只要比較一下,點2和點3造成的直線的斜率和點1和點2造成的直線的斜率,若是二、3造成的比一、2造成的要小,那麼3號點必定比2號點更優。咱們再看,假設下圖之中已經維護好1到5的全部點,那麼就會出現這樣的大圓包。咱們用求出6的點的直線去和這些點相交,咱們發現只有點4在當前直線上時能使截距最小(畫一畫圖就能發現是過點4時,直線的截距最小),根據是由點4轉移,咱們能夠發現,當兩個點一、3的斜率小於$2 \times s[i]$的時候,橫座標小的點必定不能用來轉移,同理斜率大於$2 \times s[i]$的兩個點,橫座標大的也不可以用來轉移,這個性質是否是很好?
根據上面咱們發現的式子,咱們能夠維護一個相似於單調隊列的隊列來維護咱們的大圓包。可是這個大圓包具體怎麼維護呢?咱們先看如何求斜率。若是給你直線上的兩個點,我想你們必定會求斜率。就是兩點的縱座標相減的差除上兩點的橫座標相減的差。這裏也就解釋了,爲何上文中的縱座標沒有了$s[i]^2$,由於兩式相減時$s[i]$是相同的,從而$s[i]^2$也就是相同的,因此相減時就將其減掉了,所以$s[i]^2$不用出如今縱座標之中。同理在相減時咱們的橫座標也不須要$l$。
double re_x(int i){return s[i];} double re_y(int i){return f[i]+(s[i]+l)*(s[i]+l);} double re_k(int i,int j){return (re_y(j)-re_y(i))/(re_x(j)-re_x(i));}
會求斜率了,咱們再來看怎麼維護大圓包,咱們發現當隊列中最後一個的點和隊列中倒數第二個點的產生斜率大於最後一個點和新產生的點產生的斜率,那麼結尾就要彈出隊列,這個用一個$whlie$循環就可以解決,最後再將新產生的點放在結尾。這個實現十分像單調隊列的實現。
int main() { while(head<tail&&re_k(q[tail],i)<re_k(q[tail],q[tail-1])) tail--; q[++tail]=i; }
咱們再看,怎麼知足第二個性質,讓更新變成$O(1)$的?咱們發現當隊列中第一個點和第二個點產生的斜率若是小於當前的直線,那麼第二個點更新必定比第一個點更新更優,咱們就要進行隊首彈出。這個過程也十分像單調隊列的維護。最後直接用隊首進行更新。
int main() { while(head<tail&&re_k(q[head],q[head+1])<2*s[i]) head++; f[i]=f[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1); }
這樣咱們就解決了維護的問題,最後就是將這些組裝在一塊兒,造成下方的代碼。
#include <stdio.h> #define N 50001 int n,l,head,tail; long long f[N],s[N],q[N]; double re_x(int i){return s[i];} double re_y(int i){return f[i]+(s[i]+l)*(s[i]+l);} double re_k(int i,int j){return (re_y(j)-re_y(i))/(re_x(j)-re_x(i));} int main() { scanf("%d%d",&n,&l); for(int i=1;i<=n;i++) scanf("%lld",&s[i]),s[i]+=s[i-1]; for(int i=1;i<=n;i++) s[i]+=i; q[tail]=0; for(int i=1;i<=n;i++) { while(head<tail&&re_k(q[head],q[head+1])<2*s[i]) head++; f[i]=f[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1); while(head<tail&&re_k(q[tail],i)<re_k(q[tail],q[tail-1])) tail--; q[++tail]=i; } printf("%lld\n",f[n]); }
4.分析上方代碼的適用範圍
上方的代碼是有必定的適用範圍的,你們想一下,爲何咱們敢彈出隊首與隊尾?
咱們再來看一下題目,這個題目顯然知足一個特色,就是因爲咱們將$s[i]$定義爲前綴和,因此他必定是單調遞增的,而且咱們的點的橫座標也是$s[i]$也知足單調遞增。這兩個性質十分好。咱們把隊首的元素彈出的條件是斜率小於$2 \times s[i]$,由於$s[i]$知足單調遞增,因此彈出時小於,那之後就必定一直小於下去,因此彈出就彈出了。咱們再看,由於咱們的橫座標知足單調遞增,因此每一次插入點都會在最後,所以結尾彈出也是正確的。
可是若是斜率沒有單調性呢?咱們就不能將隊首彈出,這樣咱們就不能在$O(1)$的時間內求出新的元素,咱們能夠在大圓包上進行二分。咱們看下面的大圓包,會發現只有當前點和上一個點的斜率小於直線斜率,而且和下一個點的斜率大於直線的斜率時,這個點纔是最優的。因此咱們能夠進行二分查找。
若是咱們的橫座標沒有單調性呢?咱們就不可以將隊尾刪掉了,咱們應該用平衡樹來維護,動態維護大圓包。可是怎麼維護呢?咱們能夠運用$splay$,具體請聽本人口述。
3、練習
1.倉庫建設
$1)$列方程,並轉化形式
$f[i] = min ( f[j] + x[i] \times ( P[i] - P[j]) + g[i] - g[j] + c[i]) \downarrow$
$ f[i] = f[j] + x[i] \times P[i] - x[i] \times P[j] +g[i] -g[j] +c[i] \downarrow$
$f[j] - g[j] + x[i] \times P[i] +g[i] + c[i] = x[i] \times P[j] + f[i]$
$2) $找點
顯然這裏的點就是$( f[j] - g[j] ,P[j] )$,斜率就是$x[i]$,截距就是$f[i]$。
$3)$寫吧