Chia kẹo¶
Khái quát¶
Bài toán trình bày dưới đây là một ví dụ về quy hoạch động và có thể được xếp vào dạng bài toán tổng của tập con (subset sum problem).
Bên cạnh đó, bài toán này cũng thuộc dạng chỉ cần tìm ra một lời giải thoả mãn điều kiện, mà không cần phải chỉ ra tất cả lời giải. Lời giải được chỉ ra chưa chắc là tốt nhất.
Bài toán¶
Yêu cầu¶
Cho \(n\) gói kẹo (\(n \le 200\)), mỗi gói chứa không quá 200 viên kẹo, và một số \(m \le 40000\). Hãy chỉ ra một cách ra một số các gói kẹo để được tổng số kẹo là \(m\), hoặc thông báo rằng không thể thực hiện việc đó. 1
Input¶
Output¶
Giải thích¶
Input:
- Dòng thứ nhất chứa
n
gói kẹo vàm
viên kẹo cần đạt. - Dòng thứ hai là số kẹo trong mỗi gói. Số thứ tự của các gói được đánh từ 1.
Output:
- Số thứ tự của các gói kẹo được chọn để đạt được
m
viên kẹo.
Cụ thể, số viên kẹo của các gói 2, 3, 4, 5 và 6 là 9 + 5 + 6 + 2 + 10 = 32 viên kẹo.
Cách giải đề xuất¶
Đọc input¶
Gọi pack[i]
là số viên kẹo của gói thứ i
.
Xây dựng bảng quy hoạch¶
Gọi P[candy]
là số thứ tự của gói được chọn để đạt candy
viên kẹo và là nhỏ nhất khi thực hiện quá trình chọn từ gói P[1]
đến gói P[candy]
.
Khởi tạo giá trị vô cực cho các phần tử của mảng P
. Mục đích là sau khi duyệt xong mà P[m]
vẫn mang giá trị vô cực thì có nghĩa là không có lời giải.
Trong khi duyệt từng gói kẹo, gói kẹo thứ i
được chọn khi nó thoả các điều kiện sau:
-
Số viên kẹo của gói thứ
i
phải ít hơncandy
viên kẹo đang xét, tứcpack[i] <= candy
. -
Số thứ tự của gói kẹo được chọn trước đó phải nhỏ hơn
i
, cụ thể:P[candy - pack[i]] < i
. Trong đó:pack[i]
là số viên kẹo của gói thứi
.candy - pack[i]
là số viên kẹo cần đạt trước khi chọn góii
.P[candy - pack[i]] < i
là số thứ tự của gói kẹo nào đó trước khi chọn góii
.
Xuất output¶
Sau khi bảng quy hoạch P
đã điền đầy đủ, ta thực hiện truy ngược từ cuối bảng về đầu bảng:
- Nếu
P[m]
vẫn mang giá trị vô cực thì nghĩa là không có lời giải, xuất-1
. - Nếu
P[m]
mang giá trị cụ thể thì:P[m]
là số thứ tự của gói đầu tiên (tính từ cuối mảng) được chọn, ta nạpP[m]
vào mảngresult
.- Lặp các thao tác sau:
- Đặt
remaining_candy
là số kẹo còn lại sau khi trừ đi số kẹo của gói vừa chọn trước đó, ví dụ tại bước duyệt này làP[m]
:remaining_candy = remaing_candy - pack[số thứ tự của gói vừa chọn trước đó]
. - Nạp
P[remaining_candy]
là số thứ tự của gói tiếp theo được chọn vào mảngresult
. - Vòng lặp dừng khi không còn kẹo để xét nữa:
remaining_candy == 0
.
- Đặt
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
-
Lê Minh Hoàng, Giải thuật và lập trình. Hà Nội: Nhà xuất bản Đại học Sư phạm Hà Nội, 1999-2002. ↩