Professional Documents
Culture Documents
Bài tập tham khảo Quy hoạch động
Bài tập tham khảo Quy hoạch động
Bài tập tham khảo Quy hoạch động
Contents
1 Đề 1
2 Giải 2
2.1 Câu a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.1.1 Mô phỏng ý tưởng ban đầu . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.1.2 Thuật toán cho trường hợp có vũng nước . . . . . . . . . . . . . . . . . . 3
2.2 Câu b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Code 6
1 Đề
Tìm thuật toán để giải quyết bài toán sau đây, gọi là Bài toán đường đi tránh vũng nước:
Xét góc phần tư thứ 1 của hệ trục tọa độ Oxy với các lưới nguyên (giống như giấy tập có ô),
có một vũng nước S (là một tập hợp các điểm nguyên nằm trong góc phần tư thứ 1).
a. Tìm số đường đi từ O đến A tránh vũng nước S biết mỗi lần đi, chỉ được lên trên 1 đơn vị
hoặc qua phải 1 đơn vị.
b. Có nhận xét gì về số đường đi như vậy nếu vũng nước S không tồn tại?
Trang 1
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
2 Giải
Đây là phần “suy luận” ra công thức truy hồi. Nếu các bạn không muốn đọc có thể nhảy xuống
phần 3 (Code) ở dưới. Lưu ý là trong phần suy luận này mình trình bày theo kiểu bottom-up.
2.1 Câu a
2.1.1 Mô phỏng ý tưởng ban đầu
Em xin mô hình hoá bài toán đã cho thành một bài toán quy hoạch động. Để dễ mô hình hoá,
em xin biểu diễn lại hệ trục toạ độ đã cho bằng một ma trận có kích thước (n + 1) × (m + 1)
với m, n là toạ độ của điểm A(m, n) đã cho trong đề bài. Khi đó bài toán có thể phát biểu lại
như sau:
Cho một lưới hình chữ nhật có kích thước (n + 1) × (m + 1), có bao nhiêu cách để đi từ điểm có
toạ độ (0, 0) đến điểm có toạ độ (n, m) mà chỉ đi lên trên hoặc đi qua phải, mỗi lần đi 1 đơn
vị?
Giả sử ta quy ước ô nằm ở góc dưới cùng bên trái là (0, 0) và trên cùng bên phải là (n, m).
Ta gọi cách để đi từ ô có toạ độ (0, 0) (ô bắt đầu) để đi đến
một ô có toạ độ (i, j) là a(i, j) với i, j lần lượt là chỉ số hàng
(n,m) và chỉ số cột của ô đó.
Vì từ ô bắt đầu ta chỉ có thể đi sang trái hoặc sang phải, nên
ta nhận thấy rằng để đi từ ô bắt đầu tới ô có toạ độ (i, j)
n+1 bất kỳ thì ta chỉ có 2 cách đi: tới từ ô ở ngay dưới bên dưới
(i − 1, j) hoặc từ ô ở ngay bên phải (i, j − 1).
Do đó, cách để đi đến một ô có vị trí (i, j) bất kỳ là:
(0,0)
a(i, j) = a(i − 1, j) + a(i, j − 1)
m+1
Ta nhận thấy rằng để đi từ ô có vị trí (0, 0) đến ô có vị trí (0, 0) thì ta có duy nhất 1 cách đi
là không làm gì cả, nên a(0, 0) = 1.
Để dễ tính toán, ta quy ước không có cách đi nào đến những ô có toạ độ âm, nên a(i, j) = 0
nếu i, j < 0.
Như vậy, ta có thể rút ra thuật toán cho bài toán này là (hiện tại ta chưa xét các “vũng nước”):
• Bắt đầu tại ô (0, 0). Lần lượt đi từ trái qua phải, từ dưới lên trên và cập nhật giá trị cho
các ô đã đi qua theo công thức:
1
, (i, j) = (0, 0)
a(i, j) = 0 ,i < 0 ∨ j < 0
a(i, j − 1) + a(i − 1, j) , các TH còn lại
• Lặp lại bước trên và thoát khỏi thuật toán sau khi gán xong giá trị cho ô (n, m).
• Kết quả của bài toán (số đường đi từ O tới A) là giá trị tại ô (n, m).
Trang 2
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
1 5 15 35
1 4 10 20
1 3 6 10
1 2 3 4
1 1 1 1
⇒ Có 35 cách đi từ O tới A(3, 4). Ta có thể kiểm chứng bằng công thức chứng minh được ở
3
câu b: C3+4 = 35.
Gọi S là tập hợp những điểm trên hệ trục toạ độ bị “chìm” trong vũng nước.
Ta dễ dàng nhận thấy ở những ô chìm trong vũng nước thì không có cách nào để đi đến đó,
nên a(i, j) = 0 nếu (i, j) ∈ S.
Thuật toán cho trường hợp này tương tự trường hợp trên, chỉ có điều lần này ta sẽ xét thêm
các ô đó có nằm trong vũng nước hay không. Cụ thể như sau:
• Bắt đầu tại ô (0, 0). Lần lượt đi từ trái qua phải, từ dưới lên trên và cập nhật giá trị cho
các ô đã đi qua theo công thức:
1
, (i, j) = (0, 0)
a(i, j) = 0 , i < 0 ∨ j < 0 ∨ (i, j) ∈ S
a(i, j − 1) + a(i − 1, j) , các TH còn lại
• Lặp lại bước trên và thoát khỏi thuật toán sau khi gán xong giá trị cho ô (n, m).
• Kết quả của bài toán là giá trị tại ô (n, m).
Trang 3
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
1 7 13 19 34 82
1 6 6 6 15 48
1 5 0 0 9 33
1 4 0 0 9 24
1 3 0 4 9 15
1 2 3 4 5 6
1 1 1 1 1 1
Trang 4
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
2.2 Câu b
Nếu không tồn tại vũng nước, ta nhận thấy rằng để đi
từ gốc toạ độ O đến A(m, n) luôn luôn tốn m + n bước.
Ví dụ như xét điểm A(5, 6) như hình bên, ta luôn luôn
tốn 5 + 6 = 11 bước để đi từ O đến A.
Nếu ta ký hiệu một bước đi lên là ký tự U và một bước
sang phải là ký tự R, một đường đi sẽ tương ứng với
một chuỗi các kí tự U, R. Ta nhận thấy nếu ta đổi chỗ
các kí tự U hoặc các kí tự R cho nhau thì chuỗi trên
vẫn không thay đổi, nên bài toán sẽ trở thành: có bao
nhiêu cách chọn m + n kí tự từ m kí tự R và n kí tự U?
Lưu ý về ký hiệu:
n
= Cnk
k
Trang 5
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
3 Code
Các bạn có thể copy nội dung của các đoạn code trong phần này trong thư mục answer-sources
ở link sau: Github: hungngocphat01/nes-ktlt-2021.
1 #include <iostream>
2 using namespace std;
3
31 // Hàm kiểm tra xem (i, j) có nằm trong vũng nước không
32 // S: vũng nước (danh sách các tọa độ); z: kích thước mảng S
33 bool inPuddle(int i, int j, Point2D S[], int z) {
34 for (int k = 0; k < z; k++) {
35 if (S[k].x == i && S[k].y == j) {
36 return true;
37 }
38 }
39 return false;
40 }
41
42 // Hàm đếm số bước đi từ gốc tọa độ (0, 0) đến điểm (m, n) có vũng nước là S
43 // Hàm này chưa sử dụng quy hoạch động
Trang 6
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
57 // Hàm tương tự như trên nhưng sử dụng quy hoạch động theo top-down
58 // answer: bảng tra lời giải
59 int QHD_TopDown(int m, int n, Point2D S[], int z, int **answer) {
60 // Kiểm tra xem lời giải đã có trong bảng chưa. Nếu có trả về luôn
61 // Ta phải có đk m*n > 0 để tránh trường hợp bị chỉ số âm
62 if (m * n > 0 && answer[m][n] != -1) {
63 return answer[m][n];
64 }
65
66 // Nếu chưa có lời giải, giair lại y hệt hàm đệ quy ở trên
67 if (m == 0 && n == 0) {
68 return 1;
69 }
70 else if (m < 0 || n < 0 || inPuddle(m, n, S, z)) {
71 return 0;
72 }
73 int result = QHD_TopDown(m - 1, n, S, z, answer) + QHD_TopDown(m, n - 1,
,→ S, z, answer);
74 // Lưu lại giá trị trên vào bangr tra
75 answer[m][n] = result;
76 return result;
77 }
78
Trang 7
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
90
91 Phải làm như thế này để tránh trường hợp i < 0 hay j < 0 do khi
,→ ta sử dụng mảng 2 chiều thì chỉ số ko thể âm. **/
92 else if ((i == 0 && j != 0) || (i != 0 && j == 0)) {
93 answer[i][j] = 1;
94 }
95 // Các trg hợp còn lại
96 else {
97 answer[i][j] = answer[i - 1][j] + answer[i][j - 1];
98 }
99 }
100 }
101
136 return 0;
Trang 8
Tài liệu ôn thi Kỹ thuật Lập trình CLB Học thuật NES
137 }
Sau khi chạy, các bạn sẽ thu được kết quả như sau:
Trong đó 2 cái đầu tiên có quy hoạch động chạy rất nhanh (giải ra ngay lập tức). Cái cuối
cùng giải rất lâu nên mình không chụp được kết quả. Trong một số trường hợp có thể không
giải ra được.
Như các bạn thấy thì việc code 1 bài như trên đòi hỏi phải sử dụng cấp phát động và định
nghĩa thêm kiểu struct khá phiền phức. Đó là lý do tại sao bạn nên sử dụng Python (hay
các ngôn ngữ bậc cao khác) để học thuật toán. Các bạn có thể đọc code của bài đó giải bằng
Python ở link: QHD_ThamKhao.py @ hungngocphat01/LaTeX-NES-KTLT-2021.
Trang 9