DP專題-專項訓練:斜率優化 DP
阿新 • • 發佈:2022-04-17
目錄
1. 前言
本篇博文是斜率優化 DP 的習題博文。
講解斜率優化 DP 的博文的傳送門:動態規劃專題-學習筆記:斜率優化 DP
說句實在話,斜率優化的題目只要推出轉移方程,\(y=kx+b\) 還不好推嗎?
注意本文全是單調佇列斜率優化,如果想看一般的李超線段樹斜率優化的看這篇:資料結構專題-學習筆記:李超線段樹。
2. 練習題
題單:
P4360 [CEOI2004]鋸木廠選址
注意以下敘述對所有資料做了一個翻轉處理。
設 \(f_i\) 表示當前從 1 到 \(i\),\(i\) 作為一個鋸木廠點的最小花費。
考慮列舉另外一個鋸木廠點 \(j<i\),那麼有轉移方程:
\[f_i=\min\{tot-d_j\times (w_{i-1}-w_{j-1})-d_i\times (w_n-w_{i-1})\} \]其中的 \(d,w\) 做了字首和處理,\(tot\) 表示將所有木頭移到第 0 個點的花費。
然後就是斜率優化拆拆拆,碼碼碼。
程式碼:
/* ========= Plozia ========= Author:Plozia Problem:P4360 [CEOI2004]鋸木廠選址 Date:2021/4/14 ========= Plozia ========= */ #include <bits/stdc++.h> typedef long long LL; const int MAXN = 20000 + 10; int n, w[MAXN], d[MAXN], tot, sumw[MAXN], sumd[MAXN], ans = 0x7f7f7f7f, f[MAXN]; int q[MAXN], l, r; int read() { int sum = 0, fh = 1; char ch = getchar(); for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1; for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48); return sum * fh; } int Min(int fir, int sec) { return (fir < sec) ? fir : sec; } int Max(int fir, int sec) { return (fir > sec) ? fir : sec; } int X(int x) { return sumd[x]; } int Y(int x) { return sumd[x] * sumw[x - 1]; } int K(int x) { return sumw[x - 1]; } double Slope(int x, int y) { return ((double)Y(x) - Y(y)) / ((double)X(x) - X(y)); } int main() { memset(f, 0x7f, sizeof(f)); n = read(); for (int i = n; i >= 1; --i) w[i] = read(), d[i] = read(); for (int i = 1; i <= n; ++i) sumw[i] = sumw[i - 1] + w[i], sumd[i] = sumd[i - 1] + d[i]; for (int i = 1; i <= n; ++i) tot += w[i] * sumd[i]; q[l = r = 1] = 1; for (int i = 2; i <= n; ++i) { while (l < r && Slope(q[l], q[l + 1]) <= (double)K(i)) ++l; int j = q[l]; f[i] = tot - sumd[j] * (sumw[i - 1] - sumw[j - 1]) - sumd[i] * (sumw[n] - sumw[i - 1]); while (l < r && Slope(i, q[r]) <= Slope(q[r], q[r - 1])) --r; q[++r] = i; } // for (int i = 2; i <= n; ++i) // for (int j = 1; j < i; ++j) // f[i] = Min(f[i], tot - sumd[j] * (sumw[i - 1] - sumw[j - 1]) - sumd[i] * (sumw[n] - sumw[i - 1])); for (int i = 2; i <= n; ++i) ans = Min(ans, f[i]); // for (int i = 2; i <= n; ++i) std::cout << f[i] << "\n"; printf("%d\n", ans); return 0; }
P3195 [HNOI2008]玩具裝箱
設 \(f_i\) 表示處理了 \([1,i]\) 的所有玩具時的最小總費用。
那麼有狀態轉移方程:$$f_i=\min{f_j+(i-j-1+c_i-c_j-l)^2|j<i}$$
同樣的,\(c\) 做了字首和處理。
然後還是斜率優化拆拆拆,碼碼碼。
程式碼:
/* ========= Plozia ========= Author:Plozia Problem:P3195 [HNOI2008]玩具裝箱 Date:2021/4/15 ========= Plozia ========= */ #include <bits/stdc++.h> typedef long long LL; const int MAXN = 5e4 + 10; int n, L, l, r, q[MAXN]; LL f[MAXN], c[MAXN]; int read() { int sum = 0, fh = 1; char ch = getchar(); for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1; for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48); return sum * fh; } LL Min(LL fir, LL sec) { return (fir < sec) ? fir : sec; } LL Max(LL fir, LL sec) { return (fir > sec) ? fir : sec; } double K(int i) { return 2.0 * (i + c[i]); } double X(int i) { return 1.0 * i + c[i]; } double Y(int i) { return 1.0 * f[i] + (i + c[i]) * (i + c[i]) + 2 * (i + c[i]) * (L + 1); } double Slope(int x, int y) { return (Y(x) - Y(y)) / (X(x) - X(y)); } int main() { n = read(), L = read(); for (int i = 1; i <= n; ++i) c[i] = read() + c[i - 1]; q[l = r = 1] = 0; for (int i = 1; i <= n; ++i) { while (l < r && Slope(q[l], q[l + 1]) <= K(i)) ++l; int j = q[l]; f[i] = f[j] + (i - j - 1 + c[i] - c[j] - L) * (i - j - 1 + c[i] - c[j] - L); while (l < r && Slope(q[r], i) <= Slope(q[r], q[r - 1])) --r; q[++r] = i; } // for (int i = 1; i <= n; ++i) // for (int j = 0; j < i; ++j) // f[i] = Min(f[i], f[j] + (i - j - 1 + c[i] - c[j] - l) * (i - j - 1 + c[i] - c[j] - l)); printf("%lld\n", f[n]); return 0; }
3. 總結
斜率優化的題目就是推暴力的方程,沒了~
當然有一部分題目還是很難的,僅斜率優化可能不夠,就需要一些科技來進一步優化。