Hướng dẫn giải của Xếp lịch hội trường
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.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , , ,
Hiểu bài toán
Cho N yêu cầu sử dụng hội trường, mỗi yêu cầu có thời gian bắt đầu (bi) và kết thúc (ei). Tại một thời điểm chỉ có thể phục vụ một yêu cầu. Mục tiêu là chọn một danh sách các yêu cầu không chồng lấn sao cho tổng thời gian sử dụng hội trường (tổng hiệu (ei - bi) của các yêu cầu được chọn) là lớn nhất.
Đây là bài toán tối ưu hóa trên các khoảng thời gian. Các yêu cầu có thể chồng lấn, nên ta phải chọn lọc.
Các cách tiếp cận
Cách Quy hoạch động cơ bản (O(n^2))
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<pair<int, int>> intervals(n);
for (int i = 0; i < n; i++) {
cin >> intervals[i].first >> intervals[i].second;
}
// Sắp xếp theo thời gian kết thúc
sort(intervals.begin(), intervals.end(), [](pair<int, int> a, pair<int, int> b) {
if (a.second != b.second) return a.second < b.second;
return a.first > b.first;
});
vector<int> dp(n);
for (int i = 0; i < n; i++) {
dp[i] = intervals[i].second - intervals[i].first;
}
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (intervals[i].first >= intervals[j].second) {
dp[i] = max(dp[i], dp[j] + intervals[i].second - intervals[i].first);
}
}
ans = max(ans, dp[i]);
}
cout << ans << endl;
return 0;
}
- Time Complexity: O(n^2)
- Space Complexity: O(n)
Đầu tiên, ta sắp xếp các khoảng thời gian theo thời gian kết thúc tăng dần. Sau đó, sử dụng mảng dp où dp[i] là tổng thời gian lớn nhất có thể đạt được khi xét đến yêu cầu thứ i và yêu cầu i được chọn.
Công thức truy hồi: dp[i] = max(dp[i], dp[j] + duration_i) với mọi j < i sao cho start_i >= end_j.
Cách này đúng nhưng chậm do duyệt qua tất cả các cặp j < i.
Cách Quy hoạch động kết hợp Tìm kiếm nhị phân (O(n log n))
#include <bits/stdc++.h>
#define int long long
using namespace std;
struct Vip {
int start, end;
};
bool compare(Vip a, Vip b) {
return a.end < b.end;
}
int n;
Vip a[10005];
int Dp[10005];
// Tìm chỉ số lớn nhất j sao cho a[j].end <= x
int TKNP(int x) {
int l = 0, r = n + 1;
while (r - l > 1) {
int mid = (l + r) / 2;
if (a[mid].end <= x) l = mid;
else r = mid;
}
return l;
}
main() {
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].start >> a[i].end;
}
sort(a + 1, a + n + 1, compare);
for (int i = 1; i <= n; i++) {
int time = a[i].end - a[i].start;
int j = TKNP(a[i].start); // Tìm index thỏa mãn
Dp[i] = max(Dp[i - 1], Dp[j] + time);
}
cout << Dp[n];
return 0;
}
- Time Complexity: O(n log n)
- Space Complexity: O(n)
Vẫn sắp xếp theo thời gian kết thúc. Thay vì duyệt qua tất cả các j trước i, ta chỉ cần tìm một j tốt nhất.
Vì ta đã sắp xếp theo end, nên các dp[j] sẽ được tối ưu hóa theo thứ tự. Ta cần tìm j lớn nhất sao cho a[j].end <= a[i].start. Sử dụng tìm kiếm nhị phân (binary search) để tìm j này trong O(log n).
Công thức: Dp[i] = max(Dp[i-1], Dp[j] + duration_i). Dp[i-1] được dùng để đảm bảo tính liên tục của dãy con tối ưu (trường hợp không chọn interval i).
Cách Sử dụng Heap (Priority Queue) để tối ưu (O(n log n))
// Code tham khảo logic từ Solution 2
#include <bits/stdc++.h>
using namespace std;
struct Interval {
int start, end, duration;
};
bool compareEnd(const Interval& a, const Interval& b) {
return a.end < b.end;
}
int main() {
int n;
cin >> n;
vector<Interval> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i].start >> a[i].end;
a[i].duration = a[i].end - a[i].start;
}
// Sắp xếp theo thời gian kết thúc
sort(a.begin(), a.end(), compareEnd);
// Priority queue lưu các khoảng thời gian có thể ghép nối trước đó
// Lưu {duration, end_time}
priority_queue<pair<int, int>> pq;
int max_time = 0;
for (int i = 0; i < n; i++) {
// Loại bỏ các khoảng đã qua mà không thể nối với interval hiện tại
while (!pq.empty() && pq.top().second > a[i].start) {
pq.pop();
}
int current_best = a[i].duration;
if (!pq.empty()) {
current_best = max(current_best, pq.top().first + a[i].duration);
}
// Cập nhật kết quả
max_time = max(max_time, current_best);
// Đẩy vào hàng đợi
pq.push({current_best, a[i].end});
}
cout << max_time << endl;
return 0;
}
- Time Complexity: O(n log n)
- Space Complexity: O(n)
Cách tiếp cận này sử dụng Priority Queue (Max Heap) để lưu trữ các tổng thời gian lớn nhất có thể của các chuỗi các yêu cầu đã xét.
- Sắp xếp các khoảng theo thời gian kết thúc.
- Duyệt qua từng khoảng. Ta cần tìm tổng thời gian lớn nhất của các chuỗi các khoảng kết thúc trước thời điểm bắt đầu của khoảng hiện tại (
start_i). - Sử dụng PQ để lưu trữ các chuỗi đó. Khi xét interval mới, ta loại bỏ các chuỗi trong PQ mà interval hiện tại không thể nối tiếp (do end > start_i).
- Giá trị mới tại interval này là
duration_ihoặcduration_i + max_prev_duration(lấy từ PQ). - Đẩy giá trị mới vào PQ.
Cách này linh hoạt và dễ hiểu nếu đã quen với cấu trúc dữ liệu PQ.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(n^2) | O(n) | Quy hoạch động cơ bản (O(n^2)) |
| 2 | O(n log n) | O(n) | Quy hoạch động kết hợp Tìm kiếm nhị phân (O(n log n)) |
| 3 | O(n log n) | O(n) | Sử dụng Heap (Priority Queue) để tối ưu (O(n log n)) |
Bài học kinh nghiệm
- Bài toán có thể coi là biến thể của 'Weighted Interval Scheduling' (Lên lịch khoảng thời gian có trọng số), trong đó trọng số là độ dài của khoảng thời gian.
- Phải sắp xếp các khoảng theo thời gian kết thúc trước khi thực hiện quy hoạch động.
- Việc tìm kiếm khoảng thời gian ngay trước (previous compatible interval) có thể tối ưu từ O(n) xuống O(log n) bằng Binary Search hoặc Priority Queue.
Lỗi thường gặp
- Quên sắp xếp input theo thời gian kết thúc, dẫn đến kết quả tính toán sai.
- Xử lý sai trường hợp hai khoảng có cùng thời điểm kết thúc nhưng bắt đầu khác nhau (nên ưu tiên khoảng có độ dài lớn hơn hoặc bắt đầu muộn hơn nếu cùng kết thúc).
- Lỗi index mảng trong khi implement Binary Search (Off-by-one error).
Bình luận