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

Divide and Conquer DP and Knuth

Hai dạng quy hoạch động hay gặp có chuyển tiếp tốn O(n)O(n) mỗi trạng thái. Chia dãy thành kk đoạn: dp[t][i]=min⁡j<idp[t−1][j]+cost(j+1,i)dp[t][i] = \min_{j < i} dp[t-1][j] + cost(j + 1, i), tổng O(kn2)O(kn^2). Quy hoạch động trên đoạn: dp[l][r]=min⁡l≤m<rdp[l][m]+dp[m+1][r]+w(l,r)dp[l][r] = \min_{l \le m < r} dp[l][m] + dp[m+1][r] + w(l, r), tổng O(n3)O(n^3). Khi điểm đạt min, gọi là optopt, di chuyển đơn điệu theo trạng thái, ta không cần thử mọi jj hay mọi mm. Chia để trị đưa dạng thứ nhất về O(knlog⁡n)O(kn \log n); Knuth đưa dạng thứ hai về O(n2)O(n^2).

Điểm tối ưu đơn điệu

Xét một tầng: g[i]=min⁡j<if[j]+cost(j+1,i)g[i] = \min_{j < i} f[j] + cost(j + 1, i), với ff là tầng trước. Gọi opt(i)opt(i) là jj nhỏ nhất đạt min. Nhiều chi phí thỏa opt(1)≤opt(2)≤⋯≤opt(n)opt(1) \le opt(2) \le \dots \le opt(n): đoạn cuối kết thúc càng xa thì điểm bắt đầu của nó càng không lùi. Khi đó tính gg bằng chia để trị: tính g[mid]g[mid] cho midmid ở giữa bằng cách thử mọi jj trong khoảng ứng viên [optL,optR][optL, optR]; được opt(mid)opt(mid), thì nửa trái chỉ cần thử j∈[optL,opt(mid)]j \in [optL, opt(mid)], nửa phải j∈[opt(mid),optR]j \in [opt(mid), optR]. Mỗi tầng đệ quy có tổng độ dài các khoảng ứng viên O(n)O(n), có log⁡n\log n tầng, nên một lớp tốn O(nlog⁡n)O(n \log n), kk lớp tốn O(knlog⁡n)O(kn \log n).

Hàng i là các j cần thử cho g[i]; thanh màu là khoảng ứng viên thật sự được thử, chấm đỏ là opt(i).

Điều kiện đủ hay dùng là bất đẳng thức tứ giác: cost(a,c)+cost(b,d)≤cost(a,d)+cost(b,c)cost(a, c) + cost(b, d) \le cost(a, d) + cost(b, c) với mọi a≤b≤c≤da \le b \le c \le d. Bình phương tổng đoạn, số cặp bằng nhau trong đoạn, tổng khoảng cách tới trung vị đều thỏa. Trong lúc thi, cách kiểm nhanh nhất là chạy bản O(kn2)O(kn^2) trên dữ liệu ngẫu nhiên nhỏ, in optopt và xem có giảm lần nào không.

Hỏi nhanh

Vì sao tối ưu chia để trị chỉ tốn O(nlog⁡n)O(n \log n) cho một lớp, dù mỗi midmid có thể thử nhiều ứng viên?

Ví dụBình Phương Các Khối

Đề. Dãy n≤3000n \le 3000 số dương, chia thành đúng kk đoạn, chi phí mỗi đoạn là bình phương tổng của nó. Tìm tổng nhỏ nhất.

Lời giải. ft[i]f_t[i] là chi phí nhỏ nhất chia ii phần tử đầu thành tt đoạn: ft[i]=min⁡jft−1[j]+cost(j+1,i)f_t[i] = \min_j f_{t-1}[j] + cost(j + 1, i) với cost(l,r)=(Pr−Pl−1)2cost(l, r) = (P_r - P_{l-1})^2, PP là tổng tiền tố. Điểm chia tốt nhất opt(i)opt(i) không giảm theo ii, nên mỗi tầng tính được bằng chia để trị như trên trong O(nlog⁡n)O(n \log n) thay vì O(n2)O(n^2).

Bước 1: chi phí một đoạn. Tổng tiền tố tính tại chỗ khi đọc.

C++
ll cost(ll l, ll r) {
    return (pre[r] - pre[l - 1]) * (pre[r] - pre[l - 1]);
}

