Hướng dẫn giải của [Free Contest 05] HORROR


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

Yêu cầu: chọn thứ tự xem ~N \le 7~ bộ phim để xem trọn được nhiều bộ nhất, nếu có nhiều phương án thì chọn thứ tự từ điển nhỏ nhất.

Sub 1: Thử mọi thứ tự — ~O(N! \cdot N)~, dùng khi ~N \le 5~

Với mỗi hoán vị, mô phỏng: độ sợ ban đầu 74; phim ~k~ xem trọn khi ~F \ge S_k~ và ~F + 47 \ge L_k~, sau đó ~F \leftarrow F + 47 - L_k~. Gặp phim không xem được thì dừng. Lấy hoán vị đầu tiên theo thứ tự từ điển có số phim xem trọn lớn nhất.

Nếu chỉ nộp sub này: đúng 70/70 test.

Sub 2: Quy hoạch bitmask — ~O(2^N \cdot N^2)~

Độ sợ chỉ phụ thuộc tập phim đã xem chứ không phụ thuộc thứ tự, nên trạng thái là một tập con. Tính các tập xem được, số phim lớn nhất ~w~, rồi chọn tham lam phần tử nhỏ nhất ở mỗi vị trí sao cho vẫn đạt ~w~ phim. Phần còn lại xếp tăng dần.

Nếu chỉ nộp sub này: đúng 70/70 test.

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

Với ~N \le 5~ gọi sub 1, còn lại gọi sub 2. Sub sau có sai hoặc chưa kịp làm thì vẫn ăn điểm sub trước.

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

int N;
long long L[8], S[8];

// Độ sợ của John sau khi đã xem trọn các phim trong tập mask (không phụ thuộc thứ tự xem)
long long fearOf(int mask) {
    long long F = 74;
    for (int i = 0; i < N; i++)
        if (mask >> i & 1) F += 47 - L[i];
    return F;
}

// Xem trọn phim i khi đã xem trọn tập mask:
// cảnh kinh dị lúc phút S[i] cần F - S[i] >= 0 (không ngủ trước đó),
// và cuối phim cần F + 47 - L[i] >= 0 (độ sợ giảm dần đến cuối phim).
bool canWatch(int mask, int i) {
    long long F = fearOf(mask);
    return F >= S[i] && F + 47 >= L[i];
}

// Sub 1: thử mọi thứ tự xem O(N! * N) — dùng khi N <= 5
// Lấy thứ tự từ điển nhỏ nhất đạt số phim xem trọn nhiều nhất.
void sub1() {
    vector<int> p(N);
    iota(p.begin(), p.end(), 0);
    int best = -1;
    vector<int> bestP;
    do {
        int mask = 0, cnt = 0;
        for (int i : p) {
            if (!canWatch(mask, i)) break;   // John ngủ, không xem được nữa
            mask |= 1 << i;
            cnt++;
        }
        if (cnt > best) { best = cnt; bestP = p; }
    } while (next_permutation(p.begin(), p.end()));
    for (int x : bestP) cout << x << "\n";
}

// Sub 2: quy hoạch bitmask O(2^N * N) — lời giải đầy đủ
// Độ sợ chỉ phụ thuộc tập phim đã xem, nên trạng thái là tập mask.
void sub2() {
    int full = 1 << N;
    vector<char> reach(full, 0), good(full, 0);
    reach[0] = 1;
    int w = 0;   // số phim xem trọn nhiều nhất
    for (int m = 0; m < full; m++) {
        if (!reach[m]) continue;
        w = max(w, __builtin_popcount(m));
        for (int i = 0; i < N; i++)
            if (!(m >> i & 1) && canWatch(m, i)) reach[m | 1 << i] = 1;
    }
    // good[m]: từ tập m có thể xem thêm để đạt đủ w phim
    for (int m = full - 1; m >= 0; m--) {
        if (__builtin_popcount(m) == w) { good[m] = reach[m]; continue; }
        for (int i = 0; i < N; i++)
            if (!(m >> i & 1) && canWatch(m, i) && good[m | 1 << i]) { good[m] = 1; break; }
    }
    // Chọn tham lam phần tử nhỏ nhất ở mỗi vị trí; phần còn lại xếp tăng dần
    vector<int> ord;
    int cur = 0;
    for (int pos = 0; pos < w; pos++) {
        for (int i = 0; i < N; i++)
            if (!(cur >> i & 1) && canWatch(cur, i) && good[cur | 1 << i]) {
                ord.push_back(i);
                cur |= 1 << i;
                break;
            }
    }
    for (int i = 0; i < N; i++)
        if (!(cur >> i & 1)) ord.push_back(i);
    for (int x : ord) cout << x << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> N;
    for (int i = 0; i < N; i++) cin >> L[i] >> S[i];
    if (N <= 5) 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.