Hướng dẫn giải của [Free Contest 05] HORROR
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