學習筆記 斜率優化DP

2022-04-29 06:09:09 字數 3764 閱讀 1383

作為數學渣,先複習一下已知兩點\((x_1, y_1)\), \((x_2, y_2)\),怎麼求過兩點的一次函式的斜率...

待定係數法代入 \(y = kx + b\) 有:

\(x_1k + b = y_1\)

\(x_2k + b = y_2\)

兩式相減有:

\(k = \frac\)

故事圍繞著《演算法競賽高階指南》的三一道例題展開:

任務安排 1:

假如我們啟動了乙個任務\([l, r]\),那麼它會對後面造成\(s * \sum_^ c_i\)的費用。

設\(st\)為 \(t\) 的字首和,設\(sum\)為\(c\)的字首和

\(f[i]\) 表示安排完前 \(i\) 個任務的最小花費:

$f[i] = min(f[j] + (sum[i] - sum[j]) * t[i] + (sum[n] - sum[j]) * s) $

時間複雜度\(o(n ^ 2)\)

任務安排 2:

將上題推出的轉移式子得\(min\)去掉觀察:

\(f[i] = f[j] + (sum[i] - sum[j]) * t[i] + (sum[n] - sum[j]) * s\)

發現我們無法優化\(dp\)的原因是有與 \(i, j\) 兩者都有關的乘積項,導致我們沒有最優策略:

\(- sum[j] * t[i]\)

考慮把這個式子拆開轉換為一次函式:\(y = kx + b\) 的形式。

則以上式子可以化成:

\(\underline_y = \underline_k * \underline_x + \underline_b\)

發現當 \(i\) 確定後,該一次函式的斜率 \(k\) 確定,則截距 \(b\) 越小, \(f[i]\) 越小。

我們將 \((x, y)\) 即 \((sum[j], f[j])\) 放在座標系上。

則形象化可理解為一條直線從下往上平移,所碰到的第乙個點即為最優解。

發現乙個點如果被另外兩個點圍起來,永遠不可能作為最優解。

刪除了這些點後,發現相鄰點之間的斜率為單調遞增的,即構成乙個凸包:

發現乙個斜率 \(k\) 固定的直線所匹配的最優點滿足:

由於這道題斜率 \(t[i] + s\)、橫座標 \(sum[j]\) 皆單調遞增。

由於橫座標遞增,所以維護凸包時,每當加入乙個點時:

\(\frac - y_} - x_} >= \frac - y_} - x_}\)

由於斜率遞增,所以 \(i + 1\) 的最優解一定在 \(i\) 的右邊,所以一旦隊頭兩個點構成的斜率 $ < $ 當前的斜率,可以彈出隊頭。即滿足:

\(\frac - y_} - x_} < t[i] + s\)

然後隊頭的元素即為最優選擇。

時間複雜度 \(o(n)\)

\(tips:\)

由於除法會有精度問題,可以通過交叉相乘的形式比較大小

#include #include #include #define x(a) (c[a])

#define y(a) (f[a])

#define k(a) (t[a] + s)

using namespace std;

typedef long long ll;

const int n = 300005;

int n, s;

ll t[n], c[n], q[n], f[n];

int main()

printf("%lld\n", f[n]);

return 0;

}

任務安排3

此時的斜率不再遞增了,也就是我們不能\(pop\_front\)了,不過我們仍可以維護凸包,然後保持單調性,二分。

時間複雜度\(o(nlog_2n)\)

#include #include #include #define x(a) (c[a])

#define y(a) (f[a])

#define k(a) (t[a] + s)

using namespace std;

typedef long long ll;

const int n = 300005;

int n, s, t[n], c[n], q[n];

ll f[n];

int main()

f[i] = f[q[r]] + (ll)(c[i] - c[q[r]]) * t[i] + (ll)(c[n] - c[q[r]]) * s;

while(hh < tt && ((y(q[tt]) - y(q[tt - 1])) * (x(i) - x(q[tt])) >= ((y(i) - y(q[tt])) * (x(q[tt]) - x(q[tt - 1]))))) tt--;

q[++tt] = i;

}printf("%lld\n", f[n]);

return 0;

}

運輸小貓

設 \(d[i]\) 為從 \(1\) 走到 \(i\) 的距離。

那麼每條小貓最佳的出發時間應為 \(a[i] = t[i] - d[h[i]]\),如果要接上這只貓,必須大於這個時間出發。

我們將 \(a\) 陣列排序,那麼問題等價轉換於把 \(m\) 個點劃分成 \(p\) 個連續區間,使每一段到右端點的距離之和的總和最小。(內心 \(os\):這不就是擺渡車的變種嗎?)

那麼樸素 \(dp\) 便很好列出了:

\(f[k][i]\) 表示將前 \(i\) 只小貓分成 \(k\) 組的最小總和。

設 \(suma\) 為 \(a\) 陣列的字首和。

\(f[k][i] = min(f[k - 1][j] + a[i] * (i - j) - suma[i] + suma[j]) (0 <= j < i)\)

由於這裡面有乙個非常討厭的 \(a[i] * -j\),所以我們考慮斜率優化:

\(\underline_y = \underline_k * \underline_x + \underline_b\)

發現這裡的橫座標、斜率都是單調遞增,即情況 \(1\)。那麼我們可以將不需要的直接踢出即可。

時間複雜度 \(o(pm)\)

#include #include #include #include using namespace std;

typedef long long ll;

const int n = 100005, s = 105;

int n, m, p, d[n], a[n], q[n];

ll f[s][n], sum[n];

ll inline y(int i, int k)

int main()

sort(a + 1, a + 1 + m);

for (int i = 1; i <= m; i++) sum[i] = sum[i - 1] + a[i];

for (int k = 1; k <= p; k++)

}printf("%lld\n", f[p][m]);

}

斜率優化DP學習筆記

本文以luogup3195 玩具裝箱為例,我們很容易可以的出下面這個柿子 f i min 設 b i s i i j 為 f i 的最優決策點,則有 f i f j b i b j l 1 2 把只與 j 有關的放在左邊 f j b j 2 b j l 1 2 b i b j b i 2 l 1 2...

斜率優化dp筆記

瞎扯 演算法真的是無止境,從暴力到dp原本以為很神奇了,沒想到還能優化dp,而且是把o n 2 變成o n 真是無 說。引入 我們來分析這麼乙個問題,給你n個數,要你把他們分成連續的若干塊,使得讓他們的每段和的平方加起來最小.正常我們會想到的就是o n 2 的dp,方程就是 dp i min dp ...

斜率優化DP 學習筆記 更新中

參考資料 1.元旦集訓的課件已經很好了 2.一 對於一類轉移方程 f i max a i 和c i 是開始求解前就知道常數,b j 和d j 知道f j 後就知道有關 可以使用斜率優化 不是這個形式就盡量往這個形式化 決策單調性 對於兩個轉移j和k,設b j 假設j比k優或相等,把式子一化就變成了 ...