Bước 2: chia để trị một tầng. f là tầng t−1t - 1, g là tầng tt. solve(l, r, optl, optr) tính g[mid] bằng cách thử mọi jj trong khoảng ứng viên [optl,min⁡(mid−1,optr)][optl, \min(mid - 1, optr)] và nhớ jj tốt nhất bo; rồi nửa trái chỉ thử j≤boj \le bo, nửa phải chỉ thử j≥boj \ge bo. Bỏ qua f[j] == INF (tiền tố chưa chia được thành t−1t - 1 đoạn) để khỏi cộng tràn.

C++
void solve(ll l, ll r, ll optl, ll optr) {
    if (l > r) return;
    ll mid = (l + r) >> 1, best = INF, bo = optl;
    for (ll j = optl; j <= min(mid - 1, optr); j++) {
        if (f[j] == INF) continue;
        ll v = f[j] + cost(j + 1, mid);
        if (v < best) {
            best = v;
            bo = j;
        }
    }
    g[mid] = best;
    solve(l, mid - 1, optl, bo);
    solve(mid + 1, r, bo, optr);
}

Bước 3: kk tầng. Tầng 0 chỉ có f[0] = 0; mỗi tầng g[0] = INF vì không chia 0 phần tử thành t≥1t \ge 1 đoạn, xong thì chép g sang f.

C++
for (ll t = 1; t <= k; t++) {
    g[0] = INF;
    solve(1, n, 0, n - 1);
    for (ll i = 0; i <= n; i++) f[i] = g[i];
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 3005, INF = 4e18;
ll n, k, pre[N], f[N], g[N];

ll cost(ll l, ll r) {
    return (pre[r] - pre[l - 1]) * (pre[r] - pre[l - 1]);
}

void solve(ll l, ll r, ll optl, ll optr) {
    if (l > r) return;
    ll mid = (l + r) >> 1, best = INF, bo = optl;
    for (ll j = optl; j <= min(mid - 1, optr); j++) {
        if (f[j] == INF) continue;
        ll v = f[j] + cost(j + 1, mid);
        if (v < best) {
            best = v;
            bo = j;
        }
    }
    g[mid] = best;
    solve(l, mid - 1, optl, bo);
    solve(mid + 1, r, bo, optr);
}

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

    cin >> n >> k;
    for (ll i = 1; i <= n; i++) {
        cin >> pre[i];
        pre[i] += pre[i - 1];
        f[i] = INF;
    }
    for (ll t = 1; t <= k; t++) {
        g[0] = INF;
        solve(1, n, 0, n - 1);
        for (ll i = 0; i <= n; i++) f[i] = g[i];
    }
    cout << f[n] << '\n';
}
5 2
1 3 2 4 2

Mỗi tầng đệ quy của solve thử tổng cộng O(n)O(n) ứng viên, có log⁡n\log n tầng: O(knlog⁡n)≈108O(kn \log n) \approx 10^8 phép, so với O(kn2)=2,7⋅1010O(kn^2) = 2{,}7 \cdot 10^{10}.

Chi phí từ tổng tiền tố hai chiều

Khi chi phí của đoạn [l,r][l, r] là tổng một ma trận trên hình vuông [l,r]×[l,r][l, r] \times [l, r], như tổng xung đột giữa mọi cặp người cùng toa, tổng tiền tố hai chiều (bài 02) cho nó trong O(1)O(1): sq(l,r)=Sr,r−Sl−1,r−Sr,l−1+Sl−1,l−1sq(l, r) = S_{r,r} - S_{l-1,r} - S_{r,l-1} + S_{l-1,l-1}, và chi phí là một nửa vì mỗi cặp được đếm hai lần. Chi phí này thỏa bất đẳng thức tứ giác khi các phần tử ma trận không âm. Với nn cỡ vài nghìn, chú ý bộ nhớ: bảng 4001×40014001 \times 4001 số int khoảng 64 MB.

Ví dụCác Toa Yên Tĩnh

Đề. N≤4000N \le 4000 hành khách xếp hàng, ma trận xung đột uu là chữ số 0 đến 9. Chia thành đúng K≤800K \le 800 toa (đoạn liên tiếp), chi phí mỗi toa là tổng uiju_{ij} trên các cặp trong toa. Tìm tổng nhỏ nhất.

