Tải bản đầy đủ (.doc) (18 trang)

PHƯƠNG PHÁP QUY HOẠCH ĐỘNG bồi dưỡng tin 9

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (203.04 KB, 18 trang )

Một số bài toán Quy hoạch động

Phương pháp quy hoạch động dùng để giải bài tốn tối ưu có bản chất đệ quy, tức là
việc tìm phương án tối ưu cho bài tốn đó có thể đưa về tìm phương án tối ưu của một
số hữu hạn các bài toán con. Đối với nhiều thuật toán đệ quy chúng ta đã tìm hiểu,
nguyên lý chia để trị (divide and conquer) thường đóng vai trị chủ đạo trong việc thiết
kế thuật toán. Để giải quyết một bài toán lớn, ta chia nó làm nhiều bài tốn con cùng
dạng với nó để có thể giải quyết độc lập. Trong phương pháp quy hoạch động, nguyên
lý này càng được thể hiện rõ: Khi khơng biết cần phải giải quyết những bài tốn con
nào, ta sẽ đi giải quyết tất cả các bài toán con và lưu trữ những lời giải hay đáp số của
chúng với mục đích sử dụng lại theo một sự phối hợp nào đó để giải quyết những bài
tốn tổng qt hơn. Đó chính là điểm khác nhau giữa Quy hoạch động và phép phân
giải đệ quy và cũng là nội dung phương pháp quy hoạch động:
Phép phân giải đệ quy bắt đầu từ bài toán lớn phân rã thành nhiều bài toán con và đi
giải từng bài toán con đó. Việc giải từng bài tốn con lại đưa về phép phân rã tiếp thành
nhiều bài toán nhỏ hơn và lại đi giải tiếp bài tốn nhỏ hơn đó bất kể nó đã được giải hay
chưa.
Quy hoạch động bắt đầu từ việc giải tất cả các bài toán nhỏ nhất ( bài tốn cơ sở)
để từ đó từng bước giải quyết những bài toán lớn hơn, cho tới khi giải được bài toán
lớn nhất (bài toán ban đầu).
Ta xét một ví dụ đơn giản:
Dãy Fibonacci là dãy vơ hạn các số nguyên dương F[1], F[2], … được định nghĩa như
sau:
F[i]=1, if i ≤ 2
F[ i ] = { F[ i −1] + F[ i − 2], if i ≥ 3
Hãy tính F[6]
Xét hai cách cài đặt chương trình:
Cách 1

Cách 2


program Fibo1;

program Fibo2;
var
F: array[1..6] of Integer;
i: Integer;

function F(i: Integer): Integer;
begin
if i < 3 then F := 1
else F := F(i - 1) + F(i - 2);
end;

begin
F[1] := 1; F[2] := 1;
for i := 3 to 6 do
F[i] := F[i - 1] + F[i - 2];
WriteLn(F[6]);
end.

begin
WriteLn(F(6));
end.

Cách 1 có hàm đệ quy F(i) để tính số Fibonacci thứ i. Chương trình chính gọi F(6), nó
sẽ gọi tiếp F(5) và F(4) để tính … Q trình tính tốn có thể vẽ như cây dưới đây. Ta
nhận thấy để tính F(6) nó phải tính 1 lần F(5), hai lần F(4), ba lần F(3), năm lần F(2), ba
lần F(1).

-1-



Một số bài tốn Quy hoạch động


ch 2 thì khơng như vậy. Trước hết nó tính sẵn F[1] và F[2], từ đó tính tiếp F[3], lại tính
tiếp được F[4], F[5], F[6]. Đảm bảo rằng mỗi giá trị Fibonacci chỉ phải tính 1 lần.
(Cách 2 cịn có thể cải tiến thêm nữa, chỉ cần dùng 3 giá trị tính lại lẫn nhau)
Trước khi áp dụng phương pháp quy hoạch động ta phải xét xem phương pháp đó có
thoả mãn những yêu cầu dưới đây hay khơng:
Bài tốn lớn phải phân rã được thành nhiều bài toán con, mà sự phối hợp lời giải của
các bài tốn con đó cho ta lời giải của bài tốn lớn.
Vì quy hoạch động là đi giải tất cả các bài tốn con, nên nếu khơng đủ không gian vật lý
lưu trữ lời giải (bộ nhớ, đĩa…) để phối hợp chúng thì phương pháp quy hoạch động
cũng khơng thể thực hiện được.
Q trình từ bài tốn cơ sở tìm ra lời giải bài tốn ban đầu phải qua hữu hạn bước.
Các khái niệm:
- Bài toán giải theo phương pháp quy hoạch động gọi là bài toán quy hoạch động
- Công thức phối hợp nghiệm của các bài tốn con để có nghiệm của bài tốn lớn gọi là
cơng thức truy hồi (hay phương trình truy tốn) của quy hoạch động
- Tập các bài toán nhỏ nhất có ngay lời giải để từ đó giải quyết các bài toán lớn hơn gọi
là cơ sở quy hoạch động
- Khơng gian lưu trữ lời giải các bài tốn con để tìm cách phối hợp chúng gọi là bảng
phương án của quy hoạch động
Các bước cài đặt một chương trình sử dụng quy hoạch động:
- Giải tất cả các bài tốn cơ sở (thơng thường rất dễ), lưu các lời giải vào bảng phương
án. Dùng công thức truy hồi phối hợp những lời giải của những bài toán nhỏ đã lưu
trong bảng phương án để tìm lời giải của những bài toán lớn hơn và lưu chúng vào bản
phương án. Cho tới khi bài tốn ban đầu tìm được lời giải.
- Dựa vào bảng phương án, truy vết tìm ra nghiệm tối ưu.

Cho đến nay, vẫn chưa có một định lý nào cho biết một cách chính xác những bài
tốn nào có thể giải quyết hiệu quả bằng quy hoạch động. Tuy nhiên để biết được
bài tốn có thể giải bằng quy hoạch động hay khơng, ta có thể tự đặt câu hỏi: “Một
nghiệm tối ưu của bài toán lớn có phải là sự phối hợp các nghiệm tối ưu của các
bài tốn con hay khơng ?” và “Liệu có thể nào lưu trữ được nghiệm các bài toán
con dưới một hình thức nào đó để phối hợp tìm được nghiệm bài tốn lớn"
MỘT SỐ BÀI TỐN QHĐ CƠ BẢN
Bài toán 1: Dãy con đơn điệu tăng dài nhất:

-2-


Một số bài toán Quy hoạch động

Cho dãy A gồm n số nguyên, ký hiệu [a0, a1,..., an-1] . Tìm một dãy con đơn điệu tăng
dài nhất của dãy A, biết một dãy con của A là dãy có được từ A bằng cách xóa đi một số
phần tử của A.
Ví dụ: dãy [1, 5, 9, 2, 3, 11, 8, 10, 4] có dãy con đơn điệu tăng dài nhất là [1, 2, 3, 8,
10].
Input: đọc từ tệp Day.inp gồm các số nguyên trong dãy A được viết cách nhau bởi dấu
cách
Output: Ghi kết quả tính được vào tệp Day.out gồm 2 dòng
- Dòng 1: ghi độ dài của dãy con dài nhất
- Dòng 2: Ghi lần lượt từng phần tử của dãy con.
Cách
giải:
Bổ sung vào a 2 phần tử a[0]=-∞ và a[n+1]=+∞ khi đó dãy đơn điệu dài nhất
chắc
chắn
sẽ

bắt
đầu
từ
a[0]

kết
thúc

a[n+1]
với ∀ i 0 ≤ i ≤ n+1. ta tính L[i] bằng độ dại dãy con đơn điệu tăng dài nhất bắt đầu tại
a[i].
*

sở
QHĐ
L[n+1]=1

dãy
này
chỉ
gồm
1
phần
tử
là +∞
* Cơng thức truy hồi
Giả sử với i chạy từ n về 0, ta cần tính L[i]: độ dài dãy con tăng dài nhất bắt đầu tại a[i].
L[i] được tính trong điều kiện L[i+1..n+1] đã biết:
Dãy con đơn điệu tăng dài nhất bắt đầu từ a[i] sẽ được thành lập bằng cách lấy a[i] ghép
vào đầu một trong số những dãy con đơn điệu tăng dài nhất bắt đầu tại vị trí a[j] đứng

sau a[i]. Ta sẽ chọn dãy nào để ghép a[i] vào đầu? Tất nhiên là chỉ được ghép a[i] vào
đầu những dãy con bắt đầu tại a[j] nào đó lớn hơn a[i] (để đảm bảo tính tăng) và dĩ
nhiên ta sẽ chọn dãy dài nhất để ghép a[i] vào đầu (để đảm bảo tính dài nhất). Vậy L[i]
được tính như sau: Xét tất cả các chỉ số j trong khoảng từ i + 1 đến n + 1 mà a[j] >
a[i], chọn ra chỉ số jmax có L[jmax] lớn nhất. Đặt L[i] := L[jmax] + 1:
L [i ] = max L [ j] +1
i < j≤ n −1
a [ i ]< a [ j]
* Truy vết
Tại bước xây dựng dãy L, mỗi khi gán L[i] := L[jmax] + 1, ta đặt T[i] = jmax. Để lưu lại
rằng:
Dãy con dài nhất bắt đầu tại a[i] sẽ có phần tử thứ hai kế tiếp là a[jmax].
Sau khi tính xong hay dãy L và T, ta bắt đầu từ T[0]. T[0] chính là phần tử đầu
tiên được chọn, T[T[0]] là phần tử thứ hai được chọn, T[T[T[0]]] là phần tử thứ ba
được chọn …
Quá trình truy vết có thể diễn tả như sau:
i := T[0];
while
i
<>
n
+
{Chừng nào chưa duyệt đến số a[n+1]=+∞ ở cuối} begin
<Thông báo chọn a[i]>
-3-

1

do



Một số bài tốn Quy hoạch động

i := T[i];
end;
Ví dụ: với A = (5, 2, 3, 4, 9, 10, 5, 6, 7, 8). Hai dãy L và T sau khi tính sẽ là:
i

0

1

2

3

4

5

6

7

8

9

10


11

ai − ∞ 5
L[i
] 9
5

2

3

4

9

10

5

6

7

8

+∞

8

7


6

3

2

5

4

3

2

1

T[i]2

3

4

7

6

11

8


9

10 11

8

Cách 1: Duyệt từ cuối dãy về đầu dãy
Const Max = 5000;
Var a,L,T:array [0..Max] of integer;
i,j,n,jmax:integer; fi,fo:text;
Procedure Nhapdulieu;
begin
assign(fi,'day.inp'); reset(fi); N:=0;
while not eof (fi) do
begin
N:=N+1;
read(fi,a[N]);
end;
close(fi);
end;
BEGIN
assign(fo,'day.out'); rewrite(fo);
Nhapdulieu;
a[0]:= low(integer);a[n+1]:=high(integer); {thêm 2 phần tử canh 2 đầu dãy}
L[n+1]:= 1; {Khởi tạo cơ sở QHĐ}
for i:= n downto 0 do {tính bảng phương án}
begin
{chọn trong các chỉ số j đứng sau i thỏa mãn a[j]>a[i] chọn ra jmax có L[jmax] lớn
nhất}

jmax:=n+1;
for j:=i+1 to n+1 do
if (a[i]<a[j]) and (L[j]>L[jmax]) then jmax:=j;
L[i] := L[jmax]+1; {lưu độ dài dãy tăng dài nhất tại ai}
T[i] := jmax; {lưu vết: phần tử đứng liền sau a[i] trong dãy con tăng dài nhất đó là
a[jmax]}
end;
Writeln(fo,L[0]-2); { độ dài dãy con tăng dài nhất =L[0]-2 }
i:=T[0]; {bắt đầu truy vết tìm nghiệm}
while i<>n+1 do
begin
-4-


Một số bài toán Quy hoạch động

write(fo,a[i],' ');
i:=T[i];
end;
close(fo);
END.
Cách 2: Duyệt từ đầu dãy về cuối dãy
Const
Max
=
Var
a,L,T:array
[0..Max]
i,j,n,jmax:integer;
Procedure

begin
assign(fi,'day.inp');
reset(fi);
while
not
eof

5000;
integer;
fi,fo:text;
Nhapdulieu;

of

N:=0;
do
begin
read(fi,a[N]);
end;

(fi)

N:=N+1;
close(fi);
end;
Procedure
Begin

quyhoachdong;
for


if

End;
Procedure
Begin

L[0]:=1;a[0]:=low(integer);a[N+1]:=high(integer);
i:=1
to
N+1
do
begin
jmax:=0;
for
j:=0
to
i-1
do
(a[j]and
(L[j]>L[jmax])
then
jmax:=j;
L[i]:=L[jmax]+1;
T[i]:=jmax;
end;

Truyvet(i:integer);


{dùng

đệ

quy

if

để

in

i=0

dãy
then

write(fo,a[i],'
end;
Procedure
assign(fo,'day.out');
writeln(fo,'so

luong

phan

tối

ưu}


i:=T[i];
exit;
truyvet(i);
');

ghidulieu;
Begin
rewrite(fo);
quyhoachdong;
tu=',l[n+1]-2);
truyvet(N+1);
close(fo);
End;

BEGIN
Nhapdulieu;
Ghidulieu;
-5-


Một số bài toán Quy hoạch động

END.
Bài
toán
2:

cung
Trong một chuyến thám hiểm mạo hiểm, một đồn thám hiểm khơng may lọt vào một mê

cung với nhiều cạm bẫy. Trong mê cung đó chỉ có một lối ra duy nhất, lối ra bao gồm
các ơ hình vng được xếp thành một hàng dài. Muốn đi được ra ngoài mọi người phải
bước qua một hàng các ơ hình vng đó và phải bước theo quy tắc sau:
- Quy tắc 1: Mỗi bước chỉ có thể bước một ơ, hai ơ hoặc ba ơ
- Quy tắc 2: Từ người thứ 2 trở đi bước theo quy tắc 1 và khơng được trùng với các
cách
bước
của
tất
cả
những
người
đi
trước
đó.
Hỏi đồn thám hiểm đó cịn lại tối thiểu bao nhiêu người khơng thể thốt ra khỏi mê
cung
đó.
Dữ
liệu
vào:
mecung.inp
- Dịng 1: ghi số nguyên m (m<=1018) là số người trong đoàn thám hiểm
- Dòng 2: ghi một số nguyên n (n<=70) là tổng số ô vuông
Dữ
liệu
ra:
mecung.out
Gồm một số nguyên duy nhất thể hiện số người còn lại tối thiểu bao nhiêu người khơng
thể

thốt
ra
khỏi

cung
Ví dụ:
mecung.inp
mecung.out
20
7
5

-

Bài
tốn
3:
Dãy
con
chung
dài
nhất
Cho 2 dãy số ngun A=<a1,a2,..,an> và B=<b1,b2,…,bm>. Hãy tìm dãy số nguyên C sao
cho C là dãy con chung dài nhất của A và B.
Dữ liệu vào: từ tệp daychung.inp gồm 3 dòng
Dòng 1 ghi 2 số nguyên n,m là độ dài 2 dãy
Dòng 2 ghi n số nguyên cách nhau bởi dấu cách
Dòng 3 ghi m số nguyên cách nhau bởi dấu cách
Dữ liệu ra: Ghi vào tệp daychung.out gồm 2 dòng
Dòng 1 ghi 1 số nguyên thể hiện độ dài xâu cong chung dài nhất

Dòng 2 ghi các phần tử của xâu con chung, mỗi phần tử cách nhau bởi dấu cách.
v Ví dụ:
daychung.inp
66
157896
123796

daychung.out
4
1796

Bài tốn 4: Xâu con chung dài nhất (như bài trên)
Xâu kí tự A được gọi là xâu con của xâu kí tự B nếu ta có thể xóa đi một số kí tự trong
xâu B để được xâu A.
Cho 2 xâu ký tự X,Y. Hãy tìm xâu kí tự Z có độ dài lớn nhất và là con của cả X
và Y.
Input:
Dòng 1 chứa xâu X
-6-


Một số bài tốn Quy hoạch động

Dịng 2 chứa xâu Y
Output: chỉ gồm 1 dòng ghi độ dài xâu Z tìm được
Ví dụ:
Input.inp
Output.out
ALGORITHM
7

LOGARITHM
Bài tốn 5: Ba lơ (cái túi)
Cho n gói hàng (n<=1000). Gói hàng thứ i có khối lượng là W[i]<=1000 và giá trị
V[i]<=1000. Cần chọn những gói hàng nào để bỏ vào một ba lô sao cho tổng giá trị của
các gói hàng đã chọn là lớn nhất nhưng tổng khối lượng của chúng không vượt quá khối
lượng M<=1000 cho trước. Mỗi gói chỉ chọn 1 hoặc khơng chọn.
Input data: cho file văn bản balo.inp
- Dòng 1: N, M cách nhau ít nhất 1 dấu cách
- N dịng tiếp theo: mỗi dòng gồm 2 số Wi và Vi là khối lượng và giá trị gói hàng
Output data: ghi kết quả vào file văn bản balo.out giá trị lớn nhất của các gói hàng mà
balo có thể chứa.
Ví dụ:
balo.inp
balo.out
5 13
16
3 4
4 5
5 6
2 3
1 1
Giải thích
n = 5; M = 13
I
1
2
3
4
5
W[i]

3
4
5
2
1
V[i]
4
5
6
3
1
Tổng giá trị của các gói hàng bỏ vào ba lơ: 16
Các gói được chọn: 1(3, 4) 2(4, 5) 3(5, 6) 5(1, 1)
Cách giải:
Gọi f[i,j] là tổng giá trị lớn nhất của túi khi xét từ vật 1 đến vật i với trọng lượng không
vượt quá j (trọng lượng giới hạn).
Khi xét vật i thì có 2 khả năng
- Khả năng 1: nếu trọng lượng của vật i là w[i] lớn hơn trọng lượng giới hạn j
(w[i]>j) thì giá trị của vật i bằng giá trị của vật i-1 tại giới hạn j.
Tức là f[i,j]=f[i-1,j]
- Khả năng 2: nếu trọng lượng vật i là w[i] nhỏ hơn hoặc bằng giới hạn j
(w[i]<=j) thì có thể bỏ được vật i vào túi, trọng lượng còn lại của túi là j-w[i] có thể
chọn từ các vật từ 1 đến i-1. {giải thích: túi chỉ chứa được trọng lượng j, khi đã bỏ vào
túi trọng lượng w[i] thì trọng lượng cịn lại của túi là j-w[i] được chọn từ các vật trước
đó}. Vậy giá trị của túi khi bỏ vật i vào là giá trị của vật i-1 tại giới hạn j-w[i] cộng với
giá trị của vật i là v[i].
-7-


Một số bài toán Quy hoạch động


Tức là f[i,j]=f[i-1,j-w[i]]+v[i]
Với 2 khả năng có thể xét ở trên thì ta lấy giá trị của khả năng nào tối ưu nhất ?
Muốn biết khả năng nào tối ưu thì ta xét xem giá trị của khả năng nào lớn hơn thì ta
lấy khả năng đó.
Tức là F[i,j]=max(f[i-1,j], f[i-1,j-w[i]]+v[i])
code QHĐ
f[0,j]:=0;
for

i:=1

to

for

j:=1
if

n

do
to
m
do
w[i]<=j
then
f[i,j]:=max(f[i-1,j],f[i-1,j-w[i]]+v[i])
else
f[i,j]:=f[i-1,j];


write(fo,f[n,m]);
Bài tốn 6: Farmer (người nơng dân)
Một người có N mảnh đất và M dải đất. Các mảnh đất có thể coi là một tứ giác và các
dải đất thì coi như một đường thẳng. Dọc theo các dải đất ông ta trồng các cây bách, dải
đất thứ i có Ai cây bách. Ơng ta cũng trồng các cây bách trên viền của các mảnh đất,
mảnh đất thứ j có Bj cây bách. Cả ở trên các mảnh đất và dải đất, xen giữa 2 cây bách
ơng ta trồng một cây ơliu. Ơng ta cho con trai được chọn các mảnh đất và dải đất tuỳ ý
với điều kiện tổng số cây bách không vượt quá Q. Người con trai phải chọn thế nào để
có nhiều cây ơliu (lồi cây mà anh ta thích) nhất.
Input data: dữ liệu được cho trong file Famer.inp
Dòng đầu tiên bao gồm: Đầu tiên là số Q (0<=Q<=150000) là số cây bách mà người
con được chọn; sau đó là số nguyên M là số cánh đồng; tiếp theo là số nguyên K là số
dải đất.
- Dòng thứ hai chứa M số nguyên N 1, N2, …, Nm (3<= N1, N2, …, Nm<=150) là số cây
bách trên cánh đồng
- Dòng thứ 3 chứa K số nguyên R1, R2, …, Rk (2<= R1, R2, …, Rk <= 150) là số cây bách
trên dải đất.
Output data: Ghi ra tệp Farmer.out gồm 1 số nguyên duy nhất thể hiện số cây oliu lớn
nhất mà người con có thể thừa hưởng.
Ví dụ
Famer.inp
Famer.out
17 3 3
17
13 4 8
4 8 6
Bài toán 7: Đổi tiền
Bạn được cho một tập hợp các mệnh giá tiền. Tập hợp luôn chứa phần tử mang giá trị 1.
Mỗi mệnh giá có vơ hạn các đồng tiền mang mệnh giá đó. Cho số tiền S, hãy tìm cách

đổi S thành ít đồng tiền nhất, sao cho mỗi đồng tiền có mệnh giá thuộc tập hợp đã cho.
Input data: Dữ liệu vào được đọc từ tệp doitien.inp gồm 2 dòng:
- Dòng 1: Hai số nguyên dương N (số phần tử của tập hợp mệnh giá tiền) và S (số
tiền cần đổi) (1 ≤ N ≤ 100; 1 ≤ S ≤ 106 ).
-8-


Một số bài tốn Quy hoạch động

- Dịng 2: N số nguyên dương biểu thị mệnh giá của các phần tử trong tập hợp (giá
trị không vượt quá 100).
Output data: Ghi kết quả ra tệp doitien.out gồm một số nguyên duy nhất là số đồng
tiền ít nhất có thể đổi được.
Ví dụ
doitien.inp
2 3
1 2

doitien.out
2

Bài tốn 8: Bố trí phịng họp
Có n cuộc họp, cuộc họp thứ i bắt đầu vào thời điểm ai và kết thúc ở thời điểm bi. Do
chỉ có một phịng hội thảo nên 2 cuộc họp bất kì sẽ được cùng bố trí phục vụ nếu
khoảng thời gian làm việc của chúng không giao nhau hoặc chỉ giao nhau tại đầu mút.
Hãy bố trí phịng họp để phục vụ được nhiều cuộc họp nhất.
Input data: file văn bản phonghop.inp
- Dòng 1: số nguyên dương N (N<=1000)
- N dòng tiếp theo: dòng thứ i chứa 2 số nguyên dương (ai,bi<=1000) chỉ thời điểm bắt
đầu và thời điểm kết thúc của cuộc họp i

Output data: file văn bản phonghop.out. Một số nguyên duy nhất thể hiện số cuộc họp
nhiều nhất
Ví dụ:
phonghop.inp
4
45
56
16
69

phonghop.out
3

Bài tốn 9: Cho th máy: Trung tâm tính tốn hiệu năng cao nhận được đơn đặt hàng
của N khách hàng. Khách hàng i muốn sử dụng máy trong khoảng thời gian từ ai đến bi
và trả tiền thuê là ci. Hãy bố trí lịch thuê máy để tổng số tiền thu được là lớn nhất mà
thời gian sử dụng máy của 2 khách hàng bất kì được phục vụ đều khơng giao nhau (cả
trung tâm chỉ có một máy cho thuê)
Input data: file văn bản thuemay.inp
- Dòng 1: số nguyên dương N (N<=10000)
- N dòng tiếp theo: dòng thứ i chứa 3 số nguyên dương (ai,bi,ci<=100
Output data: file văn bản thuemay.out. Một số nguyên duy nhất thể hiện số tiền lớn
nhất thu được.
Ví dụ:
thuemay.inp
3
1 8 16
276
799


thuemay.out
16

Bài tốn 10: Tiền khách sạn
-9-


Một số bài toán Quy hoạch động

Trong dịp nghi lễ 30 tháng 4 và 1 tháng 5 vừa qua do cùng đợt nghỉ với ngày giỗ tổ
Hùng Vương 10 tháng 3(âm lịch) nên số ngày nghỉ lễ tăng lên. Vì thế lượng khách du
lịch đổ về Nha Trang tham quan cũng tăng kỷ lục, dẫn đến tinh trạng các khách sạn ở
đây “cháy phòng”. Khách sạn Quang Huy chỉ còn một phịng nên quyết định cho th
phịng này theo hình thức thỏa thuận về giá cả. Sau khi tổng hợp các đơn đặt hàng,
khách sạn nhận được n đơn đặt hàng, trong đó đơn đặt hàng thứ i đăng ký ngày bắt đầu
là ai, ngày trả phòng là bi và chấp nhận trả số tiền th phịng là ci.
Do có nhiều đơn đặt hàng, thời gian đặt phòng lại chồng chéo nhau, số tiền khách
hàng chấp nhận trả cho khách sạn cũng khác nhau nên ban quản lý khách sạn đang rất
khó khăn khơng biết nhận lời hay từ chối khách hàng nào.
Yêu cầu: Viết chương trình giúp khách sạn nhận đơn đặt phòng sao cho lợi nhuận
thu được là lớn nhất.
Lưu ý: Theo điều lệ của khách sạn, khách hàng phải trả phòng trước 12 giờ trưa,
khách hàng khác có thể nhận phịng từ 12 giờ trong một ngày.
Dữ liệu vào: được ghi trên tệp tienks.inp bao gồm:
o Dòng thứ nhất là số nguyên n (1 ≤ n ≤ 12000) thể hiện số đơn đặt hàng.
o n dòng tiếp theo gồm 3 số nguyên ai, bi và ci. Mỗi số cách nhau một khoảng trắng với
ràng buộc(l ≤ai ≤ bi ≤ 100, 0 ≤ c ≤1000).
Dữ liệu ra: lưu trong tệp tienks.out với một số nguyên thể hiện số tiền lớn nhất
TIENKS.INP
TIENKS.OUT

20
3
Vi dụ 1

128
236
476
17

4
145
Vi dụ 2

138
354
469

10. Tìm xâu con chung dài nhất
Cho A và B là hai xâu. Tìm xâu con chung dài nhất của A và B.

Dữ liệu vào: file văn bản STRING.INP: dòng 1 ghi xâu A, dòng 2 ghi xâu B

Kết quả: ghi ra file văn bản STRING.OUT: gồm duy nhất 1 dòng là xâu con tìm
được
Ví dụ:
STRING.INP
STRING.OUT
THANHCONG
TNHONG
- 10 -



Một số bài toán Quy hoạch động

TNONTHONG
Giải:

Bước 1: Lập hệ thức
− Giả sử n là độ dài của xâu A, m là độ dài của xâu B
− Gọi L[i,j] là độ dài xâu con chung dài nhất khi xét xâu A gồm i kí tự đầu tiên và của
xâu B gồm j kí tự đầu tiên (i<=n, j<=m). Ta có:
• Nếu i=0 hoặc j=0 thì L[i,j] = 0
• Nếu i>0 và j>0 và A[i] = B[j] thì L[i,j] = 1 + L[i-1,j-1]
• Nếu i>0 và j>0 và A[i] # B[j] thì L[i,j] = Max(L[i-1,j],L[i,j-1])

Bước 2: Tổ chức dữ liệu và chương trình
− Xây dựng bảng hai chiều kích thước m+1 dịng và n + 1 cột để tính L:
L 0 1 2 3 4 ...
...
n
0 0 0 0 0 0 0
0
0
1 0
2 0
[i-1,j- [i-1,j]
1]
... 0
[i,j-1] [i,j]
m 0

KQ
• Cách thực hiện: Dùng dịng 0 để tính dịng 1, dùng dịng 1 để tính dịng
2, ... đến khi tính hết dịng m, tại mỗi dịng lần lượt tính từ cột 1 đến cột n
For i:=1 To m Do L[i,0] := 0;
For j:=1 To n Do L[0,j] := 0;
For i:=1 To m Do
For j:=1 To n Do
If A[i]=B[j] Then L[i,j] := 1 + L[i-1,j-1]
Else L[i,j] := Max(L[i-1,j], L[i,j-1]);
− Truy vết xác định xâu con chung: dựa vào bảng L:
S := ‘’; i := m; j := n;
Repeat
If A[i] = B[j] Then Begin Insert(A[i],S,1); i:=i-1; j:=j-1; End
Else If L[i,j] = L[i-1,j] Then i := i – 1 Else j := j-1;
Until ( i=0) or (j=0);

Bước 3: Cải tiến: Không

- 11 -


Một số bài tốn Quy hoạch động

11. Tìm đường đi trên tam giác số
Hình bên biểu diễn một tam giác số. Hãy viết chương
7
trình tính tổng lớn nhất các số trên con đường bắt đầu từ
3 5
đỉnh và kết thúc ở đáy, biết rằng tại mỗi bước đi phải đi
8 1 0

chéo xuống lệch trái hoặc đi chéo xuống lệch phải. Mỗi số
2 7 4 4
trong tam giác số là số ngun khơng âm và có giá trị
4 5 2 6 5
<=1000. Số lượng dịng khơng q 10000.

Dữ liệu vào: file văn bản TRIANG.INP:
Dòng đầu: ghi N là số lượng dòng
a.
b. Các dòng tiếp theo ghi các số biểu diễn tam giác, dòng thứ i ghi i số cách
nhau 1 dấu cách

Kết quả: file văn bản TRIANG.OUT: ghi duy nhất giá trị tổng lớn nhất tìm được
Ví dụ:
TRIANG.INP
5
7
35
810
2744
45265

TRIANG.OUT
30

Giải:

Bước 1: Lập hệ thức
Gọi A(N,N) là ma trận lưu các số trong tam giác số
c.

d. Gọi M[i,j] là tổng lớn nhất của các số trên đường đi tính đến dịng i, cột j
(i<=n, j<=i)
• Nếu i=1 và j=1 thì M[i,j] = A[i,j]
• Nếu j=1 thì M[i,j] = A[i,j]+ M[i-1,j]
• Nếu j=i thì M[i,j] = A[i,j]+ M[i-1,j-1]
• Nếu j#1 và j# i thì M[i,j] = A[i,j] + Max(M[i-1,j-1], M[i-1,j])

Bước 2: Tổ chức dữ liệu và chương trình
Xây dựng mảng hai chiều kích thước n dịng và n cột để tính M:
e.
M 1
2 ...
...
n
1 A[1,1]
2
...
[i-1,j- [i-1,j]
1]
...
[i,j]
n
• Cách thực hiện: dùng dịng 1 để tính dịng 2, dùng dịng 2 để tính dịng
3, ... cho đến dịng n, tại mỗi dịng thứ i có thể bắt đầu tính từ 1 tăng đến i
hoặc theo hướng ngược lại từ i giảm xuống 1
M[1,1] := A[1,1];
For i:=1 To m Do
For j:= 1 To i Do
If j = 1 Then M[i,j] := A[i,j] + M[i-1,j]
Else If j = i Then M[i,j] := A[i,j] + M[i-1,j-1]

Else M[i,j] := A[i,j] + Max(M[i-1,j],M[i-1,j-1];
- 12 -


Một số bài toán Quy hoạch động

Truy vết: Xác định giá trị cần tìm là:
Max(M[n,j], j=1,n).
SumMax := M[n,1];
For j:=2 To n Do If SumMax < M[n,j] Then SumMax := M[n,j];
Bước 3: Cải tiến
g. Hệ thức: Ta đặt M[i,j] = 0 với j=0 hoặc j>i. Như vậy, hệ thức trên được
viết thành:
M[i,j] := A[i,j] + Max(M[i-1,j-1], M[i-1,j]), với i=1,n, j=1,i
h. Dữ liệu:
• Việc tính M[i,j] chỉ phụ thuộc vào M[i-1,j-1] và M[i-1,j] nên trong q
trình tính tốn ta chỉ cần sử dụng 2 mảng 1 chiều để lưu giá trị dòng i-1 và
dịng i
• Các giá trị của mảng A chỉ sử dụng 1 lần duy nhất đên ta không cần lưu trữ
mà chỉ cần vừa đọc vừa xử lý.
M: Array[1..2,0..MaxN]
f.



Fillchar(M,sizeof(M),0); { Đặt tất cả các giá trị trong M bằng 0 }
d := 1; { d là biến xác định dòng i-1, dòng i là 3-d }
For i:=1 To N Do
Begin
For j:=1 To N Do

Begin
Read(f,So); { So: chính là A[i,j] }
M[3-d,j] := So + Max(M[d,j-1], M[d,j]);
End;
d := 3 – d; { đảo dòng i thành dòng i-1 }
End;
Xếp hàng mua vé
Có N người được đánh số từ 1... N xếp hàng mua vé theo thứ tự tăng dần. Mỗi người
chỉ được mua một vé và người bán vé chỉ bán cho một người tối đa là 2 vé. Biết T i là
thời gian người i mua vé cho mình (i=1...N). Nếu người thứ i+1 nhờ người i mua hộ vé
cho mình thì thời gian để người i mua vé cho cả hai người là Ri (i=1...N-1).
Yêu cầu: Tìm phương án những người đứng lại mua vé và những người cần rời khỏi
hàng để người đứng trước mình mua hộ vé sao cho tổng thời gian bán đủ vé cho N
người là ít nhất

Dữ liệu vào: file văn bản TICKETS.INP: gồm 3 dòng:
Dòng đầu ghi số nguyên dương N, N<=100
i.
Dòng thứ hai ghi N số nguyên dương lần lượt T1 T2 ... TN
j.
k. Dòng thứ ba ghi N-1 số nguyên dương lần lượt R1 R2 ... RN-1

Kết quả: ghi ra file văn bản TICKETS.OUT: gồm 2 dòng
Dòng đầu ghi tổng số thời ít nhất
l.
m. Dịng thứ hai ghi số hiệu những người đứng lại mua vé
Ví dụ:
TICKETS.INP
TICKETS.OUT
5

8
12232
124
- 13 -


Một số bài toán Quy hoạch động

2344
Giải:

Bước 1: Lập hệ thức
n. Gọi M[i] là tổng thời gian nhỏ nhất cần để mua vé cho i người đầu tiên
(i<=n):
• Nếu i = 1 thì M[i] = T[i]
• Nếu i = 2 thì M[i] = Min(T[1]+T[2], R[1])
• Nếu i>=3 thì M[i] = Min(M[i-1]+T[i], M[i-2]+R[i-1])

Bước 2: Tổ chức dữ liệu và chương trình
o. Xây dựng mảng một chiều kích thước N để tính M và mảng một chiều C
kích thước N để đánh dấu những người được chọn đứng lại mua vé
1
2 3 4 .. .. N
. .
M T[1] .. .. .. .. .. ...
. . . . .
M[0] := 0; M[1] := T[1];
For i:=2 To N Do
M[i] := Min(M[i-1]+T[i], M[[i-2]+R[i-1]);
p. Truy vết: tổng số thời gian ít nhất là M[N].Dựa vào mảng M để đánh dấu

tương ứng vào mảng chọn C bắt đầu từ vị trí N:
i := N;
While i>0 Do If C[i]=M[i-1] + T[i] Then
Begin C[i] := 1; i := i – 1 End
Else Begin C[i-1]:= 1;i := i – 2; End;
Bước 3: Cải tiến: Không
12.Dãy con không giảm dài nhất
Cho dãy số nguyên A = a1,a2...,an. Dãy con của A là một cách chọn trong dãy A một
số phần tử giữ nguyên thứ tự.
Yêu cầu: Hãy tìm một dãy con của A khơng giảm có số lượng phần tử nhiều nhất.

Dữ liệu vào: file văn bản INCSEQ.INP, gồm 2 dòng:
Dòng 1: ghi số N là số lượng phần tử của dãy N<=1000
a.
b. Dòng2: ghi N số nguyên ai cách nhau ít nhất một dấu cách |ai|<=100000

Kết quả: ghi ra file văn bản INCSEQ.OUT, gồm 3 dòng:
Dòng 1: ghi M là số lượng phần tử của dãy con
c.
d. Dòng 2: ghi lần lượt vị trí của các số trong dãy A ban đầu
Dòng 3: ghi lần lượt giá trị của các số trong dãu A ban đầu
e.
Ví dụ:
INCSEQ.INP
INCSEQ.OUT
11
8
1 2 3 8 9 4 5 6 20 9 1 2 3 6 7 8 10 11
10
1 2 3 4 5 6 9 10

Giải:

Bước 1: Lập hệ thức
Gọi M[i] là độ dài lớn nhất của dãy con không giảm xét từ a[1] đến a[i]
f.
(i<=n):


- 14 -


Một số bài tốn Quy hoạch động

• Nếu i=1 thì M[1] =1
• Nếu a[i] < a[j] với mọi j thuộc đoạn 1 .. i -1 thì M[i] = 1, ngược lại
L[i]=1+M0 trong đó M0 = Max(L[j]) với a[j] <= a[i] và j thuộc đoạn 1 .. i
-1

Bước 2: Tổ chức dữ liệu và chương trình

Bước 3: Cải tiến
\Đường đi ngắn nhất
Có N thành phố đánh số 1..N. Đường đi (nếu có) giữa 2 thành phố bất kì đều là
đường đi 2 chiều. Sơ đồ mạng lưới giao thông của N thành phố được cho bởi ma trận
đối xứng A(N,N) trong đó:
u cầu: tìm đường đi ngắn nhất từ thành phố P đến thành phố Q
g. A[i,j] là độ dài đường đi từ thành phố i đến thành phố j
h. A[i,j] = 0 nếu khơng có đường đi trực tiếp từ thành phố i đến thành phố j
A[i,j] = A[j,i] và A[i,i] = 0. A[i,j] nguyên, không âm.
i.


Dữ liệu vào: file văn bản SORTPATH.INP gồm N+1 dòng
Dòng đầu: ghi N, P, Q (0j.
k. N dòng tiếp theo, dòng thứ i ghi N số nguyên A[i,1], A[i,2], ... , A[i,N],
(1<=i<=N)

Kết quả: ghi ra file văn bản SORTPATH.OUT gồm 2 dong
Dòng đầu: ghi độ dài ngắn nhất, nếu khơng có đường đi thì ghi -1
l.
m. Dịng thứ hai: nếu có đường đi ngắn nhất thì ghi thứ tự các thành phố nằm
trên đường
Ví dụ:
SORTPATH.INP SORTPATH.OUT
615
18
050009
165
506000
060700
007080
000809
900090
Giải:
Thuật tốn Floyd

Bước 1:
Lập hệ thức
n. Gọi D[i,j] là đường đi nhất từ thành phố i đến thành phố j, ban đầu:
• D[i,j] = vơ cùng lớn nếu i#j và A[i,j] = 0, ngược lại D[i,j] = A[i,j]

• Xét mọi k=1..n, nếu D[i,j] > D[i,k] + D[k,j] thì D[i,j] = D[i,k] + D[k,j]

Bước 2: Tổ chức dữ liệu và chương trình
o. Xây dựng mảng khoảng cách D(N,N), mảng G(N,N), G[i,j] = k có ý nghĩa
đường đi ngắn nhất từ i đến j phải đi qua đỉnh k:
For k :=1 To N Do For i := 1 To N Do
For j:=1 To N Do If D[i,j] > D[i,k] +D[k,j] Then
Begin D[i,j] := D[i,k] +D[k,j]; G[i,j] := k; End;
p. Truy vết: Xâu S lưu đường đi từ P đến Q:
Ok : =True; S:='PQ';
Repeat For i:=1 To length(S)-1 Do If G[i,i+1]<>0 Then
- 15 -


Một số bài toán Quy hoạch động

Begin Insert(S,i,G[i,j]); Ok := False; End; Until Ok;

Bước 3: Cải tiến
Thuật toán Dijkstra

Bước 1: Lập hệ thức
q. Gọi D[i] là độ dài đường đi ngắn nhất từ đỉnh xuất phát P đến đỉnh i. B[i]
đánh dấu những đỉnh nằm trên đường đi tối ưu từ đỉnh P.
• k sẽ là đỉnh tiếp theo nằm trên đường đi tối ưu nếu D[k]=Min(D[j]) với
B[j]=0. Nếu k là đỉnh tối ưu thì: với các đỉnh i, nếu D[i] D[i]=D[k]+A[k,i]; đánh dấu đỉnh k: B[k] = 1

Bước 2: Tổ chức dữ liệu và chương trình
Xây dựng mảng khoảng cách D(N+1), mảng đánh dấu B(N) và mảng trước

r.
T(N) với T[i]=j nếu đỉnh nằm trước đỉnh i trên đường đi tối ưu là j, ma trận
khoảng cách A(N,N):
• Khởi tạo:
For i:=1 To N do Begin T[i] := 0; B[i] := 0; End;
For i:=0 To N Do D[i] := VC;
For i:=1 To N Do
For j :=1 To i-1 Do
If A[i,j] = 0 Then Begin A[i,j] = VC; A[j,i] := VC; End;


Lặp:
Repeat
k := 0; For j:=1 to N Do If (B[j]=0)And(D[j]If k>0 Then
Begin For i:=1 To N Do
If D[i] > D[k] + A[k,i] Then
Begin D[i] := D[k]+A[k,i]; T[i] := k; End;
B[k] := 1;
End;
Until (k=0)or(k=Q);
Truy vết:
s.
• Khoảng cách ngắn nhất là D[Q]: nếu D[Q] = VC thì khơng tồn tại đường
đi.
• Đường đi: xây dựng thủ tục đệ quy để lần ngược dựa vào mảng T

Bước 3: Cải tiến
Đổi tiền
Một ngân hàng có N loại tiền đánh số 1 ..N với mệnh giá là A 1, A2,...,AN. Số lượng

tiền mỗi loại không giới hạn. Ngần hàng cần thanh tốn cho khách hàng M đồng.
u cầu: Tìm phương án chi trả sao cho số tờ tiền sử dụng là ít nhất

Dữ liệu vào: file văn bản COIN.INP:
Dòng đầu ghi 2 số N, M. (N<=200,M<=10000)
t.
u. Dòng thứ 2 ghi lần lượt A1, A2, ..., AN

Kết quả: file văn bản COIN.OUT:
Dòng đầu: ghi T là số lượng tờ tiền cần sử dụng
v.
w. T dòng tiếp theo, mỗi dòng ghi 2 số A i và Si là mệnh giá và số lượng tiền
mệnh giá đó
- 16 -


Một số bài tốn Quy hoạch động

Ví dụ:
COIN.INP COIN.OUT
3 12
3
432
43
Giải

Lập hệ thức
x. Gọi D[i] là số tờ tiền ít nhất để trả số tiền i:
• Nếu i = 0 thì D[0] = 0
• Nếu khơng có phương án nào để trả số tiền i thì D[i] = VC; i=1..N

• Nếu sử dụng tiền loại j để trả số tiền i (i>=A[j]) thì: số tờ tiền cần sử dụng
để trả số tiền i là = 1+D[i-A[j]]. Vậy D[i] = Min(1+D[i-A[j]]) với j=1..N,
i>=Aj.

Tổ chức dữ liệu và chương trình
Mảng D(M+1) với D[i] là số tờ tiền ít nhất để trả số tiềni, C(M) với C[i] là
y.
loại tiền cuối cùng đưa vào để trả số tiền i
D[0] := 0; For i:=1 To M Do
Begin D[i] := VC; C[i] := 0; End;
For i:=1 To M Do
For j:=1 To N Do
If i>= A[j] Do If D[i] > 1 + D[i-A[j]] Then
Begin D[i] := 1 + D[i-A[j]]; C[i] := j; End;
Truy vết: số tờ ít nhất là D[i]. Dùng mảng Sl(N+1) để lưu số lượng mỗi
loại tiền sử dụng
For i:=1 To M Do SL[C[i]] := Sl[C[i]] + 1;

Cải tiến
Chia kẹo:
Có N gói kẹo đánh số từ 1 .. N, gói thứ i có số kẹo là A i, i=1..N. N gói kẹo được chia
làm 2 phần khác nhau.
Yêu cầu: tìm cách chia sao cho độ chênh lệch tổng số kẹo giữa hai phần là ít nhất

Dữ liệu vào: file văn bản CANDY.INP:
aa. Dòng đầu ghi N, N<=200
bb. Dòng thứ hai ghi lần lượt A1, A2, ..., AN. Ai<=1000

Kết quả: file văn bản CANDY.OUT:
cc. Dòng đầu ghi D là độ chênh lệch ít nhất

dd. Dịng thứ 2 ghi số hiệu các gói thuộc phần thứ nhất
ee. Dịng thứ 3 ghi số hiệu các gói thuộc phần thứ 2
Ví dụ:
CANDY.INP CANDY.OUT
4
2
3 4 7 12
347
12
Giải

Lập hệ thức
ff. Gọi D[i] là số hiệu nhỏ nhất của gói kẹo cuối cùng để có tổng kẹo của nó
với một số gói kẹo trước đó bằng i. Như vậy:
z.

- 17 -


Một số bài tốn Quy hoạch động





• D[0] = 0
• D[i] = VC nếu i không thể biểu diễn bằng tổng của số kẹo của một số gói
1..N
• D[i] = k nếu i = A[k] + A[D[i-A[k]] + ... + D[0] như vậy D[i-A[k]]gg. Để độ chênh lệch ít nhất thì một phần 1 phải có tổng lệnh với Sum div 2 ít

nhất
Tổ chức dữ liệu và chương trình
hh. Mảng D(N+1) lưu số hiệu gói kẹo nhỏ nhất có thể thành lập tổng bằng i.
Mảng A(N) lưu số kẹo các gói 1..N. Mảng C(N) đánh dấu những gói được
chọn vào phần1. M=Sum div 2
For i:= 1 To N Do D[i] := VC; D[0] := 0; C[i]:=0, i=1,N
For i:=1 To M Do
For j:=1 To N Do If( i>=A[j]) And (D[i-A[j]Begin D[i] := j; Break; End;
ii. Truy viết: tổng phần một gần M nhất.
Tp1 := M; While D[Tp1] =VC Do Tp1 := Tp1 - 1;
Dolech := Sum - 2*Tp1;
While D[Tp1] > 0 Do
Begin C[D[Tp1]]:=1; Tp1 := Tp1 - A[D[Tp1]]; End;
Cải tiến

- 18 -



×