Hướng dẫn giải của Câu 4 (Tuyển sinh chuyên Tin Bình Dương 2023-2024)


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

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

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.