Hướng dẫn thuật toán bài VECTICKET

Trùm CUỐI 2025-12-15 4:15:07 2025-12-15 4:35:40

Link đề bài

1. Phân tích đề bài

  • Mục tiêu: Tìm thời gian sớm nhất để người thứ N mua xong vé.
  • Quy tắc phục vụ:
    • Phục vụ theo nhóm (đợt), mỗi nhóm tối đa C người.
    • Thời gian bắt đầu một đợt = \max(\text{thời gian kết thúc đợt trước}, \text{thời gian đến của người đầu tiên trong đợt}) .
    • Điều kiện quan trọng: Tất cả mọi người trong một nhóm phải có mặt tại ga trước hoặc đúng thời điểm nhóm bắt đầu được phục vụ.
  • Đặc điểm: Đây là bài toán tối ưu hóa việc chia dãy số thành các đoạn con, rất phù hợp với Quy hoạch động.

2. Ý tưởng thuật toán

Gọi F[i] là thời gian sớm nhất mà người thứ i hoàn thành việc mua vé. Để tính F[i] , ta xét xem người thứ i thuộc nhóm nào. Giả sử nhóm cuối cùng bao gồm các người từ j+1 đến i (với 1 \le i - j \le C ).

Khi đó:

  1. Thời điểm bắt đầu của nhóm này sẽ là: Start = \max(F[j], t_{j+1}) .
  2. Điều kiện hợp lệ: Vì người thứ i là người đến muộn nhất trong nhóm (giả sử danh sách đã sắp xếp theo t ), nên để nhóm này hợp lệ thì t_i \le Start .
  3. Thời gian phục vụ của nhóm là tổng d của các người từ j+1 đến i .
  4. Thời điểm kết thúc F[i] = Start + \text{Tổng thời gian phục vụ} .

Ta sẽ lấy giá trị nhỏ nhất có thể của F[i] khi thử tất cả các vị trí j hợp lệ.

3. Các bước thực hiện

  1. Sắp xếp: Sắp xếp danh sách người theo thời gian đến t_i tăng dần (nếu đề chưa cho sắp xếp).
  2. Mảng cộng dồn (Prefix Sum): Tạo mảng S để tính nhanh tổng thời gian phục vụ d .
    • S[i] = S[i-1] + d_i .
    • Tổng d từ j+1 đến i S[i] - S[j] .
  3. Quy hoạch động:
    • Khởi tạo mảng F với giá trị vô cùng lớn, F[0] = 0 .
    • Duyệt i từ 1 đến N .
    • Với mỗi i , duyệt ngược j từ i-1 về i-C (đảm bảo j \ge 0 ).
    • Tính Start = \max(F[j], t_{j+1}) .
    • Kiểm tra điều kiện: Nếu t_i \le Start , cập nhật:

      F[i] = \min(F[i], Start + (S[i] - S[j]))

  4. Kết quả: Giá trị F[N] .

4. Lưu ý khi cài đặt (C++/Python)

  • Kiểu dữ liệu: Thời gian có thể lên tới 10^9 cộng dồn qua nhiều người, nên bắt buộc dùng kiểu số nguyên 64-bit (long long trong C++, Python tự động xử lý).
  • Giới hạn vòng lặp: Vòng lặp trong (duyệt j ) chỉ chạy tối đa C lần. Độ phức tạp tổng thể là O(N \times C) . Với N=50.000 , thuật toán này chạy tốt nếu C nhỏ. Nếu C lớn xấp xỉ N , cần cẩn thận tối ưu hoặc ngôn ngữ chậm như Python có thể bị quá thời gian (TLE).
  • Xử lý chỉ số: Lưu ý mảng trong C++ bắt đầu từ 0 hoặc 1 tùy cách bạn cài đặt, hãy đồng bộ chỉ số giữa mảng t, d và mảng quy hoạch động F .

Đoạn mã minh họa ý tưởng (C++):

// Giả sử t[] và d[] đã nhập và sắp xếp theo t (1-based index)
// S[] là mảng cộng dồn của d[]
vector<long long> F(N + 1, 2e18); // Khởi tạo giá trị vô cùng lớn
F[0] = 0;

for (int i = 1; i <= N; i++) {
    for (int k = 1; k <= C; k++) { // k là số lượng người trong nhóm cuối
        int j = i - k;
        if (j < 0) break;
        
        long long startTime = max(F[j], t[j+1]);
        
        // Kiểm tra điều kiện: người cuối cùng (i) phải đến trước hoặc đúng lúc bắt đầu
        if (t[i] <= startTime) {
            long long duration = S[i] - S[j];
            F[i] = min(F[i], startTime + duration);
        }
    }
}
cout << F[N];

Thuật toán Tham lam: Không cần gộp, ai đến trước khám trước.

Độ phức tạp: O(N \log n) .