Hướng dẫn giải của Chọn sách (Đề HSG Nghệ An 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

Chọn nhiều sách nhất để xếp thành chồng sao cho sách trên có cả chiều dài và chiều rộng nhỏ hơn hẳn sách dưới. Đây là bài dãy con tăng dài nhất trên hai chiều.

Sub 1: Quy hoạch động — ~O(n^2)~, dùng khi ~n \le 2000~

Sắp xếp các sách theo ~d~ tăng dần, nếu ~d~ bằng nhau thì ~r~ giảm dần. Gọi ~f[i]~ là số sách nhiều nhất của chồng có sách ~i~ nằm dưới cùng; ~f[i] = 1 + \max f[j]~ với ~j < i~, ~d_j < d_i~, ~r_j < r_i~. Đáp án là ~\max f[i]~. Nếu chỉ nộp sub này: đúng 18/25 test.

Sub 2: Dãy con tăng ngặt dài nhất — ~O(n \log n)~

Sau khi sắp xếp như trên, chỉ cần tìm dãy con tăng ngặt dài nhất theo ~r~ (hai sách cùng ~d~ có ~r~ giảm dần nên không thể cùng nằm trong dãy tăng ngặt). Dùng mảng ~tail~ và lower_bound (tìm vị trí đầu tiên ~\ge r~, đảm bảo ngặt). Nộp riêng sub này đúng 25/25 test.

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

Hàm ~main~ đọc dữ liệu từ file, sắp xếp; nếu ~n \le 2000~ gọi sub1, ngược 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. Chương trình đạt 25/25 test.

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

int n;
pair<int, int> b[200005];   // (d, r)
int f[200005];

// Sub 1: sắp xếp theo d tăng (d bằng nhau thì r giảm), quy hoạch động O(n^2):
// f[i] = số sách nhiều nhất của chồng có sách i ở dưới cùng. Dùng khi n <= 2000
void sub1() {
    int best = 0;
    for (int i = 0; i < n; i++) {
        f[i] = 1;
        for (int j = 0; j < i; j++)
            if (b[j].first < b[i].first && b[j].second < b[i].second)
                f[i] = max(f[i], f[j] + 1);
        best = max(best, f[i]);
    }
    cout << best << "\n";
}

// Sub 2: sau khi sắp xếp, đáp án là dãy con tăng NGẶT dài nhất theo r, tìm bằng chặt nhị phân O(n log n)
void sub2() {
    vector<int> tail;
    for (int i = 0; i < n; i++) {
        auto it = lower_bound(tail.begin(), tail.end(), b[i].second);
        if (it == tail.end()) tail.push_back(b[i].second);
        else *it = b[i].second;
    }
    cout << tail.size() << "\n";
}

int main() {
    freopen("chonsach.inp", "r", stdin);
    freopen("chonsach.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> n;
    for (int i = 0; i < n; i++) cin >> b[i].first >> b[i].second;
    // d bằng nhau thì xếp r giảm dần để hai sách cùng d không bao giờ cùng nằm trong một dãy tăng ngặt
    sort(b, b + n, [](const pair<int,int> &x, const pair<int,int> &y) {
        if (x.first != y.first) return x.first < y.first;
        return x.second > y.second;
    });
    if (n <= 2000) 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.