Chương 4·Bài 42·Nâng cao

Convex Hull Trick

Nhiều quy hoạch động có chuyển tiếp dạng dpi=min⁡j<i(dpj+bj⋅xi)dp_i = \min_{j < i} (dp_j + b_j \cdot x_i), hoặc đưa về được dạng đó sau khi khai triển một bình phương. Với mỗi jj, biểu thức bj⋅x+dpjb_j \cdot x + dp_j là một đường thẳng theo xx; dpidp_i là giá trị nhỏ nhất của các đường tại x=xix = x_i. Tập các đường chỉ cần giữ phần bao dưới: những đường từng là nhỏ nhất ở một khoảng xx nào đó. Khi hệ số góc thêm vào đơn điệu và điểm hỏi cũng đơn điệu, bao dưới giữ bằng một hàng đợi hai đầu và mỗi bước tốn O(1)O(1) trung bình, đưa O(n2)O(n^2) về O(n)O(n). Đây là kỹ thuật bao lồi đường thẳng (convex hull trick, CHT).

Chuyển tiếp là cực tiểu của các đường thẳng

Viết chuyển tiếp thành dpi=min⁡j(kj⋅xi+mj)+cidp_i = \min_j (k_j \cdot x_i + m_j) + c_i, trong đó kjk_j và mjm_j chỉ phụ thuộc jj, xix_i và cic_i chỉ phụ thuộc ii. Mỗi jj đã tính xong cho một đường thẳng y=kjx+mjy = k_j x + m_j; hỏi dpidp_i là hỏi đường nào thấp nhất tại xix_i. Tập các đường thấp nhất theo xx tạo thành một bao dưới lồi: đi từ trái sang phải, hệ số góc của đường đang thấp nhất giảm dần. Một đường nằm hoàn toàn trên bao không bao giờ cần nữa.

Đường xanh dương đang trong hàng đợi, đường cong xanh lá là bao dưới. Kéo x để hỏi đường thấp nhất.

Ví dụDãy Chuyển Tiếp Đơn Điệu

Đề. Hai dãy aa tăng ngặt (a1=1a_1 = 1) và bb giảm ngặt (bn=0b_n = 0), n≤105n \le 10^5. dp1=0dp_1 = 0, dpi=min⁡j<i(dpj+bj⋅ai)dp_i = \min_{j < i} (dp_j + b_j \cdot a_i). In dpndp_n.

Lời giải. Mỗi jj cho một đường thẳng y=bj⋅x+dpjy = b_j \cdot x + dp_j; tính dpidp_i là hỏi giá trị nhỏ nhất của các đường tại x=aix = a_i. Hệ số góc giảm ngặt khi jj tăng, điểm hỏi tăng ngặt: đúng trường hợp hàng đợi hai đầu ở mục sau. Ở đây viết sẵn khung đó.

Bước 1: đường nào vô dụng. Bao lồi lưu trong hai mảng K (hệ số góc), M (tung độ gốc), dùng như hàng đợi hai đầu từ hd tới tl - 1. Đường giữa yy vô dụng khi giao điểm của xx với zz nằm không xa hơn giao điểm của xx với yy; so bằng nhân chéo để khỏi chia, và tích tới 1018⋅10910^{18} \cdot 10^9 nên dùng __int128.

C++
bool bad(ll x, ll y, ll z) {
    return (__int128)(M[z] - M[x]) * (K[x] - K[y]) <= (__int128)(M[y] - M[x]) * (K[x] - K[z]);
}

Bước 2: thêm đường vào cuối. Ghi đường mới vào ô tl; chừng nào đường áp chót vô dụng thì chép đường mới đè lên nó và lùi tl; cuối cùng tăng tl.

