Chia phần thưởng¶
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ính số cách biểu diễn một số tự nhiên bằng tổng của các số tự nhiên theo thứ tự không tăng.
Bài toán này yêu cầu tính số cách chia số phần thưởng cho học sinh. Bài toán không nói về một phương án chia thưởng cụ thể, mà chỉ đề cập số phương án chia thưởng. Cụ thể, tính số cách chia phần thưởng để học sinh chưa giỏi nhận phần thưởng không nhiều hơn những người đã nỗ lực hơn mình.
Bài toán¶
Yêu cầu¶
Có bao nhiêu cách để chia r phần thưởng cho s học sinh, sao cho mỗi học sinh không nhận ít phần thưởng hơn người xếp hạng thấp hơn mình.1
Input¶
Ouput¶
Giải thích¶
Có 11 cách chia 7 phần thưởng cho 4 học sinh:
Cách giải đề xuất¶
Xây dựng bảng quy hoạch¶
Bảng quy hoạch là một mảng hai chiều D
gồm:
student + 1
hàng, vớistudent
là số lượng học sinh.reward + 1
cột, vớireward
là số lượng phần thưởng.
Mỗi phần tử D[s][r]
lưu số cách mà s
học sinh nhận r
phần thưởng.
Như vậy, phần tử cuối cùng D[student][reward]
sẽ là kết quả của bài toán.
Bước 1: Khởi tạo
Khởi tạo một số giá trị ban đầu cho bảng quy hoạch D
:
-
Hàng
0
: Các phần tửD[0, r]
đều bằng0
, nghĩa là không có cách nào để0
học sinh nhậnr
phần thưởng. -
Quy ước phần tử
D[0][0] = 1
, nghĩa là chỉ có1
cách để0
học sinh nhận0
phần thưởng.
- 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
Tiến hành duyệt bảng theo từng hàng (tương ứng với số học sinh). Trong mỗi hàng, lần lượt điền giá trị vào từng cột (tương ứng với số phần thưởng).
Có hai trường hợp:
Trường hợp 1: Số phần thưởng ít hơn số học sinh
Điều này đồng nghĩa không có đủ số phần thưởng cho tất cả học sinh, nên chỉ có r
học sinh được nhận r
phần thưởng. Nói cách khác, các học sinh xếp hạng từ r + 1
đến s
sẽ không được nhận thưởng.
Như vậy, số cách để s
học sinh nhận r
phần thưởng cũng bằng với số cách để r
học sinh nhận r
phần thưởng. Số cách của trường hợp 1 là D[r][r]
.
Trường hợp 2: Số phần thưởng bằng hoặc nhiều hơn số học sinh
Ta chia trường hợp này thành hai trường hợp con.
-
Trường hợp 2a: Học sinh xếp hạng chót không được nhận thưởng.
Như vậy, số cách để
s
học sinh nhậnr
phần thưởng cũng bằng với số cáchs - 1
học sinh (tức trừ đi học sinh hạng chót) nhậnr
phần thưởng. Số cách của trường hợp 2a làD[s - 1][r]
. -
Trường hợp 2b: Học sinh xếp hạng chót được nhận thưởng.
Điều này có nghĩa mọi học sinh đều nhận thưởng. Lúc này, ta thực hiện truy hồi bằng cách bỏ bớt 1 phần thưởng của mỗi học sinh ra.
Có thể thấy, số cách để
s
học sinh nhậnr
phần thưởng bằng với số cách đểs
học sinh nhậns - r
phần thưởng (tức tạm thời bỏ bớtr
phần thưởng củar
học sinh). Số cách của trường hợp 2b làD[s][r - s]
.
Dễ thấy, số cách của trường hợp 2 là: số cách của 2a + số cách của 2b.
Một cách giải khác¶
Ý tưởng cải tiến¶
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 |
3 | 1 | 1 | 2 | 3 | 4 | 5 | 7 | 8 |
4 | 1 | 1 | 2 | 3 | 5 | 6 | 9 | 11 |
Quan sát bảng quy hoạch D
trên, ta nhận thấy các giá trị ở phần đầu của mỗi hàng đều bằng với hàng ngay trên nó. Ví dụ, xét hàng 4, các giá trị từ cột 0 đến cột 3 đều bằng với các giá trị tương ứng ở hàng 3. Lý do là 3 phần thưởng chỉ có thể chia cho tối đa 3 học sinh. Nói cách khác, nếu chỉ có 3 phần thưởng, thì dù có 4 hay 5 hay 10 học sinh, thì số cách vẫn không thay đổi.
Do đó, ta chỉ cần lập mảng một chiều, tức bảng chỉ có một hàng, và tính từ phần tử thứ 4 (cột 4) trở đi.
Thực hiện quy hoạch động¶
Dựa theo ý tưởng trên:
- Đối với các phần tử trong phạm vi
[0..s - 1]
, ta giữ nguyên giá trị, không cần tính. - Đối với các phần tử trong phạm vi
[s..reward]
, vì chỉ cần duyệt cột nên ta sử dụng biểu thức như cũ và bỏ đi chỉ số hàng.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
-
Nguyễn Xuân Huy, Sáng tạo trong thuật toán và lập trình - Tập 1. Hà Nội: Nhà xuất bản Thông tin và Truyền thông, 2015. ↩