Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
CHUYÊN ĐỀ NÂNG CAO 6
CHUYÊN ĐỀ 6: QUY HOẠCH ĐỘNG
Trong ngành khoa học máy tính, quy hoạch động là một phương pháp giảm thời gian chạy của
các thuật toán thể hiện các tính chất của các bài toán con gối nhau (overlapping subproblem) và cấu trúc
con tối ưu (optimal substructure).
Nhà toán học Richard Bellman đã phát minh phương pháp quy hoạch động vào năm 1953.
Ngành này đã được thành lập như là một chủ đề về kỹ nghệ và phân tích hệ thống đã được tổ chức IEEE
thừa nhận.
AA. MỘT SỐ BÀI TOÁN BẮT ĐẦU
1. Bài toán Fibonaci
Dãy Fibonaci được định nghĩa như sau:
F1 = F2 = 1
Fn = Fn −1 + Fn−2 (n ≥ 3)
Em hãy tìm số Fibonaci thứ n
* Ý tưởng đầu tiên – Thuật toán 1: Đệ quy
Nhìn vào công thức trên ta thấy ngay bản chất đệ quy, việc tính Fn ta có thể viết ngay rất dễ dàng
và ngắn gọn:
Function Fb(n:longint):int64;
Begin
If (n<=2) then exit(1) else fb:=fb(n-1) + fb(n-2);
End;
Ta sẽ nhận xét về cách làm này thông qua ví dụ sau:
Khi ta gọi: x:=Fb(10) thì việc tính fb(10) này sẽ tính như sau:
X:= fb(9) + fb(8)
X:= fb(7) + fb(8) + fb(6) + fb(7)
X:= fb(5) + fb(6) + fb(6) + fb(7) + fb(4) + fb(5) + fb(5) + fb(6)
…..
Ta thấy ngay bước thứ 3: có rất nhiều lời gọi hàm trùng lặp nhau: f6 gọi 3 lần, f5 gọi 3 lần … chính điều
này làm cho thời gian thực thi thuật toán này rất lớn. Chính vì vậy thuật toán đệ quy này chỉ chạy được
với dữ liệu nhỏ. Với n>=44 thuật toán trên không khả thi.
* Ý tưởng cải tiến – Thuật toán 2: Quy hoạch động
Để thuật toán của ta chạy nhanh hơn ta thấy cần cải tiến làm sao để mỗi fb(x) chỉ cần tính một
lần.
Để làm điều này ta gọi F[i] là số fibonaci thứ i. (f là mảng một chiều)
Khi đó ta thấy việc tính mảng này rất nhanh chóng:
Function Fibo(n:longint):int64;
Var i:longint;
Begin
f[1]:=1; f[2]:=1;
For i:=1 to n do f[i] : = f[i-1] + f[i-2];
Exit(f[n]);
End;
Trong thuật toán trên: Việc tính các số fibonaci được lưu lại, mỗi số tính 1 lần, tính từ nhỏ đến
lớn, Số fibo sau được tính thông qua các số fibo nhỏ hơn. đó cũng là một phần của tư tưởng quy
hoạch động.
Giáo viên: Lê Thanh Phú
1|Tra n g
Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
(chú thích: có các thuật toán khác tốt hơn nữa để tính số fibonaci thứ n, tuy nhiên để làm quen
với tư tưởng QHĐ thì ta tạm dừng ở thuật toán này)
2. Bài toán sắp bò: Nguồn: NTU – Tên bài: Sabo
Anh nông dân Bo có một đàn bò gồm rất nhiều con cái và con đực. Trong một hội chợ, anh muốn
sắp một hàng bò gồm n con. Tuy nhiên những con bò đực rất hung hăng nếu đứng gần nhau, anh phải
sắp tối thiểu k con bò cái xen giữa hai con bò đực để chúng khỏi húc nhau.
Bạn hãy giúp anh Bo đếm thử xem có bao nhiêu cách để sắp một hàng gồm n con bò mà hai con
bò đực bất kỳ không húc nhau (anh Bo có rất nhiều bò nên không sợ thiếu bò cái hoặc bò đực).
Ví dụ với n = 4 và k= 1, ta có 8 cách xếp như sau (M: bò đực, F: bò cái):
FFFF, MFFF, FMFF, FFMF, FFFM, MFMF, MFFM, FMFM.
Dữ liệu nhập:
- Gồm hai số nguyên n và k cách nhau một khoảng trắng ( 1 ≤ n, k ≤ 1.000)
Dữ liệu xuất:
- Số cách xếp hàng thỏa mãn yêu cầu. Do số lượng này có thể rất lớn nên chỉ cần in ra tối đa 6 chữ số
cuối cùng (modulo 1.000.000)
Ví dụ
Sapbo.inp
Sapbo.out
Sapbo.in Sapbo.out
p
41
8
52
9
* ý tưởng đầu tiên: đệ quy quay lui
Ta nhận thấy bài này có thể quy về: Có bao nhiêu dãy nhị phân có độ dài n sao cho các kí tự 1
cách nhau k kí tự 0.
Ta sẽ duyệt quay lui tất cả cách sắp bò (sinh ra tất cả các dãy nhị phân có độ dài n thỏa mãn điều
kiện) và ta đếm.
Nhận xét: ở đây n<=1000 vậy cách liệt kê dãy nhị phân này không thể chạy được trong thời gian
cho phép.
* ý tưởng 2: Quy hoạch động
Ta gọi B[i] là số cách sắp i con bò thỏa mãn điều kiện của bài toán. Kết quả bài toán là B[n]
Vấn đề đặt ra ở bài này là ta chưa có công thức để tính b[i]. (Không giống bài 1: đã có công thức
tính F[i] = f[i-1] + f[i-2]).
Vậy để tính được mảng B này ta cần phải suy luận để tìm ra công thức. Trong những trường hợp
như vậy, cách làm chính là lấy giấy, bút, liệt kê thử xem với một số giá trị cụ thể:
Ví dụ: Với n=8 k=2 ta sẽ liệt kê thử số cách sắp:
Số cách sắp 1 con B[1] = 2
Số cách sắp 2 con B[2] = 3
Số cách sắp 3 con B[3] = 4
Số cách sắp 4 con B[4] = 6
Số cách sắp 5 con B[5] = 9
Số cách sắp 6 con B[6] = 13
Số cách sắp 7 con B[7] = 19
Số cách sắp 8 con B[8] = 28
…
0 1
00 01 10 (11 không được vì 2 bò đực sát nhau)
000 001 010 100
0000 0001 0010 0100 1000 1001
00000 00001 00010 00100 01000 01001 10000 10001 10010
000000 000001 000010 000100 …
0000000 0000001 0000010 …
00000000 00000010 00000100 …
Chắc chắn b[i] sẽ được tính thông qua các b[..] nhỏ hơn nên ta thử:
B[1] = 1 + 1
B[2] = 2 + 1
B[3] = 3 + 1
Giáo viên: Lê Thanh Phú
2|Tra n g
Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
B[4] = b[3] + b[1]
( 6 = 4 + 2)
B[5] = b[4] + b[2]
( 9 = 6 + 3)
B[6] = b[5] + b[3]
…
B[i] = b[i-1] + b[i-k-1] Đây chính là thứ ta cần tìm
Sau khi thử như trên ta có thể thấy được quy luật như sau:
•
•
Với i=1 đến k+1:
Với i=k+2 đến n:
b[i] = i+1
b[i] = b[i-1]
+
b[i-k-1]
Công thức ta vừa tìm được ở trên chính công thức quy hoạch. Chương trình áp dụng công thức trên như
sau:
Const
tepvao='Sapbo.inp'; tepra='Sapbo.out';
modu=1000000;
Var
i,n,k:longint;
a,f:array[0..10001] of int64;
BEGIN
Assign(input,tepvao); reset(input);
Assign(output,tepra); rewrite(output);
Readln(n,k);
//QHD f[i]: so cach sap hang i con bo
f[1]:=2;
{F[1] duoc goi la co so QHD}
for i:=2 to k+1 do f[i]:=(i+1);
for i:=k+2 to n do f[i]:=(f[i-1] + f[i-k-1]) mod modu;
//Ghi ket qua
write(f[n]);
Close(input); Close(output);
END.
3. Bài toán bàn cờ:
Xét một bàn cờ hình vuông kích thước nxn với mỗi ô c[i,j] là số lượng hạt đậu đang nằm ở ô [i,j]. Một
quân cờ xuất phát từ một ô bất kỳ của hàng 1, ta cần tìm một đường đi đến hàng cuối cùng n sao cho số
lượng hạt đậu quân cờ nhặt được trên đường đi là lớn nhất.
Quân cờ chỉ có thể đi thẳng xuống, chéo xuống sang trái hoặc chéo xuống sang phải. quân cờ đi
qua ô nào thì có thể nhặt được số lượng các hạt đậu trên ô này. (n<=1000)
Ví dụ: với n=3, đáp án: 17
4
5
2
5
2
3
1
7
4
* ý tưởng đầu tiên: Đệ quy quay lui
Ta sẽ đệ quy quay lui tương tự bài Ốc sên ăn rau: Tìm tất cả đường đi từ hàng 1 đến hàng n:
Đường đi nào có tổng lớn nhất thì ta ghi nhận.
Ta thấy phương án đầu tiên này không khả thi khi n lớn (n<=1000).
* ý tưởng 2: Quy hoạch động
B[i,j] là số lượng hạt đậu nhặt được nhiều nhất khi đến được ô [i,j]
Ta có thể dễ dàng tìm được công thức để tính mảng 2 chiều B này như sau: Tại ô [i,j] ta nhận thấy
có 3 ô trước đó mà từ ô đó quân cờ có thể đến được ô [i,j] này:
Ví dụ: tại ô có giá trị 7: Có 3 ô ở hàng trên có thể đến được: chéo xuống sang trái là ô [i-1,j+1] = 3,
chéo xuống sang phải [i-1,j-1] = 5, chéo xuống thẳng [i-1,j] = 2.
Vậy thì ta thấy b[i,j] = max(của 3 ô này) + số lượng hạt tại ô [i,j]
Giáo viên: Lê Thanh Phú
3|Tra n g
Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
Hay B[i,j] = max (b[i-1,j-1], b[i-1,j], b[i-1,j+1]) + C[i,j]
4
5
2
5
2
3
1
7
4
Ta nhận thấy hàng đầu tiên b[1,j] = c[1,j] còn ta sẽ tính lần lượt từ hàng 2 trở đi xuống đến hàng
n. mà đáp án sẽ là số lớn nhất trên hàng n
For j:=1 to n do b[1,j]:=c[1,j];
For i:=2 to n do
For j:=1 to n do B[i,j]:= max(b[i-1,j-1], b[i-1,j], b[i-1,j+1]) + C[i,j];
Thuật toán trên độ phức tạp là O(n2) và chi phí bộ nhớ là mảng 2 chiều nxn phần tử.
* ý tưởng 3: Quy hoạch động rút gọn hơn
Ta nhận thấy cách làm 2 tuy tốt nhưng bộ nhớ sử dụng là mảng 2 chiều nên hiện tại với
Freepascal ta chỉ có thể khai báo mảng tối đa khoảng b[1..10000,1..10000] (n<=10.000) vậy nên không
thể chạy được với dữ liệu lớn.
Ta thấy: Để đi đến dòng i thì chỉ có thể đi từ dòng i-1. Tính đến dòng thứ n thì ta thấy chỉ cần
dòng n-1 còn các dòng trước không có tác dụng ( lãng phí bộ nhớ). Vậy nên ý tưởng ở đây là ta chỉ cần
dùng các mảng một chiều, mỗi lần tính ta chỉ quan tâm 2 dòng: mảng lưu dòng i-1 và mảng lưu dòng i.
Fillchar(t,sizeof(t),0);
For j:=1 to n do
Begin
For i:=1 to n do read(a[i]);
//Đọc một dòng từ tệp
For i:=1 to n do B[i]:=max(t[i-1], t[i], t[i+1]) + a[i];
T:=b;
End;
Với cách làm này cho ta giải quyết được với n>=10.000.
4. Một số bài tập mở đầu làm quen với QHĐ:
4.1 Lát gạch version 1:
Nguồn: SPOJ – bài: LATGACH
Cho một hình chữ nhật kích thước 2xN (1<=N<=100). Hãy đếm số cách lát các viên gạch nhỏ kích thước
1×2 và 2×1 vào hình trên sao cho không có phần nào của các viên gạch nhỏ thừa ra ngoài, cũng không có
vùng diện tích nào của hình chữ nhật không được lát.
Input
Gồm nhiều test, dòng đầu ghi số lượng test T ( T<=100 ).
T dòng sau mỗi dòng ghi một số N.
Output
Ghi ra T dòng là số cách lát tương ứng.
Example
Latgach1.in Latgach1.ou
Latgach1.in
p
t
p
3
1
2
1
2
4
2
3
5
3
Gợi ý: f[i] là số cách lát gạch cho sân kích thức 2 x i
4.2 Tổng đoạn:
Cho một dãy số nguyên A gồm n phần tử (ai<=10.000, n<=10 5). Nam
bất kỳ trong dãy số trên là bao nhiêu. Bạn hãy giúp cho Nam tính nhé!
Input
Dòng đầu ghi số lượng phần tử của dãy số N
Giáo viên: Lê Thanh Phú
4|Tra n g
Latgach1.o
ut
5
8
thắc mắc liệu tổng của một đoạn
Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
Dòng 2: ghi các phần tử của dãy số A
Dòng 3: ghi số lượng test T ( T<=105).
T dòng sau mỗi dòng ghi hai số a và b (nam muốn tính tổng của đoạn [a..b]
Output Ghi ra T dòng tổng tương ứng của các bộ test
Example
Tongdoan.in Tongdoan.ou
p
t
3
3
135
9
2
12
13
Gợi ý: nếu dùng cách duyệt bình thường O(n2) thì chắc chắn sẽ không chạy được với n=105, t=105.
4.3 Hiệu lớn nhất: Nguồn: NTU – Bài: Hiso
Cho một dãy n số nguyên a1, a2, ..., an. Hãy tìm hai chỉ số i, j sao cho i < j và hiệu aj - ai là lớn nhất.
Dữ liệu vào: gồm 2 dòng
- Dòng 1: là số nguyên n (2 ≤ n ≤ 105)
- Dòng 2: gồm n số nguyên a1, a2, ..., an (0 ≤ ai ≤ 109)
Dữ liệu xuất:
- Là giá trị lớn nhất của hiệu aj - ai.
Ví dụ
hieuso.inp hieuso.out
hieuso.in hieuso.ou
p
t
3
2
4
3
123
2513
4.4 Lát gạch Version 2: Nguồn NTU – Bài: Laga
Có một khoảng sân hình chữ nhật kích thước 2 x n ô vuông, gồm 2 hàng và n cột. Đánh số hàng từ 1 đến
2 theo thứ tự từ trên xuống dưới, đánh số cột từ 1 đến n theo thứ tự từ trái qua phải. Người ta muốn lát
sân bằng gạch màu trắng và điểm xuyết một số ô gạch màu đen, mỗi ô vuông được lát bởi một viên
gạch, sao cho không có hai viên gạch màu đen nào chung cạnh với nhau. Hỏi có tất cả bao nhiêu cách
khác nhau để lát khoảng sân trên (hai cách lát sân được gọi là khác nhau nếu tồn tại tối thiểu một ô ở
dòng i cột j được lát gạch màu trắng ở cách này và lát gạch màu đen ở cách kia).
Ví dụ với n = 2, ta có 7 cách lát sân sau đây:
Dữ liệu nhập: - Là một số nguyên n (1 ≤ n ≤ 1.000)
Dữ liệu xuất:
- Là số cách lát gạch khoảng sân theo yêu cầu trên. Số lượng này có thể rất lớn nên chỉ cần in ra 8 số cuối
(mod 100.000.000)
Ví dụ
Laga.inp Laga.out
Laga.inp Laga.out
Laga.inp Laga.out
2
7
3
17
4
41
4.5 Dạo chơi bằng xe buýt: Nguồn: SPOJ – Bài: KMbus
Một tuyến đường ở thành phố có các bến xe bus ở từng km tuyến đường. Mỗi lần qua bến, xe đều đỗ
để đón khách. Mỗi bến đều có điểm xuất phát. Một xe chỉ chạy không quá B km kể từ điểm xuất phát
Giáo viên: Lê Thanh Phú
5|Tra n g
Tài liệu bồi dưỡng học sinh giỏi THPT – Môn Tin học
Phần II: Chuyên đề nâng cao
của nó. Hành khách khi đi xe sẽ phải trả tiền cho độ dài đoạn đường mà họ ngồi trên xe. Cước phí cần
trả để đi đoạn đường độ dài i là Ci(i=1,2..B). Một du khách xuất phát từ 1 bến nào đó muốn đi dạo L km
theo tuyến nói trên. Hỏi ông ta phải lên xuống xe như thế nào để tổng số tiền phải trả là nhỏ nhất có
thể.
Dữ liệu vào:
Dòng đầu ghi 2 số nguyên dương B, L.
Dòng thứ i trong số B dòng tiếp theo ghi 1 số nguyên dương Ci ( 1 ≤ i ≤ B ).
Kết qủa
Một dòng duy nhất là số tiền nhỏ nhất phải trả
Giới hạn
0 ≤ B ≤ 100 0 ≤ L ≤ 10000
0 ≤ Ci ≤ 100
Ví dụ
Dữ liệu:
57
3
4
6
9
22
Kết qủa
14
Giáo viên: Lê Thanh Phú
6|Tra n g