Hướng dẫn giải của Câu 4 (Tuyển sinh chuyên Tin Bình Dương 2023-2024)
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
Có N phần quà (N chẵn), mỗi phần quà i có hai giá trị: xi nếu giao cho tổ 1 và yi nếu giao cho tổ 2. Mỗi tổ nhận đúng N/2 phần quà. Cần chia sao cho tổng giá trị của hai tổ là lớn nhất.
Ý chính: tổng luôn bằng tổng yi cộng với phần chênh lệch (xi - yi) của các phần quà giao cho tổ 1. Vậy chỉ cần chọn N/2 phần quà có xi - y_i lớn nhất cho tổ 1.
Sub 1: Vét cạn — ~O(2^n \cdot n)~, dùng khi ~n \le 20~
Thử mọi tập con gồm đúng N/2 phần quà giao cho tổ 1 (dùng bitmask, bỏ qua mask không có đúng N/2 bit), tính tổng rồi lấy max.
Chấm riêng sub này: đúng 5/50 test (các test có N <= 20 đều đúng).
Sub 2: Tham lam — ~O(n \log n)~, dùng cho mọi n
Tính di = xi - yi, sắp xếp giảm dần. Đáp án là tổng yi + tổng N/2 giá trị di lớn nhất đầu tiên. Lý do: mỗi phần quà giao cho tổ 1 thay vì tổ 2 làm thay đổi tổng đúng bằng di, nên ta chọn các d_i lớn nhất.
Ví dụ: N = 4, các cặp (1,2), (2,3), (3,5), (2,1). Tổng y = 11, các d = -1, -1, -2, 1. Chọn hai d lớn nhất là 1 và -1, tổng d = 0, đáp án là 11.
Bẫy: N có thể tới 5·10^5 nên dùng mảng động hoặc vector; giá trị đến 100 nên tổng có thể lớn, dùng long long cho an toàn.
Chương trình hoàn chỉnh
main đọc N và N cặp giá trị, nếu N <= 20 gọi sub1, còn lại gọi sub2.
#include <bits/stdc++.h>
using namespace std;
int n; // số phần quà (chẵn)
long long x[500005]; // x[i]: giá trị ưa thích của tổ 1 dán vào phần quà i
long long y[500005]; // y[i]: giá trị ưa thích của tổ 2 dán vào phần quà i
// Mỗi phần quà i giao cho tổ 1 thì được x[i], giao cho tổ 2 thì được y[i].
// Tổng = (tổng y[i] với mọi i) + (tổng x[i]-y[i] của các phần quà giao cho tổ 1).
// Mỗi tổ nhận đúng n/2 phần quà.
// Sub 1: vét cạn — thử mọi cách chọn n/2 phần quà cho tổ 1 (bitmask), lấy cách có tổng lớn nhất. Dùng khi n <= 20.
void sub1() {
long long sumY = 0;
for (int i = 0; i < n; i++) sumY += y[i];
long long best = LLONG_MIN;
for (int mask = 0; mask < (1 << n); mask++) {
if (__builtin_popcount(mask) != n / 2) continue;
long long total = sumY;
for (int i = 0; i < n; i++)
if (mask >> i & 1) total += x[i] - y[i];
best = max(best, total);
}
cout << best << "\n";
}
// Sub 2: tham lam. Sắp xếp các phần quà theo hiệu x[i]-y[i] giảm dần, n/2 phần quà đầu cho tổ 1 (lợi hơn khi giao cho tổ 1),
// n/2 phần còn lại cho tổ 2. O(n log n). Dùng cho mọi n.
void sub2() {
vector<long long> d(n);
long long total = 0;
for (int i = 0; i < n; i++) {
d[i] = x[i] - y[i];
total += y[i];
}
sort(d.begin(), d.end(), greater<long long>());
for (int i = 0; i < n / 2; i++) total += d[i];
cout << total << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
if (n <= 20) sub1(); // dữ liệu nhỏ: vét cạn
else sub2(); // tham lam
return 0;
}
Bình luận