Hướng dẫn giải của Các đồng xu (đề Daknong 2021)


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 tổ quạ, tổ i đang có ~a_i~ đồng xu và sẽ rơi khi có đúng ~b_i~ đồng. Nam có ~m~ đồng, ném trúng đích. Muốn lật tổ i, Nam ném thêm ~b_i - a_i~ đồng vào tổ, tổ rơi và anh nhặt được ~b_i~ đồng: số tiền của Nam tăng thêm đúng ~a_i~ (và lúc ném cần đang có ít nhất ~b_i - a_i~ đồng). Cần tìm số tiền lớn nhất sau khi lật các tổ theo thứ tự tùy chọn (mỗi tổ lật một lần).

Ví dụ: ~m = 3~; tổ 1 cần ném 3 đồng, lật xong có ~3 - 3 + 4 = 4~ đồng; tổ 2 cần ném 4 đồng, lật xong có ~4 - 4 + 6 = 6~ đồng.

Sub 1: Mô phỏng — ~O(n^2)~, dùng khi ~n \le 3000~

Lặp lại: quét các tổ chưa lật, tổ nào có chi phí ~b_i - a_i~ không vượt quá số tiền hiện có thì lật ngay (tiền tăng ~a_i~). Dừng khi một lượt quét không lật được tổ nào. Nếu chỉ nộp sub này: đúng 12/20 test (các test có ~n \le 3000~), các test lớn bị quá thời gian.

Sub 2: Sắp xếp theo chi phí — ~O(n \log n)~

Vì ~a_i \ge 0~ nên tiền không bao giờ giảm: tổ nào đã lật được thì sau đó vẫn lật được. Do đó chỉ cần sắp xếp các tổ theo chi phí ~b_i - a_i~ tăng dần và lật lần lượt, dừng khi tổ tiếp theo không đủ tiền. Tổng tiền có thể tới khoảng ~10^{10}~ nên dùng long long.

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

main đọc dữ liệu, nếu ~n \le 3000~ gọi sub1, còn lại gọi sub2. Khi đi thi nên viết như vậy: sub sau có sai hoặc chưa kịp làm thì vẫn ăn điểm sub trước. Kết quả chấm: đúng 20/20 test (sub1 chạy 12 test, sub2 chạy 8 test).

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

int n;
long long m;
long long a[100005], b[100005];

// Sub 1: mô phỏng trực tiếp. Lặp: tìm một tổ chưa lật mà số tiền hiện có >= b[i]-a[i]
// (ném thêm b[i]-a[i] đồng để tổ có đủ b[i] đồng, tổ rơi, nhặt được b[i] đồng => tiền tăng thêm a[i]).
// Mỗi lần quét hết n tổ, lặp tới khi không lật thêm được. O(n^2), dùng khi n <= 3000.
void sub1() {
    vector<bool> done(n + 1, false);
    long long cur = m;
    bool changed = true;
    while (changed) {
        changed = false;
        for (int i = 1; i <= n; i++)
            if (!done[i] && cur >= b[i] - a[i]) {
                done[i] = true;
                cur += a[i];
                changed = true;
            }
    }
    cout << cur << "\n";
}

// Sub 2: tiền chỉ tăng (a[i] >= 0) nên tổ nào lật được ở một thời điểm thì sau đó vẫn lật được.
// Sắp xếp các tổ theo chi phí b[i]-a[i] tăng dần, lật lần lượt tới khi tổ tiếp theo không đủ tiền. O(n log n).
void sub2() {
    vector<pair<long long, long long>> v;  // (chi phí, số xu đang có trong tổ)
    for (int i = 1; i <= n; i++) v.push_back({b[i] - a[i], a[i]});
    sort(v.begin(), v.end());
    long long cur = m;
    for (auto &p : v) {
        if (cur < p.first) break;
        cur += p.second;
    }
    cout << cur << "\n";
}

int main() {
    freopen("coins.inp", "r", stdin);
    freopen("coins.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    if (n <= 3000) sub1();
    else sub2();
    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.