Dán tem
Đề bài
Cho một tập hợp gồm $n$ loại tem, mỗi loại có một mệnh giá khác nhau. Một phong bì có thể dán tối đa $k$ con tem. Nhiệm vụ là tìm số nguyên dương lớn nhất $m$ sao cho mọi giá trị từ $1$ đến $m$ đều có thể tạo thành bằng cách dán không quá $k$ con tem từ tập mệnh giá đã cho.
Dữ liệu
- Dòng đầu gồm hai số nguyên $k$ và $n$ $(1 \le k \le 200;\ 1 \le n \le 50)$: số tem tối đa được dán trên mỗi phong bì và số loại tem.
- Dòng tiếp theo chứa $n$ số nguyên $a_i$ $(1 \le a_i \le 10^4)$, mỗi số là mệnh giá của một loại tem.
Kết quả
- Một dòng duy nhất là số nguyên $m$ lớn nhất sao cho từ $1$ đến $m$ đều có thể biểu diễn bằng không quá $k$ con tem. Nếu không có giá trị nào thỏa mãn thì in
0.
5 2
1 313Giải thích ví dụ: có 2 loại tem mệnh giá $1$ và $3$, dán tối đa $k = 5$ con. Các giá trị $1, 2, \ldots, 13$ đều dán được, ví dụ $13 = 3 + 3 + 3 + 3 + 1$ (5 con). Nhưng $14$ cần ít nhất $3 + 3 + 3 + 3 + 1 + 1$ = 6 con tem > 5 → không dán được, nên $m = 13$. Mỗi loại tem được dùng bao nhiêu con cũng được.
Lưu ý: dòng đầu là $k$ trước, $n$ sau – rất dễ đọc nhầm thứ tự.
Lời mở đầu
Đây là một biến thể của bài đổi tiền (coin change) kinh điển: thay vì hỏi "đổi số tiền $s$ cần ít nhất mấy đồng xu", ta hỏi lần lượt cho $s = 1, 2, 3, \ldots$ và dừng ở tổng đầu tiên cần nhiều hơn $k$ con tem. Mình sẽ bắt đầu bằng cách quay lui liệt kê mọi cách dán (đủ cho các test nhỏ), rồi chuyển sang quy hoạch động $f[s]$ = số tem ít nhất để dán đúng tổng $s$.
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJLộ trình lời giải, bấm vào từng phần để nhảy tới:
Bộ testBộ test chính thức có gì?
| Test | $k$ | $n$ | $a_i$ lớn nhất | Đáp án |
|---|---|---|---|---|
| 1–30 | 1–10 | 1–5 | 10 | 0 … 88 |
| 31–80 | 100–196 | 20–50 | 9.988 | 702.823 … 1.884.898 |
| 81–100 | 200–200 | 50–50 | 10.000 | 1.977.136 … 1.992.319 |
Đề không ghi subtask nhưng bộ test chia rõ 3 mức, mình tự đặt tên như bảng dưới. Mọi test đều có các mệnh giá khác nhau đôi một. Có 1 test (test 6) không có tem mệnh giá 1 → không dán được giá trị 1, đáp án là $0$. Đáp án lớn nhất gần $2 \cdot 10^6$ – đúng cỡ $k \cdot \max a_i = 200 \cdot 10^4$.
| Mức | Điều kiện | Test | Cách 1 (quay lui) | Cách 2 (QHĐ) |
|---|---|---|---|---|
| 1 | $k \le 10,\ n \le 5,\ a_i \le 10$ | 1–30 | qua | qua |
| 2 | $k \le 200,\ n \le 50$ | 31–80 | quá thời gian | qua |
| 3 | $k = 200,\ n = 50,\ \max a_i = 10^4$ | 81–100 | quá thời gian | qua |
Cách 1Quay lui liệt kê mọi cách dán (mức 1)
Một cách dán là một tổ hợp lặp các loại tem: thứ tự dán không quan trọng, $1 + 3$ cũng như $3 + 1$. Để không đếm trùng, mình bắt các con tem được chọn theo thứ tự loại không giảm: hàm Try(i, cnt, sum) chỉ được chọn con tem tiếp theo có loại $j \ge i$. Mỗi trạng thái đi qua (kể cả khi đang dán dở) là một tổng dán được, đánh dấu ok[sum] = true. Cuối cùng đếm từ $1$ lên cho tới tổng đầu tiên chưa được đánh dấu.
ok[0]cũng được đánh dấu (không dán con nào) nhưng vòngwhilebắt đầu kiểm tra từ $1$ nên không ảnh hưởng.- Số lời gọi bằng số tổ hợp lặp chọn $\le k$ con từ $n$ loại, tức $\binom{n + k}{k}$. Với $k = 10,\ n = 5$ chỉ khoảng $3\,000$; nhưng với $k = 200,\ n = 50$ thì là một số khổng lồ cỡ $10^{55}$.
Kết quả chạy thật: đúng 30/100 test – trọn mức 1, mỗi test chạy tức thì. Toàn bộ 70 test từ test 31 trở đi đều quá thời gian: với $k \ge 100$ và $n \ge 20$ số tổ hợp lặp là con số thiên văn. Bản Python cũng qua 30 test đầu (chậm nhất 0,02 giây).
Ý tưởng QHĐCon tem cuối cùng là con nào?
Gọi $f[s]$ là số con tem ít nhất để dán được đúng tổng $s$. Một cách dán tổng $s$ (với $s > 0$) chắc chắn có một con tem "cuối cùng" mệnh giá $a_i \le s$; bỏ con tem đó ra thì phần còn lại là một cách dán tổng $s - a_i$. Vậy:
Ta tính $f[1], f[2], \ldots$ theo thứ tự tăng dần và dừng ngay ở $s$ đầu tiên có $f[s] > k$; đáp án là $s - 1$. Vòng lặp chắc chắn dừng vì $k$ con tem chỉ dán được tổng tối đa $k \cdot \max a_i \le 2 \cdot 10^6$ – đó cũng là kích thước mảng cần dùng. Nếu không có tem mệnh giá 1 thì $f[1] = \infty > k$ và dừng ngay ở $s = 1$, in ra $0$ đúng như đề yêu cầu.
Em nhập $k$ và các mệnh giá rồi bấm Tiếp để xem mảng $f$ được điền dần. Ô vàng là ô đang tính, các ô xanh tím là $f[s - a_i]$ ứng với từng con tem cuối:
Cách 2Quy hoạch động $f[s]$ (full điểm)
f[s] = INT_MAXnghĩa là "chưa dán được". Phépf[s - a[i]] + 1không bao giờ tràn số: mọi ô trước $s$ đều đã $\le k$ (nếu không thì vòng lặp đã dừng từ trước).- Mảng
fkhai báo toàn cục cỡ $2 \cdot 10^6 + 5$ (8 MB) – đủ cho tổng lớn nhất $k \cdot \max a_i + 1$. - Khi thoát vòng lặp, $s$ là tổng đầu tiên không dán được, nên in $s - 1$.
Kết quả chạy thật: đúng 100/100 test, test chậm nhất ($k = 200$, $n = 50$, đáp án gần $2 \cdot 10^6$) mất 0,13 giây. Bản Python cùng ý tưởng đúng 30 test đầu nhưng với test lớn phải chạy khoảng $10^8$ bước, mất khoảng 2,4–2,7 giây (mình thử test 31–33 và 81–83) → quá thời gian 1 giây; các bạn dùng Python thì chỉ ăn được mức 1 ở bài này.
- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét