Hướng dẫn giải của [200 đề thầy Đức] Bài 168: Độ lệch nhỏ nhất


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Hướng dẫn giải

Cho dãy ~A~ gồm ~N~ độ cao và số ~K~. Mỗi tháp phải được thay đổi đúng một lần: cộng ~K~ hoặc trừ ~K~ vào độ cao của nó. Cần chọn cách làm sao cho độ lệch giữa tháp cao nhất và tháp thấp nhất là nhỏ nhất.

Sub 1: Vét cạn — ~O(2^N \cdot N)~, dùng khi ~N \le 12~

Mỗi tháp có hai lựa chọn, nên có ~2^N~ tổ hợp. Thử hết các tổ hợp, với mỗi tổ hợp tính độ cao mới rồi lấy hiệu lớn nhất trừ nhỏ nhất, và giữ giá trị nhỏ nhất.

Sub 1 luôn cho đáp án tối ưu đúng theo đề trên mọi test có ~N \le 12~.

Sub 2: Sắp xếp và thử điểm chia — ~O(N \log N)~

Sắp xếp ~A~ tăng dần. Nhận xét: trong một lời giải tối ưu, các tháp thấp hơn nên được cộng ~K~ và các tháp cao hơn nên được trừ ~K~. Vì vậy chỉ cần thử các điểm chia: tháp ~0..i~ được cộng ~K~, tháp ~i+1..N-1~ bị trừ ~K~. Với mỗi điểm chia, độ cao lớn nhất là ~\max(a_i + K,\ a_{N-1} - K)~ và độ cao nhỏ nhất là ~\min(a_0 + K,\ a_{i+1} - K)~.

Bẫy cần chú ý: trường hợp tất cả tháp cùng cộng ~K~ (hoặc cùng trừ ~K~) cho độ lệch ~a_{N-1} - a_0~, nên đáp án khởi tạo bằng giá trị này.

Chương trình hoàn chỉnh

Chương trình đọc ~T~, rồi với mỗi bộ dữ liệu đọc ~N~, ~K~ và dãy ~A~. Nếu ~N \le 12~ thì gọi sub1, ngược lại gọi sub2. Khi đi thi nên viết như vậy: sub1 vẫn ăn điểm các test nhỏ nếu sub2 chưa làm kịp.

#include <bits/stdc++.h>
using namespace std;

int N, K;
vector<int> A;

// Sub 1: vét cạn — mỗi tháp có 2 lựa chọn (cộng K hoặc trừ K), thử mọi tổ hợp 2^N,
// tính độ lệch max - min và lấy giá trị nhỏ nhất. Dùng khi N <= 12
void sub1() {
    int best = INT_MAX;
    for (int mask = 0; mask < (1 << N); mask++) {
        int lo = INT_MAX, hi = INT_MIN;
        for (int i = 0; i < N; i++) {
            int h = (mask >> i & 1) ? A[i] + K : A[i] - K;   // bit 1: cộng K, bit 0: trừ K
            lo = min(lo, h);
            hi = max(hi, h);
        }
        best = min(best, hi - lo);
    }
    cout << best << "\n";
}

// Sub 2: sắp xếp, rồi thử điểm chia: các tháp nhỏ hơn hoặc bằng a[i] được cộng K, các tháp lớn hơn thì trừ K.
// Đáp án là min trên các điểm chia. Độ phức tạp O(N log N). Dùng cho mọi N
void sub2() {
    vector<int> a = A;
    sort(a.begin(), a.end());
    int best = a[N - 1] - a[0];   // trường hợp tất cả cùng cộng K (hoặc cùng trừ K): độ lệch không đổi
    for (int i = 0; i + 1 < N; i++) {
        // Tháp 0..i được cộng K, các tháp i+1..N-1 bị trừ K
        int hi = max(a[i] + K, a[N - 1] - K);
        int lo = min(a[0] + K, a[i + 1] - K);
        best = min(best, hi - lo);
    }
    cout << best << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        cin >> N >> K;
        A.assign(N, 0);
        for (int i = 0; i < N; i++) cin >> A[i];
        if (N <= 12) sub1();   // N nhỏ: vét cạn cho chắc
        else sub2();           // N lớn: sắp xếp và thử các điểm chia
    }
    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.