Lời giải. Cùng khuôn chia để trị như ví dụ trước; chỉ đổi hàm chi phí. Chi phí một toa [l,r][l, r] là tổng uiju_{ij} trên các cặp trong toa, lấy ra trong O(1)O(1) từ tổng tiền tố hai chiều.

Bước 1: tổng tiền tố hai chiều khi đọc. Mỗi hàng ma trận là một xâu chữ số, cộng dồn ngay. Giá trị không quá 9⋅40002≈1,4⋅1089 \cdot 4000^2 \approx 1{,}4 \cdot 10^8 nên dùng int, để bảng 400524005^2 chỉ tốn khoảng 64 MB.

C++
for (ll i = 1; i <= n; i++) {
    string s;
    cin >> s;
    for (ll j = 1; j <= n; j++) S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + (s[j - 1] - '0');
}

Bước 2: chi phí một toa. Lấy tổng hình vuông [l,r]2[l, r]^2 rồi chia 2 vì mỗi cặp nằm hai lần trong ma trận đối xứng (đường chéo bằng 0).

C++
int cost(ll l, ll r) {
    return (S[r][r] - S[l - 1][r] - S[r][l - 1] + S[l - 1][l - 1]) / 2;
}

Bước 3: KK tầng chia để trị. Hàm solve giống hệt ví dụ trước, đổi sang int.

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

const ll N = 4005;
const int INF = 2e9;
ll n, k;
int S[N][N], f[N], g[N];

int cost(ll l, ll r) {
    return (S[r][r] - S[l - 1][r] - S[r][l - 1] + S[l - 1][l - 1]) / 2;
}

void solve(ll l, ll r, ll optl, ll optr) {
    if (l > r) return;
    ll mid = (l + r) >> 1, bo = optl;
    int best = INF;
    for (ll j = optl; j <= min(mid - 1, optr); j++) {
        if (f[j] == INF) continue;
        int v = f[j] + cost(j + 1, mid);
        if (v < best) {
            best = v;
            bo = j;
        }
    }
    g[mid] = best;
    solve(l, mid - 1, optl, bo);
    solve(mid + 1, r, bo, optr);
}

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

    cin >> n >> k;
    for (ll i = 1; i <= n; i++) {
        string s;
        cin >> s;
        for (ll j = 1; j <= n; j++) S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + (s[j - 1] - '0');
    }
    for (ll i = 1; i <= n; i++) f[i] = INF;
    for (ll t = 1; t <= k; t++) {
        g[0] = INF;
        solve(1, n, 0, n - 1);
        for (ll i = 0; i <= n; i++) f[i] = g[i];
    }
    cout << f[n] << '\n';
}
5 2
02879
20168
81027
76201
98710

Khoảng 800⋅4000⋅12≈4⋅107800 \cdot 4000 \cdot 12 \approx 4 \cdot 10^7 lần tính chi phí.

Chi phí tính bằng hai con trỏ

Có chi phí không tách được thành hiệu các tổng tiền tố, như số cặp phần tử bằng nhau trong đoạn. Nhưng trong chia để trị, các đoạn được hỏi liên tiếp nhau thay đổi rất ít: với một midmid, jj chạy liên tục trong khoảng ứng viên; sang lời gọi con, midmid và khoảng ứng viên chỉ dời một nửa. Giữ một cửa sổ [L,R][L, R] cùng bảng đếm như thuật toán Mo (bài 25), mỗi lần hỏi thì dời hai biên tới đoạn cần. Tổng số bước dời của một lớp là O(nlog⁡n)O(n \log n), nên cả bài O(knlog⁡n)O(kn \log n) với hằng số nhỏ.

Ví dụCặp Trùng Trong Đoạn

Đề. Dãy N≤105N \le 10^5 số, chia thành đúng K≤20K \le 20 đoạn. Chi phí một đoạn là số cặp chỉ số trong nó có giá trị bằng nhau. Tìm tổng nhỏ nhất.

Lời giải. Chi phí số cặp trùng thỏa bất đẳng thức tứ giác, nên điểm chia tốt nhất đơn điệu và chia để trị dùng được. Khó khăn là chi phí không có công thức O(1)O(1). Nhưng trong solve, các đoạn được hỏi thay đổi từng chút một, nên tính chi phí bằng một cửa sổ kiểu Mo (bài 25): giữ cửa sổ hiện tại và dời hai biên tới đoạn cần hỏi.

