Ba lô¶
Khái quát¶
Trong chuyện cổ tích ăn khế trả vàng, con quạ chỉ cho phép mang theo túi ba gang. Người em nhờ học lập trình đàng hoàng nên lấy vàng vừa đủ, về được tới nhà. Còn người anh không chịu học Tin học, nên gom quá mức cho phép, dẫn đến kết cục "rơi tự do" dọc đường.
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 chọn các vật sao cho tổng giá trị là lớn nhất mà không vượt quá trọng lượng cho phép.
Bài toán¶
Yêu cầu¶
Có n
đồ vật, trong đó vật thứ i
có trọng lượng là weight[i]
và giá trị là value[i]
. Ta tìm cách chọn một số vật và đặt vào balo (1) sao cho tổng giá trị các vật được chọn là lớn nhất mà không được vượt quá sức chứa của balo.
- Xuất phát từ chữ ballot của tiếng Pháp.
Viết chương trình cho biết tổng giá trị lớn nhất của các vật được chọn, đồng thời liệt kê các vật theo số thứ tự tăng dần. Biết rằng vật đầu tiên có số thứ tự là 1
, vật cuối cùng có số thứ tự là n
.1
Input¶
Ouput¶
Giải thích¶
Input:
- Dòng thứ nhất: 6 là số đồ vật, đánh số thứ tự từ 1 đến 6. 10 là giới hạn trong lượng của balo.
- Từ dòng thứ hai trở đi: hai số lần lượt là trọng lượng và giá trị của vật.
Output:
- Dòng thứ nhất: tổng giá trị của các vật được chọn và là lớn nhất.
- Từ dòng thứ hai trở đi: ba số lần lượt là số thứ tự, trọng lượng và giá trị của vật được chọn.
Cụ thể, ta chọn vật 1 và vật 5. Tổng trọng lượng của chúng là 6 + 4 = 10, vừa đúng bằng trọng lượng balo. Tổng giá trị của chúng là 12 + 10 = 22, là lớn nhất trong số các cách lựa chọn.
Cách giải đề xuất¶
Xây dựng bảng quy hoạch¶
Bảng quy hoạch D
là mảng hai chiều gồm:
n + 1
hàng, tính từ0
đếnn
, chỉ số thứ tự của vật được chọn.weight_limit + 1
cột, vớiweight_limit
là giới hạn trọng lượng của balo.
Mỗi phần tử D[i][w]
lưu tổng giá trị lớn nhất khi chọn một số vật nào đó trong phạm vi [0..i]
mà tổng trọng lượng không vượt quá w
.
Như vậy, D[n][weight_limit]
là kết quả của bài toán.
Bước 1: Khởi tạo
- Hàng
0
: nghĩa là chọn0
vật, tức tổng giá trị là 0, nên ta gán0
cho tất cả phần tửD[0][w]
. - Cột
0
: nghĩa là giới hạn trọng lượng là 0, tức không thể chọn vật nào cả, nên ta cũng gán0
cho tất cả phần tửD[i][0]
.
- Dòng lệnh này vô tình khởi tạo giá trị
0
cho toàn bộ bảng quy hoạch.
Bước 2: Thực hiện quy hoạch động
Đối với các hàng tiếp theo, hàng dưới được tính dựa theo hàng trên. Cụ thể, với mỗi phần tử D[i][w]
ta xét hai trường hợp: chọn hay không chọn vật i
.
-
Nếu không chọn vật
i
thì tổng giá trị các vật được chọn không thay đổi, nghĩa làD[i][w] = D[i - 1][w]
. -
Nếu (muốn) chọn vật
i
thì ta xét xem tổng giá trị khi không có vậti
và tổng giá trị khi có thêm vậti
, tổng nào lớn hơn:D[i][w] = max(D[i][w], D[i - 1][w - weight[i]] + value[i])
. (1)-
D[i][w]
là tổng giá trị khi không có vậti
, đã tính trong trường hợp 1.w - weight[i]
là giới hạn trọng lượng còn lại khi đã chọn vậti
.D[i - 1][w - weight[i]]
là tổng giá trị lớn nhất khi chọn một số vật nào đó trong phạm vi[0..i-1]
và giới hạn trọng lượng làw - weight[i]
.D[i - 1][w - weight[i]] + value[i]
là tổng giá trị lớn nhất của một số vật nào đó trong phạm vi[0..i-1]
cộng thêm giá trị của vậti
.Tổng giá trị lớn nhất mới sẽ là giá trị lớn hơn trong hai trường hợp này.
-
Dựa vào ý tưởng trên, ta duyệt và điền giá trị cho bảng quy hoạch D
như sau:
Duyệt từng hàng (tức số thứ tự của vật) bằng biến i
trong phạm vi [1..n]
:
-
Duyệt từng cột (tức giới hạn trọng lượng) bằng biến w trong phạm vi
[1..weight_limit]
:-
Trường hợp 1: không chọn vật
i
, ta gán cho phần tửD[i][w]
đang xét giá trị phần tử trước đóD[i - 1][w]
. -
Trường hợp 2: chọn vật
i
, ta lấy giá trị lớn hơn giữa trường hợp 1 và trường hợp 2:D[i][w] = max(D[i][w], D[i - 1][w - weight[i]] + value[i])
.
-
Xuất output¶
Để xuất ra các vật được chọn, ta thực hiện truy vết dựa trên bảng quy hoạch D
dưới đây:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 12 | 12 | 12 | 12 | 12 |
2 | 0 | 0 | 0 | 1 | 1 | 1 | 12 | 12 | 12 | 13 | 13 |
3 | 0 | 0 | 0 | 8 | 8 | 8 | 12 | 12 | 12 | 20 | 20 |
4 | 0 | 0 | 0 | 8 | 8 | 8 | 12 | 12 | 12 | 20 | 20 |
5 | 0 | 0 | 0 | 8 | 10 | 10 | 12 | 18 | 18 | 20 | 22 |
6 | 0 | 0 | 0 | 8 | 10 | 10 | 12 | 18 | 18 | 20 | 22 |
Cụ thể, xuất phát từ phần tử cuối cùng D[n][weight_limit]
, duyệt từ hàng cuối về hàng đầu (tức duyệt theo vật), lặp thao tác:
-
Nếu ô dưới bằng ô ngay trên nó, tức
D[i][w] == D[i - 1][w]
, nghĩa là tổng giá trị không thay đổi, đồng nghĩa vậti
không được chọn, thì ta bỏ qua ô này. -
Ngược lại, nếu hai ô này khác nhau, thì đẩy vật
i
vào ngăn xếp, đồng thời trừ bớt trọng lượngweight[i]
để lùi cột. (Sở dĩ phải đẩy vào ngăn xếp vì yêu cầu bài toán là xuất các vật theo số thứ tự tăng dần)
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
-
Tài liệu lập trình của Võ Ngọc Hà Sơn vnhason@gmail.com. ↩