1. 程式人生 > 其它 >DP專題-專項訓練:斜率優化 DP

DP專題-專項訓練:斜率優化 DP

目錄

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. 總結

斜率優化的題目就是推暴力的方程,沒了~

當然有一部分題目還是很難的,僅斜率優化可能不夠,就需要一些科技來進一步優化。