Bước 1: cửa sổ đếm cặp trùng. cl, cr, cur là cửa sổ hiện tại và số cặp trùng trong đó; cnt đếm số lần mỗi giá trị. Phần tử mới tạo thêm cnt[a[i]] cặp với các bản đã có, rồi tăng đếm; bỏ thì làm ngược lại.

C++
void add(ll i) {
    cur += cnt[a[i]]++;
}

void del(ll i) {
    cur -= --cnt[a[i]];
}

Bước 2: dời cửa sổ tới [l,r][l, r]. Mở rộng hai biên trước, rồi mới thu hẹp, để cửa sổ không bao giờ “âm”.

C++
ll cost(ll l, ll r) {
    while (cr < r) add(++cr);
    while (cl > l) add(--cl);
    while (cr > r) del(cr--);
    while (cl < l) del(cl++);
    return cur;
}

Bước 3: chia để trị giống hệt các ví dụ trước. Trong solve, các đoạn [j+1,mid][j + 1, mid] được hỏi theo jj tăng dần, mỗi lần biên trái dời một bước; giữa các lời gọi, midmid và khoảng ứng viên đổi theo cấu trúc chia đôi. Đề bảo đảm giá trị không quá NN nên cnt dùng mảng trực tiếp.

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

const ll N = 1e5 + 5, INF = 4e18;
ll n, k, a[N], cnt[N], f[N], g[N], cl = 1, cr = 0, cur = 0;

void add(ll i) {
    cur += cnt[a[i]]++;
}

void del(ll i) {
    cur -= --cnt[a[i]];
}

ll cost(ll l, ll r) {
    while (cr < r) add(++cr);
    while (cl > l) add(--cl);
    while (cr > r) del(cr--);
    while (cl < l) del(cl++);
    return cur;
}

void solve(ll l, ll r, ll optl, ll optr) {
    if (l > r) return;
    ll mid = (l + r) >> 1, best = INF, bo = optl;
    for (ll j = optl; j <= min(mid - 1, optr); j++) {
        if (f[j] == INF) continue;
        ll v = f[j] + cost(j + 1, mid);
        if (v < best) {
            best = v;
            bo = j;
        }
    }
    g[mid] = best;
    solve(l, mid - 1, optl, bo);
    solve(mid + 1, r, bo, optr);
}

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

    cin >> n >> k;
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
        f[i] = INF;
    }
    for (ll t = 1; t <= k; t++) {
        g[0] = INF;
        solve(1, n, 0, n - 1);
        for (ll i = 0; i <= n; i++) f[i] = g[i];
    }
    cout << f[n] << '\n';
}
7 3
1 1 3 3 3 2 1

Tổng số bước dời cửa sổ của một tầng là O(nlog⁡n)O(n \log n): cả bài O(Knlog⁡n)O(Kn \log n).

Knuth cho quy hoạch động trên đoạn

Ghép các khối kề nhau, cắt thanh theo nhiều điểm, dựng cây tìm kiếm tối ưu: dp[l][r]=min⁡l≤m<rdp[l][m]+dp[m+1][r]+w(l,r)dp[l][r] = \min_{l \le m < r} dp[l][m] + dp[m+1][r] + w(l, r). Nếu ww thỏa bất đẳng thức tứ giác và đơn điệu theo bao hàm (w(b,c)≤w(a,d)w(b, c) \le w(a, d) khi [b,c]⊆[a,d][b, c] \subseteq [a, d]), thì điểm chia tốt nhất thỏa

opt[l][r−1]≤opt[l][r]≤opt[l+1][r].opt[l][r-1] \le opt[l][r] \le opt[l+1][r].

Tính dpdp theo độ dài đoạn tăng dần, và với đoạn [l,r][l, r] chỉ thử mm trong khoảng trên. Tổng số lần thử trên mọi đoạn cùng độ dài là một tổng lồng: ∑l(opt[l+1][r]−opt[l][r−1]+1)=O(n)\sum_l (opt[l+1][r] - opt[l][r-1] + 1) = O(n), nên cả bài O(n2)O(n^2) thay cho O(n3)O(n^3).

Hỏi nhanh