C++
void add(ll k, ll m) {
    K[tl] = k, M[tl] = m;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

Bước 3: hỏi ở đầu. Điểm hỏi tăng dần nên đường tốt nhất chỉ dịch sang phải: bỏ đường đầu khi đường thứ hai đã không tệ hơn tại xx.

C++
ll get(ll x) {
    while (tl - hd >= 2 && K[hd + 1] * x + M[hd + 1] <= K[hd] * x + M[hd]) hd++;
    return K[hd] * x + M[hd];
}

Bước 4: quy hoạch động. Hỏi tại aia_i được dpidp_i, rồi thêm đường của chính ii cho các bước sau.

C++
add(b[1], dp[1]);
for (ll i = 2; i <= n; i++) {
    dp[i] = get(a[i]);
    add(b[i], dp[i]);
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 1e5 + 5;
ll n, a[N], b[N], dp[N], K[N], M[N], hd, tl;

bool bad(ll x, ll y, ll z) {
    return (__int128)(M[z] - M[x]) * (K[x] - K[y]) <= (__int128)(M[y] - M[x]) * (K[x] - K[z]);
}

void add(ll k, ll m) {
    K[tl] = k, M[tl] = m;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

ll get(ll x) {
    while (tl - hd >= 2 && K[hd + 1] * x + M[hd + 1] <= K[hd] * x + M[hd]) hd++;
    return K[hd] * x + M[hd];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (ll i = 1; i <= n; i++) cin >> a[i];
    for (ll i = 1; i <= n; i++) cin >> b[i];
    dp[1] = 0;
    add(b[1], dp[1]);
    for (ll i = 2; i <= n; i++) {
        dp[i] = get(a[i]);
        add(b[i], dp[i]);
    }
    cout << dp[n] << '\n';
}
4
1 2 4 7
9 6 3 0

Mỗi đường vào và ra hàng đợi một lần: O(n)O(n).

Hàng đợi hai đầu khi mọi thứ đơn điệu

Khi đường được thêm theo hệ số góc giảm dần (cho bài min), đường mới luôn thấp nhất ở rất xa bên phải, nên được đặt vào cuối bao. Đường áp chót BB giữa AA (trước nó) và CC (đường mới) trở nên vô dụng khi giao điểm của AA và CC nằm không xa hơn giao điểm của AA và BB về bên trái: lúc đó trên mọi xx, hoặc AA hoặc CC thấp hơn BB. So hai giao điểm bằng nhân chéo, không chia:

mC−mAkA−kC≤mB−mAkA−kB  ⟺  (mC−mA)(kA−kB)≤(mB−mA)(kA−kC).\frac{m_C - m_A}{k_A - k_C} \le \frac{m_B - m_A}{k_A - k_B} \iff (m_C - m_A)(k_A - k_B) \le (m_B - m_A)(k_A - k_C).

Khi điểm hỏi tăng dần, đường tốt nhất chỉ dịch sang phải trên bao: bỏ đường đầu khi đường thứ hai đã không tệ hơn tại xx hiện tại. Mỗi đường vào và ra hàng đợi một lần, tổng O(n)O(n). Nếu điểm hỏi không đơn điệu, giữ nguyên bao và tìm kiếm nhị phân đường tốt nhất, O(log⁡n)O(\log n) mỗi câu. Nếu cả hệ số góc cũng không đơn điệu, dùng cây Li Chao (bài 75).

Hỏi nhanh

Bao đang có đường AA, BB (hệ số góc giảm dần), thêm đường CC có hệ số góc nhỏ hơn nữa. Khi nào bỏ BB?

Ví dụHệ Số Sau Mỗi Trạm

Đề. n≤2⋅105n \le 2 \cdot 10^5 trạm có tải s1≤⋯≤sns_1 \le \dots \le s_n, hệ số x≥f1≥⋯≥fnx \ge f_1 \ge \dots \ge f_n. Xử lý trạm ii với hệ số cc tốn c⋅sic \cdot s_i rồi hệ số thành fif_i. Phải xử lý trạm nn; tìm chi phí nhỏ nhất.

Lời giải. dpidp_i là chi phí nhỏ nhất khi trạm ii là trạm xử lý gần nhất: dpi=min⁡(x⋅si, min⁡j<i(dpj+fj⋅si))dp_i = \min(x \cdot s_i,\ \min_{j < i} (dp_j + f_j \cdot s_i)). Đường ban đầu (x,0)(x, 0) ứng với chưa xử lý trạm nào, hệ số vẫn là xx; đường của jj có hệ số góc fjf_j, tung độ gốc dpjdp_j. Hệ số góc không tăng, điểm hỏi sis_i không giảm: dùng khung hàng đợi của ví dụ trước. Đáp án là dpdp của trạm cuối, vì trạm nn bắt buộc phải xử lý.

Bước 1: hai đường cùng hệ số góc. Hệ số góc chỉ không tăng nên có thể bằng nhau. Khi đó chỉ giữ đường có tung độ gốc nhỏ hơn; không xử lý trường hợp này thì bad sẽ nhân với hiệu hệ số góc bằng 0 và cho kết quả sai.

C++
void add(ll k, ll m) {
    if (tl > hd && K[tl - 1] == k) {
        if (M[tl - 1] <= m) return;
        tl--;
    }
    K[tl] = k, M[tl] = m;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

Bước 2: quy hoạch động. bad và get như ví dụ trước.

C++
add(x, 0);
ll dp = 0;
for (ll i = 1; i <= n; i++) {
    dp = get(s[i]);
    add(f[i], dp);
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 2e5 + 5;
ll n, x, s[N], f[N], K[N], M[N], hd, tl;

bool bad(ll a, ll b, ll c) {
    return (__int128)(M[c] - M[a]) * (K[a] - K[b]) <= (__int128)(M[b] - M[a]) * (K[a] - K[c]);
}

void add(ll k, ll m) {
    if (tl > hd && K[tl - 1] == k) {
        if (M[tl - 1] <= m) return;
        tl--;
    }
    K[tl] = k, M[tl] = m;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

ll get(ll q) {
    while (tl - hd >= 2 && K[hd + 1] * q + M[hd + 1] <= K[hd] * q + M[hd]) hd++;
    return K[hd] * q + M[hd];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> x;
    for (ll i = 1; i <= n; i++) cin >> s[i];
    for (ll i = 1; i <= n; i++) cin >> f[i];
    add(x, 0);
    ll dp = 0;
    for (ll i = 1; i <= n; i++) {
        dp = get(s[i]);
        add(f[i], dp);
    }
    cout << dp << '\n';
}
3 5
1 3 6
4 2 1

O(n)O(n).

Khai triển bình phương

Chi phí kiểu (hi−hj)2(h_i - h_j)^2 hay (Si−Sj)2(S_i - S_j)^2 không phải đường thẳng theo ii, nhưng khai triển ra thì có: (hi−hj)2=hi2−2hihj+hj2(h_i - h_j)^2 = h_i^2 - 2 h_i h_j + h_j^2. Phần hi2h_i^2 chỉ phụ thuộc ii, đưa ra ngoài min; phần còn lại là đường có hệ số góc −2hj-2h_j, tung độ gốc dpj+hj2dp_j + h_j^2, hỏi tại x=hix = h_i. Dãy hh tăng thì hệ số góc giảm và điểm hỏi tăng, đúng trường hợp hàng đợi hai đầu. Với chia dãy thành đoạn và chi phí bình phương tổng đoạn, làm tương tự với tổng tiền tố SS. Giá trị trung gian có thể tới 101710^{17} nhân 10610^6, nên phép so sánh giao điểm cần __int128.

Hỏi nhanh

Chuyển tiếp dpi=min⁡j(dpj+(hi−hj)2)+cdp_i = \min_j \left(dp_j + (h_i - h_j)^2\right) + c. Sau khi khai triển, đường của jj có hệ số góc và tung độ gốc là gì?

Ví dụBậc Đá Tăng Dần

Đề. n≤2⋅105n \le 2 \cdot 10^5 bậc với độ cao tăng ngặt tới 10610^6. Nhảy từ jj tới i>ji > j tốn (hi−hj)2+c(h_i - h_j)^2 + c. Tìm chi phí nhỏ nhất từ bậc 1 tới bậc nn.

Lời giải. Khai triển bình phương: dpi=min⁡j<i(dpj+hj2−2hjhi)+hi2+cdp_i = \min_{j < i} (dp_j + h_j^2 - 2h_j h_i) + h_i^2 + c. Phần trong ngoặc là đường thẳng của jj tại x=hix = h_i: hệ số góc −2hj-2h_j (giảm dần), tung độ gốc dpj+hj2dp_j + h_j^2. Độ cao tăng ngặt nên không có hai đường cùng hệ số góc, add không cần bước kiểm đó.

Bước 1: khung hàng đợi bad, add, get như ví dụ đầu.

Bước 2: quy hoạch động. Bậc 1 có dp1=0dp_1 = 0. Mỗi bậc hỏi tại hih_i (tăng dần), cộng hi2+ch_i^2 + c, rồi thêm đường của chính nó.

C++
add(-2 * h[1], h[1] * h[1]);
for (ll i = 2; i <= n; i++) {
    dp[i] = get(h[i]) + h[i] * h[i] + c;
    add(-2 * h[i], dp[i] + h[i] * h[i]);
}

Đáp án tới khoảng 2⋅10172 \cdot 10^{17}.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 2e5 + 5;
ll n, c, h[N], dp[N], K[N], M[N], hd, tl;

bool bad(ll a, ll b, ll d) {
    return (__int128)(M[d] - M[a]) * (K[a] - K[b]) <= (__int128)(M[b] - M[a]) * (K[a] - K[d]);
}

void add(ll k, ll m) {
    K[tl] = k, M[tl] = m;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

ll get(ll x) {
    while (tl - hd >= 2 && K[hd + 1] * x + M[hd + 1] <= K[hd] * x + M[hd]) hd++;
    return K[hd] * x + M[hd];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> c;
    for (ll i = 1; i <= n; i++) cin >> h[i];
    add(-2 * h[1], h[1] * h[1]);
    for (ll i = 2; i <= n; i++) {
        dp[i] = get(h[i]) + h[i] * h[i] + c;
        add(-2 * h[i], dp[i] + h[i] * h[i]);
    }
    cout << dp[n] << '\n';
}
4 3
1 2 5 8

O(n)O(n).

Ví dụChia Bản Thảo

Đề. Nhiều bộ tới hết input. Dãy n≤5⋅105n \le 5 \cdot 10^5 số không âm và hằng số mm; chia dãy thành đoạn, đoạn có tổng ss tốn s2+ms^2 + m. Tìm tổng nhỏ nhất.

Lời giải. Với tổng tiền tố SS: dpi=Si2+m+min⁡j<i(dpj+Sj2−2SjSi)dp_i = S_i^2 + m + \min_{j < i} (dp_j + S_j^2 - 2S_j S_i), dp0=0dp_0 = 0. Đường của jj có hệ số góc −2Sj-2S_j, tung độ gốc dpj+Sj2dp_j + S_j^2; j=0j = 0 cho đường (0,0)(0, 0). Các số có thể bằng 0 nên SS chỉ không giảm: hai đường liên tiếp có thể cùng hệ số góc, nên dùng bản add có kiểm cùng hệ số góc như ví dụ hệ số trạm.

Bước 1: đọc thẳng vào tổng tiền tố, đặt lại hàng đợi mỗi bộ.

C++
for (ll i = 1; i <= n; i++) {
    cin >> S[i];
    S[i] += S[i - 1];
}
hd = tl = 0;
add(0, 0);

Bước 2: quy hoạch động. dp chỉ cần giá trị hiện tại, vì đường của ii đã mang theo nó.

C++
ll dp = 0;
for (ll i = 1; i <= n; i++) {
    dp = get(S[i]) + S[i] * S[i] + m;
    add(-2 * S[i], dp + S[i] * S[i]);
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 5e5 + 5;
ll n, m, S[N], K[N], M[N], hd, tl;

bool bad(ll a, ll b, ll c) {
    return (__int128)(M[c] - M[a]) * (K[a] - K[b]) <= (__int128)(M[b] - M[a]) * (K[a] - K[c]);
}

void add(ll k, ll v) {
    if (tl > hd && K[tl - 1] == k) {
        if (M[tl - 1] <= v) return;
        tl--;
    }
    K[tl] = k, M[tl] = v;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

ll get(ll x) {
    while (tl - hd >= 2 && K[hd + 1] * x + M[hd + 1] <= K[hd] * x + M[hd]) hd++;
    return K[hd] * x + M[hd];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    while (cin >> n >> m) {
        for (ll i = 1; i <= n; i++) {
            cin >> S[i];
            S[i] += S[i - 1];
        }
        hd = tl = 0;
        add(0, 0);
        ll dp = 0;
        for (ll i = 1; i <= n; i++) {
            dp = get(S[i]) + S[i] * S[i] + m;
            add(-2 * S[i], dp + S[i] * S[i]);
        }
        cout << dp << '\n';
    }
}
4 2
1 2 1 3

Mỗi bộ O(n)O(n).

Sắp và bỏ phần tử bị trội

Khi dữ liệu chưa có thứ tự, hãy tìm thứ tự làm chuyển tiếp thành đoạn liên tiếp và làm hệ số góc, điểm hỏi đơn điệu. Với các hình chữ nhật mua theo nhóm, chi phí nhóm là max⁡w⋅max⁡h\max w \cdot \max h. Một hình nằm gọn trong hình khác (cả ww lẫn hh không lớn hơn) đi cùng hình kia mà không tốn thêm, nên bỏ nó. Sau khi bỏ, sắp theo ww tăng thì hh giảm ngặt, và nhóm tối ưu luôn là một đoạn liên tiếp: chi phí đoạn [j+1,i][j + 1, i] là wi⋅hj+1w_i \cdot h_{j+1}.

Deque chỉ loại đường ở đầu khi thứ tự truy vấn đúng với chiều bao.Sắp slope và x trước khi dùng dequem giảm dầnx tăng dần
Sau khi bỏ hình bị chứa và sắp: hệ số góc giảm dần, điểm hỏi tăng dần, đúng điều kiện của hàng đợi hai đầu.

Ví dụGom Lô Đất

Đề. n≤5⋅104n \le 5 \cdot 10^4 lô đất wi×hiw_i \times h_i. Mua một nhóm tốn chiều rộng lớn nhất nhân chiều cao lớn nhất của nhóm. Chia thành các nhóm để tổng chi phí nhỏ nhất.

Lời giải. Một lô vừa hẹp hơn vừa thấp hơn một lô khác thì đi chung nhóm với lô đó miễn phí, nên bỏ được. Sau khi bỏ, sắp theo ww tăng thì hh giảm ngặt, và một nhóm tốt nhất luôn là một đoạn liên tiếp: nhóm j+1..ij + 1..i tốn wi⋅hj+1w_i \cdot h_{j+1} (rộng nhất là lô cuối, cao nhất là lô đầu). Vậy dpi=min⁡j<i(dpj+hj+1⋅wi)dp_i = \min_{j < i} (dp_j + h_{j+1} \cdot w_i): đường của jj có hệ số góc hj+1h_{j+1} giảm dần, điểm hỏi wiw_i tăng dần.

Bước 1: bỏ lô bị chứa. Sắp theo (w,h)(w, h); dùng ngăn xếp: lô mới cao không kém lô trên đỉnh thì lô trên đỉnh nằm gọn trong lô mới (hẹp hơn, thấp hơn), bỏ nó.

C++
sort(p + 1, p + n + 1);
for (ll i = 1; i <= n; i++) {
    while (m && h[m] <= p[i].second) m--;
    m++;
    w[m] = p[i].first;
    h[m] = p[i].second;
}

Bước 2: quy hoạch động. Đường của jj là (hj+1,dpj)(h_{j+1}, dp_j), thêm ngay sau khi có dpjdp_j (với j=0j = 0 là dp0=0dp_0 = 0). Lô cuối không cần thêm đường vì không còn lô nào sau nó.

C++
ll dp = 0;
add(h[1], 0);
for (ll i = 1; i <= m; i++) {
    dp = get(w[i]);
    if (i < m) add(h[i + 1], dp);
}

Chi phí tới 5⋅104⋅10125 \cdot 10^4 \cdot 10^{12}, dùng long long; so sánh giao điểm trong __int128.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 5e4 + 5;
ll n, m, w[N], h[N], K[N], M[N], hd, tl;
pair<ll, ll> p[N];

bool bad(ll a, ll b, ll c) {
    return (__int128)(M[c] - M[a]) * (K[a] - K[b]) <= (__int128)(M[b] - M[a]) * (K[a] - K[c]);
}

void add(ll k, ll v) {
    K[tl] = k, M[tl] = v;
    while (tl - hd >= 2 && bad(tl - 2, tl - 1, tl)) {
        K[tl - 1] = K[tl], M[tl - 1] = M[tl];
        tl--;
    }
    tl++;
}

ll get(ll x) {
    while (tl - hd >= 2 && K[hd + 1] * x + M[hd + 1] <= K[hd] * x + M[hd]) hd++;
    return K[hd] * x + M[hd];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (ll i = 1; i <= n; i++) cin >> p[i].first >> p[i].second;
    sort(p + 1, p + n + 1);
    for (ll i = 1; i <= n; i++) {
        while (m && h[m] <= p[i].second) m--;
        m++;
        w[m] = p[i].first;
        h[m] = p[i].second;
    }
    ll dp = 0;
    add(h[1], 0);
    for (ll i = 1; i <= m; i++) {
        dp = get(w[i]);
        if (i < m) add(h[i + 1], dp);
    }
    cout << dp << '\n';
}
3
2 5
4 3
6 2

O(nlog⁡n)O(n \log n) cho phần sắp, phần còn lại O(n)O(n).

Khi đi thi

  • Chuyển tiếp có tích f(i)⋅g(j)f(i) \cdot g(j) hoặc bình phương hiệu: tách thành phần theo ii, phần theo jj và tích chéo; tích chéo cho hệ số góc và điểm hỏi.
  • Kiểm tính đơn điệu: hệ số góc thêm vào có giảm (bài min) và điểm hỏi có tăng không. Không đơn điệu điểm hỏi thì tìm kiếm nhị phân trên bao; không đơn điệu hệ số góc thì cây Li Chao (bài 75).
  • So giao điểm bằng nhân chéo với __int128, xử lý hai đường cùng hệ số góc riêng; bài max thì đổi dấu hoặc đảo chiều các bất đẳng thức.

Tóm tắt

  • dpi=min⁡j(kjxi+mj)+cidp_i = \min_j (k_j x_i + m_j) + c_i: mỗi jj là một đường, cần bao dưới tại xix_i.
  • Hệ số góc giảm, điểm hỏi tăng: hàng đợi hai đầu, thêm ở cuối bỏ đường vô dụng, hỏi ở đầu bỏ đường đã thua; O(n)O(n).
  • Đường BB giữa AA và CC vô dụng khi (mC−mA)(kA−kB)≤(mB−mA)(kA−kC)(m_C - m_A)(k_A - k_B) \le (m_B - m_A)(k_A - k_C).
  • Bình phương hiệu khai triển thành đường; sắp và bỏ phần tử bị trội để có thứ tự đơn điệu.

Bài tập

1
Bao Dưới Độngcanuc80k4200Thêm đường và hỏi giá trị nhỏ nhất theo thứ tự tùy ý: cây Li Chao trên tọa độ hỏi đã nén (bài 75).
LQDOJ
2
Mức Giá Theo Phụ Phícanuc80k4201Sắp người mua theo bb giảm dần; bán món jj cho kk người đầu với giá bk+cjb_k + c_j thu k⋅cj+k⋅bkk \cdot c_j + k \cdot b_k, một đường theo cjc_j với hệ số góc kk; hỏi max tại mỗi cjc_j bằng bao trên.
LQDOJ
3
Mái Che Theo Cụmcanuc80k4204dpi=min⁡jdpj+c+(xi−xj+1)2dp_i = \min_j dp_j + c + (x_i - x_{j+1})^2; khai triển bình phương, hệ số góc theo xj+1x_{j+1} tăng, nn tới 10610^6 nên đọc nhanh.
LQDOJ
4
Phân Đoạn Sinh Lợicanuc80k4208Lợi nhuận đoạn là aS2+bS+caS^2 + bS + c với a<0a < 0, lấy lớn nhất; khai triển theo tổng tiền tố, bao trên với hàng đợi.
LQDOJ
5
Chọn Khung Chữ Nhậtcanuc80k4209Sắp hình chữ nhật theo xx tăng (khi đó yy giảm); dpi=xiyi−ai+max⁡j(dpj−xjyi)dp_i = x_i y_i - a_i + \max_j (dp_j - x_j y_i), hệ số góc −xj-x_j giảm, điểm hỏi yiy_i giảm.
LQDOJ
6
Trận Cuốicanuc80k4210Như bài hệ số sau mỗi trạm nhưng ss, ff tùy ý: hệ số góc không đơn điệu, dùng cây Li Chao.
LQDOJ
7
Lịch Cắt Câycanuc80k4211dpi=min⁡j<idpj+bjaidp_i = \min_{j < i} dp_j + b_j a_i với aa tăng, bb giảm: hàng đợi hai đầu như ví dụ đầu bài.
LQDOJ
8
Đóng Hộp Theo Thứ Tựcanuc80k4212Đặt fi=Si+if_i = S_i + i, chi phí đoạn là (fi−fj−L−1)2(f_i - f_j - L - 1)^2; khai triển ra đường với hệ số góc −2fj-2 f_j.
LQDOJ
9
Chia Dãy Thành Bình Phươngcanuc80k4215Chia thành đúng kk đoạn với chi phí bình phương tổng: kk tầng, mỗi tầng một hàng đợi hai đầu trên tầng trước.
LQDOJ
10
Cắt Tuyếncanuc80k4216Chi phí đoạn là tổng tích các cặp trong đoạn, viết bằng tổng tiền tố và tổng bình phương; m+1m + 1 tầng bao lồi.
LQDOJ
11
Di Chuyển Một Phần Tửcanuc80k4218Dời aia_i tới vị trí jj đổi tổng thêm một biểu thức tuyến tính theo aia_i với hệ số theo jj; dựng bao của mọi jj rồi tìm kiếm nhị phân cho từng ii, hai chiều riêng.
LQDOJ
12
Chọn Chuỗi Lượtcanuc80k4219Điểm đoạn [l,r][l, r] viết bằng hai loại tổng tiền tố, cố định rr được đường theo ll; bao trên với tìm kiếm nhị phân vì điểm hỏi không đơn điệu.
LQDOJ
13
Lịch Đón Mèocanuc80k4222Đổi mỗi con mèo thành thời điểm sớm nhất cần xuất phát, sắp; chia thành pp nhóm, chi phí nhóm qua tổng tiền tố, pp tầng bao lồi.
LQDOJ
14
K Nhát Cắtcanuc80k4223Điểm khi chia là tổng các tích tổng hai phần; kk tầng bao lồi với tổng tiền tố, lưu điểm chia để in vị trí cắt.
LQDOJ

Đọc thêm