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