Trong tối ưu Knuth, đoạn [l,r][l, r] chỉ thử điểm chia mm từ opt[l][r−1]opt[l][r-1] tới opt[l+1][r]opt[l+1][r]. Vì sao tổng số lần thử của mọi đoạn cùng độ dài là O(n)O(n)?

Ví dụGhép Theo Thứ Tự

Đề. T≤10T \le 10 bộ, mỗi bộ K≤5000K \le 5000 khối xếp hàng. Ghép hai khối kề tốn tổng kích thước của chúng. Tìm tổng chi phí nhỏ nhất để ghép tất cả thành một.

Lời giải. Quy hoạch động khoảng: dp[l][r]=min⁡m(dp[l][m]+dp[m+1][r])+w(l,r)dp[l][r] = \min_m (dp[l][m] + dp[m+1][r]) + w(l, r), với w(l,r)w(l, r) là tổng kích thước đoạn. ww thỏa cả hai điều kiện của Knuth, nên điểm chia tốt nhất kẹp giữa opt[l][r−1]opt[l][r-1] và opt[l+1][r]opt[l+1][r]. Bảng 5000×50005000 \times 5000 khá nặng: chi phí tối ưu không quá tổng kích thước nhân ⌈log⁡2K⌉\lceil \log_2 K \rceil (gộp kiểu cây cân bằng), khoảng 6,5⋅1086{,}5 \cdot 10^8, nên dp dùng int; opt dùng short vì chỉ số không quá 5000. Tổng hai bảng khoảng 150 MB.

Bước 1: đoạn độ dài 1. dp = 0, opt = i; tổng tiền tố tính ngay khi đọc.

C++
for (ll i = 1; i <= n; i++) {
    cin >> pre[i];
    pre[i] += pre[i - 1];
    dp[i][i] = 0;
    opt[i][i] = i;
}

Bước 2: đoạn dài dần, chỉ thử trong khoảng kẹp. Đoạn [l,r][l, r] chỉ thử mm từ opt[l][r - 1] tới opt[l + 1][r] (kẹp không quá r−1r - 1); hai giá trị này đã có vì đoạn ngắn hơn được tính trước. w(l,r)w(l, r) cộng sau khi chọn mm, vì không phụ thuộc mm.

C++
for (ll len = 2; len <= n; len++)
    for (ll l = 1; l + len - 1 <= n; l++) {
        ll r = l + len - 1, lo = opt[l][r - 1], hi = min((ll)opt[l + 1][r], r - 1);
        int best = INF;
        ll bm = lo;
        for (ll m = lo; m <= hi; m++) {
            int v = dp[l][m] + dp[m + 1][r];
            if (v < best) {
                best = v;
                bm = m;
            }
        }
        dp[l][r] = best + (pre[r] - pre[l - 1]);
        opt[l][r] = bm;
    }
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 5005;
const int INF = 2e9;
int dp[N][N];
short opt[N][N];
ll pre[N];

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

    ll T;
    cin >> T;
    while (T--) {
        ll n;
        cin >> n;
        for (ll i = 1; i <= n; i++) {
            cin >> pre[i];
            pre[i] += pre[i - 1];
            dp[i][i] = 0;
            opt[i][i] = i;
        }
        for (ll len = 2; len <= n; len++)
            for (ll l = 1; l + len - 1 <= n; l++) {
                ll r = l + len - 1, lo = opt[l][r - 1], hi = min((ll)opt[l + 1][r], r - 1);
                int best = INF;
                ll bm = lo;
                for (ll m = lo; m <= hi; m++) {
                    int v = dp[l][m] + dp[m + 1][r];
                    if (v < best) {
                        best = v;
                        bm = m;
                    }
                }
                dp[l][r] = best + (pre[r] - pre[l - 1]);
                opt[l][r] = bm;
            }
        cout << dp[1][n] << '\n';
    }
}
2
3
5 1 4
4
8 2 2 8

Với một độ dài cố định, khoảng thử của các đoạn liền nhau nối đuôi nhau, tổng độ dài không quá nn cộng số đoạn: cả bài O(n2)O(n^2) thay vì O(n3)O(n^3).

