斜率優化是 dp 的一種有力優化方式。常用於處理當 dp 方程 \(f[i] = max(f[j]+ val(i, j))\) 中 \(val(i,j)\) 出現同時關於 \(i\),\(j\) 的項,而無法直接單調佇列優化的情況。
考慮這麼乙個問題:
顯然的 dp 問題。令 \(f[i]\) 為以 \(i\) 為某一段的結尾,前面總得分的最大值。顯然我們有狀態轉移方程:\(f[i] = max(f[j]+a \times (sum[i]-sum[j])^2 + b \times (sum[i]-sum[j])+c)\),其中 \(sum[i]\) 是字首和。
這樣可以在 \(o(n^2)\) 解決這個問題,但如果資料範圍加強到 \(n \leq 10^6\) 呢?
第一時間想起的可能是單調佇列優化。我們知道,單調佇列優化也是 \(o(n)\) 的複雜度。只要你能把 dp 方程中關於 \(i\) 的項和關於 \(j\) 的項分開就 ok 了。嘗試對方程進行一系列變形(為了明顯,我們把如上的 \(a,b,c\) 係數改成大寫 \(a,b,c\),先把 \(\max\) 去掉,後面再說):
\(f[j]+asum[j]^2-bsum[j]+c=sum[j] \times 2asum[i] + (asum[i]^2-bsum[i]+f[i])\)
我們發現,萬事俱備,就是中間的那個 \(sum[j] \times 2asum[i]\) 非常惱火——它是乙個同時關於 \(i\) 和 \(j\) 的項——我們該怎麼處理它呢?
至此,我們引出 dp 優化中乙個非常重要的概念——「斜率優化」。
繼續觀察上式,你發現有什麼特點?
我們令 \(y = f[j]+asum[j]^2-bsum[j]+c,x=sum[j]\),把之前乙個決策 \(j\) 的資訊儲存在乙個二元組 \((x,y)\) 內。方程變為 —— \(y=2asum[i] \times x + (asum[i]^2-bsum[i]+f[i])\)。
這像個什麼?直線解析式!
可以想象,我們在乙個平面直角座標系上把 \(1 \sim i-1\) 的決策對應的二元組 \((x,y)\) 當成點的座標撒下去,如下圖:
現在我們要得到乙個正確的 \(f[i]\),顯然必須滿足 \(y=2asum[i] \times x + (asum[i]^2-bsum[i]+f[i])\) 這個等式(這畢竟原來是 \(f[i]\) 的推導公式)。
我們再把當前這個決策點 \(i\) 變成一條直線,一條斜率為 \(2asum[i]\),截距為 \(asum[i]^2-bsum[i]+f[i]\) 的直線(注意當前 \(f[i]\) 尚未確定,其他要素均已確定)。如果上面那個等式成立,是不是就說明——這條直線過 \((x,y)\) 這個點?那麼 \(f[i]\) 就可以解出!
冷靜一下,我們還有乙個重大發現——我們要的是最大的 \(f[i]\) (前面移除了個 \(\max\) 還記得嗎),也就是說這條直線的截距要盡量大!如下圖:(注意由於 \(a<0\),斜率小於 \(0\))
顯然,過決策點 \(f\) 的直線截距最大,對應地,此時取得的 \(f[i]\) 最大。
我們現在要做的,就是使程式在均攤 \(o(1)\) 下找到這個點。
再次觀察,哪些點是肯定無用的?
顯然,紅色點不可能取到,因為他們被圈在了乙個紫色的邊框內,這個邊框叫做「凸包」。而我們現在要做的,就是維護這個「凸包」。
如何維護?首先我們發現,點的 \(x\) 座標遞增(回到定義看顯然)。因此每次新加入的決策點都是在最右側,我們判斷它是在凸包上還是凸包內就 ok 了。
我們維護乙個單調佇列,左邊靠隊頭,右邊靠隊尾。顯然只有當隊尾和在隊尾左側的那個點的斜率大於隊尾和新加入的點的斜率(注意此時斜率為負),也就是這個新加入的決策點在凸包上,才不會影響之前的點。假如上圖 i 是新增的決策,那麼有 \(k_>k_\),i 才能進來。否則,就要往前不斷判斷,把一些點彈出佇列,直到 i 滿足入隊的要求(形成凸包)。
記得單調佇列還要維護隊頭,這個也一樣。觀察到,假如隊頭和其下乙個點的斜率大於當前決策 \(i\) 的斜率(注意此時斜率為負),隊頭就廢了,因此我們也找到了出隊的方法,如下圖:
假如 \(i\) 增大,斜率會減小(回到定義,注意斜率為負),當其偏離至橙色的直線時,斜率小於了 \(k_\)(\(m\) 是輔助點,不用管,\(l\) 才是隊頭),此時顯然 l 不能再被接下來的直線切到,因此 l 出隊。
#include #include #include #define maxn 1000010
#define ll long long
using namespace std;
int read()
while('0' <= c && c <= '9')
return x * f;
}ll n, a, b, c, sum[maxn], f[maxn], q[maxn];
double k(ll a, ll b)
int main()
cout << f[n] << endl;
return 0;
}
[luogu p2365] 任務安排
首先我們可以化出和之前類似的 dp 方程,如下:
\(f[j] = (s + t[i]) * c[j] + f[i] - t[i] * c[i] - s * c[n]\)
顯然,決策點的座標應為 \((c[j],f[j])\),直線斜率為 \(s+t[i]\),直接斜率優化 dp 即可。
需要注意的是,與上題不同的是,這道題維護的是下凸包。
**如下:
#include #include #include #define maxn 5010
using namespace std;
int read()
while('0' <= c && c <= '9')
return x * f;
}int n, s, t[maxn], c[maxn], f[maxn], q[maxn * 2];
double k(int a, int b)
int main()
int head = 1, tail = 1;
for(int i = 1; i <= n; ++i)
cout << f[n] << endl;
return 0;
}
還是上面的題目,但如果 \(t_i\) 可能是負數,即查詢的斜率不具有單調性,該怎麼辦呢?
思考一下,此時隊尾出隊仍和原來一樣,因為插入的決策點的橫座標仍具有單調性。然而隊頭出隊就不一定了,因為以前我們是根據斜率的單調來彈出隊頭的,現在斜率不再單調,隊頭也必須儲存。
因此這樣維護凸包的資料結構就是乙個單調棧。當我們要查詢的時候,由於斜率不單調,不能再直接讀取隊頭,需要二分找到剛好直線切凸包的位置,複雜度只多了乙個 \(\log\)。
此即 [sdoi2012]任務安排,下面呈現有關**:
#include #include #include #define maxn 300010
#define ll long long
using namespace std;
int read()
while('0' <= c && c <= '9')
return x * f;
}ll n, s, t[maxn], c[maxn], f[maxn], stack[maxn * 2], top = 1;
double k(int a, int b)
int ef(int l, int r, int i)
return stack[l];
}int main()
for(int i = 1; i <= n; ++i)
cout << f[n] << endl;
return 0;
}
斜率優化學習筆記
目錄第二類斜率優化 注意事項 q a 例題 hnoi玩具裝箱 由題意我們可以馬上得到dp方程 dp i displaystyle min sum x 為字首和 很可惜,這個解法是 o n 2 的,無法ac 但是可以在當年騙很多分?接下來的文章中我們會用幾個簡稱 f i sum i i c 1 l 優...
斜率優化 學習筆記
前言 寒假yousiki講過斜率優化,但完全沒有聽懂。現在文化課解析幾何也學了不少,終於能做一些題了。有時候我們列出dp方程會得到形如這樣的式子 f i max min w i 1 leq j我們變換一下形式 2 a i b j f i a i 2 w i f j b j 2 仔細一看,上面的等式可...
斜率優化 學習筆記
事實上斜率優化是專門用來處理這樣一類 dp 式子的 dpi ai maxj 1i 1 bj cj basei 窩萌嘗試把上式中的 bj cj 和 basei 等價成 xj yj 和 ki 並把它們丟到乙個平面上,然後它萌就會變成一堆點 xj,yj 畫一條過他們的直線,類似於 y yj ki x xj...