Khi đi thi

  • Chia dãy thành kk đoạn với nn vài nghìn tới 10510^5 và chi phí “càng dài càng tệ một cách lồi”: thử chia để trị. Kiểm optopt đơn điệu bằng bản chậm trên dữ liệu ngẫu nhiên nhỏ trước khi tin.
  • Chi phí không tách được bằng tiền tố: hai con trỏ kiểu Mo ngay trong hàm chi phí; mở rộng trước, thu hẹp sau.
  • Quy hoạch động đoạn n≤5000n \le 5000: Knuth O(n2)O(n^2); tính bộ nhớ của hai bảng n2n^2, dùng int hay short nếu giá trị cho phép. Khi kk không cố định mà đề cho phạt mỗi đoạn, nghĩ tới kỹ thuật Alien (bài 78).

Tóm tắt

  • Một lớp g[i]=min⁡jf[j]+cost(j+1,i)g[i] = \min_j f[j] + cost(j+1, i) với optopt đơn điệu: chia để trị trên ii, khoảng ứng viên thu hẹp theo opt(mid)opt(mid), O(nlog⁡n)O(n \log n) mỗi lớp.
  • Bất đẳng thức tứ giác của chi phí bảo đảm optopt đơn điệu.
  • Chi phí đoạn: tổng tiền tố một hay hai chiều, hoặc cửa sổ Mo khi không tách được.
  • Knuth: opt[l][r−1]≤opt[l][r]≤opt[l+1][r]opt[l][r-1] \le opt[l][r] \le opt[l+1][r] cho quy hoạch động đoạn, O(n2)O(n^2).

Bài tập

1
Vùng Giám Sátcanuc80k4101Chi phí cost(l,r)=(Pr−Pl−1)⋅(r−l+1)cost(l, r) = (P_r - P_{l-1}) \cdot (r - l + 1); dùng đúng min⁡(L,G)\min(L, G) nhóm, mỗi tầng một lần chia để trị.
LQDOJ
2
Những Vết Cắtcanuc80k4103Sắp các điểm cắt, thêm 00 và NN; dp[l][r]dp[l][r] trên các điểm cắt liên tiếp với ww là độ dài mảnh, tối ưu Knuth.
LQDOJ
3
Trường Trên Phốcanuc80k4105Một trường phục vụ một đoạn nhà; đặt trường ở trung vị có trọng số của đoạn, chi phí tính bằng tổng tiền tố của cic_i và i⋅cii \cdot c_i; chia để trị theo KK.
LQDOJ
4
Gom Quặng Xuôi Dòngcanuc80k4106Đống cuối của một đoạn nằm ở bến cuối của đoạn, chi phí Xr∑W−∑WXX_r \sum W - \sum W X trên đoạn; chia để trị hoặc Knuth, nhiều bộ tới hết input.
LQDOJ
5
Các Khay Hương Vịcanuc80k4107Lấy lớn nhất số loại khác nhau; chi phí đoạn tính bằng cửa sổ Mo với bảng đếm loại, chia để trị theo KK.
LQDOJ
6
Điểm OR Theo Đoạncanuc80k4108OR của đoạn lấy từ bảng thưa (bài 22); lấy lớn nhất với optopt đơn điệu, chia để trị theo từng tầng.
LQDOJ
7
Phân Nhóm Trạm Liên Lạccanuc80k4109Dijkstra từ trung tâm trên đồ thị và đồ thị ngược cho sis_i; nhóm mm chi nhánh tốn (m−1)∑s(m - 1) \sum s, nên sắp ss giảm dần, nhóm là đoạn liên tiếp, rồi chia để trị.
LQDOJ
8
Khoảng Xuất Hiệncanuc80k4111Chi phí đoạn là tổng last−firstlast - first; cửa sổ Mo cập nhật được khi thêm hay bỏ ở hai đầu bằng vị trí xuất hiện trước và sau, chia để trị theo KK.
LQDOJ
9
Cửa Vào Vòng Tròncanuc80k4112Thử mọi phòng làm cửa đầu để cắt vòng thành dãy; trên dãy, đoạn có cửa ở đầu tốn tổng rir_i nhân khoảng cách, quy hoạch động KK tầng.
LQDOJ
10
Các Chặng Thử Tháchcanuc80k4113Kỳ vọng một vùng là ∑j(tl+⋯+tj)/tj\sum_j (t_l + \dots + t_j) / t_j, tách được bằng tổng tiền tố của Pj/tjP_j / t_j và 1/tj1 / t_j; chia để trị (hoặc bao lồi đường thẳng, bài 42).
LQDOJ

Đọc thêm