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

THUẬT TOÁN QUY HOẠCH ĐỘNG

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 (174.1 KB, 11 trang )

THUẬT TOÁN QUY HOẠCH ĐỘNG
Nguyễn Hồng Thái
Trường THPT Chuyên Hạ Long, Quảng Ninh
1. Giới thiệu thuật toán quy hoạch động
Đối với nhiều thuật toán chúng ta đã biết nguyên lý “chia để trị” thường đóng vai trò chủ đạo
trong việc thiết kế thuật toán: để giải quyết bài toán lớn, chúng ta chia nó thành nhiều bài toán
con có thể được giải quyết độc lập. Trong thuật toán quy hoạch động, việc thể hiện nguyên lý
này được đẩy đến cực độ, chúng ta giải quyết các bài toán con và lưu trữ những lời giải này
với mục đích sử dụng lại chúng theo một sự phối hợp nào đó để giải quyết các bài toán tổng
quát hơn. Cách tiếp cận này được ứng dụng rộng rãi trong vận trù học.
Thuật ngữ “quy hoạch động” mà chúng ta đề cập ở đây ngụ ý nói đến quá trình đưa bài toán
ban đầu về một dạng nào đó có thể áp dụng phương pháp này để giải. Ý tưởng cơ bản của nó
là giải các bài toán con giống như vậy. Dưới đây chúng ta xem xét một vài bài toán cụ thể để
qua đó giới thiệu phương pháp tiếp cận này.
2. Thuật toán quy hoạch động trên mảng một chiều
Bài toán 1. Cho một dãy gồm n số nguyên: a1, a2, ..., an (1 ≤ n ≤ 105, 0 < ai < 106). Hãy loại
bỏ một số ít nhất các phần tử của dãy số và giữ nguyên thứ tự các phần tử còn lại sao cho dãy
số còn lại là một dãy tăng. Ta gọi dãy số còn lại sau khi đã loại bỏ một số phần tử là dãy con
của dãy đã cho. Như vậy yêu cầu của bài toán cũng đồng nghĩa với việc tìm dãy con tăng dài
nhất, tức là nhiều phần tử nhất.
Phân tích và thiết kế thuật toán
Thuật toán 1:
Một thuật toán đơn giản cho bài toán này là thuật toán duyệt và có đánh giá nhánh cận. Rõ
ràng với bài toán này thì thuật toán trên sẽ không đáp ứng được yêu cầu về mặt thời gian. Vì
vậy ta cần tìm một thuật toán tốt cho bài toán trên.
Ý tưởng của thuật toán này là ta sẽ lần lượt đi giải n bài toán con sau: tìm dãy con tăng dài
nhất của dãy a1, a2, ..., ai với i = 1, 2, ..., n. Rõ ràng bài toán con cuối cùng (ứng với i = n) là
bài toán gốc ban đầu của ta. Mặt khác lời giải của bài toán i sẽ kế thừa lời giải của các bài
toán nhỏ trước đó. Vì vậy ta lưu lời giải của các bài toán vào mảng một chiều best[1..n], ở đó
best[i] là số phần của dãy con tăng dài nhất của dãy a1, a2, ..., ai với ai là phần tử cuối cùng
của dãy con tăng dài nhất nói trên.


Ta có công thức truy hồi tính best[i] như sau:
best[i] = max{best[j] + 1} với mọi j = 1, 2, ..., (i-1) và aj < ai
Nếu các phần tử aj đứng trước ai đều lớn hơn hoặc bằng ai thì công thức trên không xác định
được giá trị cho best[i], trong trường hợp này best[i] = 1.
Nhìn vào công thức truy hồi trên, ta thấy cần phải khởi tạo giá trị best[1] = 1.
Như vậy độ dài của dãy con tăng dài nhất của dãy đã cho sẽ là giá trị lớn nhất của các phần tử
mảng best: max{best[i]} với mọi i = 1, 2, …, n.

1


Công việc cuối cùng là tìm các phần tử của dãy con tăng dài nhất. Để làm việc này ta dùng
một mảng truoc[1..n] với ý nghĩa truoc[i] là chỉ số của phần tử trước phần tử i trong dãy con
tăng dài nhất. Do đó từ mảng truoc ta có thể lấy chỉ số các phần tử thuộc dãy con tăng dài
nhất bằng cách lần ngược từ phần tử cuối về phần tử đầu. Vì vậy để in ra các phần tử của dãy
con tăng dài nhất ta phải dùng thêm một mảng phụ p và in ngược lại các phần tử của mảng p.
Chú ý rằng các phần tử của mảng truoc được khởi tạo bằng 0, do vậy trước của phần tử đầu
tiên trong dãy con tăng dài nhất là 0.
m := 0;
// số phần tử của dãy con tăng dài nhất
i := i_max; // i_max là chỉ số của phần tử lớn nhất mảng best
while i <> 0 do
begin
m := m + 1;
p[m] := a[i];
i := truoc[i];
end;

Khi đó các phần tử của dãy con tăng dài nhất được in ra bằng dòng lệnh:
for i := m downto 1 do writeln(p[i]);


Tuy nhiên nếu tinh ý ta có thể nhận ra tính đệ quy trong việc in kết quả. Do đó thủ tục in ra
dãy con tăng dài nhất rất ngắn gọn và sáng sủa:
procedure print(i : integer);
begin
if i = 0 then exit;
print(truoc[i]);
writeln(f, a[i]);
end;

Công việc in ra chỉ cần một lời gọi: print(i_max);
Độ phức tạp thời gian của thuật toán 1 là O(n2). Chương trình được cài đặt như sau.
program day_con_tang_dai_nhat_1;
const
FI = 'dctdn.in';
FO = 'dctdn.out';
var
n : longint;
a, best, truoc : array[1..100000] of longint;
f : text;
procedure doc;
var
i : longint;
begin
assign(f, FI); reset(f);
readln(f, n);
for i := 1 to n do readln(f, a[i]);
close(f);
end;
procedure xuli;

var
i, j : longint;

2


begin
for i := 1 to n do
begin
best[i] := 1;
truoc[i] := 0;
for j := 1 to (i-1) do
if (a[j] < a[i]) and (best[i] < best[j] + 1) then
begin
best[i] := best[j] + 1;
truoc[i] := j;
end;
end;
end;
procedure print(i : longint);
begin
if i = 0 then exit;
print(truoc[i]);
writeln(f, a[i]);
end;
procedure ghi;
var
i, j : longint;
begin
assign(f, FO); rewrite(f);

i := 1;
for j := 2 to n do
if best[j] > best[i] then i := j;
writeln(f, best[i]);
print(i);
close(f);
end;
begin
doc;
xuli;
ghi;
end.

Thuật toán 2:
Gọi pj là vị trí của phần tử nhỏ nhất để dãy con tăng có độ dài đúng bằng j trong dãy a1, a2, ...,
ai. Ta sẽ sử dụng thuật toán tìm kiếm nhị phân để xây dựng mảng p. Độ phức tạp thời gian của
thuật toán 2 là O(n.log2n).
program day_con_tang_dai_nhat_2;
const
FI = 'dctdn.in';
FO = 'dctdn.out';
var
n, m : longint;
a, p, truoc : array[0..100000] of longint;
f : text;
procedure doc;
var
i : longint;
begin
assign(f, FI); reset(f);

readln(f, n);
for i := 1 to n do readln(f, a[i]);

3


close(f);
end;
procedure xuli;
var
i, j, l, r, c : longint;
begin
a[0] := -MAXLONGINT;
m := 0;
p[0] := 0;
for i := 1 to n do
begin
if a[i] < a[p[1]] then j := 0
else
if a[i] > a[p[m]] then j := m
else
begin
l := 1; r := m;
repeat
c := (l+r) div 2;
if a[p[c]] < a[i] then l := c
else r := c;
until l+1 >= r;
j := l;
end;

truoc[i] := p[j];
if (j = m) or (a[i] < a[p[j+1]]) then
begin
p[j+1] := i;
if j = m then inc(m);
end;
end;

end;

procedure print(i : integer);
begin
if i = 0 then exit;
print(truoc[i]);
writeln(f, a[i]);
end;
procedure ghi;
begin
assign(f, FO); rewrite(f);
writeln(f, m);
print(p[m]);
close(f);
end;
begin
doc;
xuli;
ghi;
end.

3. Thuật toán quy hoạch động trên mảng hai chiều

Chúng ta có thể gặp các bài toán mà mảng quy hoạch động có kích thước lớn hơn: 2, 3, …
chiều, chẳng hạn như bài toán dưới đây.
4


Bài toán 2. Cho 2 dãy số nguyên a1, ..., am và b1, ..., bn. Hãy tìm dãy con chung dài nhất của
cả 2 dãy.
Phân tích và thiết kế thuật toán
Ở đây, bài toán con là tìm dãy con chung dài nhất của 2 dãy nhỏ hơn: dãy gồm i (i = 1, 2, ...,
n) phần tử đầu tiên của dãy thứ nhất và dãy gồm j (j = 1, 2, ..., n) phần tử đầu tiên của dãy thứ
hai. Ta gọi best[i, j] là độ dài của dãy con chung dài nhất này.
Bây giờ ta xét bài toán tìm dãy con chung dài nhất của hai dãy a1, ..., ai và b1, ..., bj với i =
1, ..., m; j = 1, ..., n, giả sử rằng các bài toán với i, j nhỏ hơn đã giải xong. Ta hãy bắt đầu tại
hai phần tử cuối cùng của hai dãy, có hai khả năng xảy ra:
• Trường hợp thứ nhất a[i] = b[j]: Suy ra best[i, j] = best[i-1, j-1] + 1.
• Trường hợp thứ hai a[i] ≠ b[j]: Suy ra best[i, j] = max(best[i, j-1], best[i-1, j]).
Nhìn vào công thức truy hồi trên, ta thấy cần phải khởi tạo hàng 1 và cột 1 của mảng best, tức
là phải giải bài toán khi 1 trong 2 dãy có một phần tử. Thực ra bài toán cơ sở này là dễ, nhưng
để dễ hơn, ta sẽ xét bài toán con của nó là khi có 1 trong 2 dãy là rỗng (i = 0 hoặc j = 0). Lời
giải bài toán này đơn giản là không có dãy con chung nào cả. Vì vậy ta sẽ mở rộng mảng best
ra thêm hàng 0, cột 0 và thay vì phải khởi tạo cho hàng 1, cột 1 ta sẽ khởi tạo cho hàng 0, cột
0:
• best[0, j] = 0 với mọi j = 0, 1, …, n.
• best[i, 0] = 0 với mọi i = 0, 1, …, m.
Khi đó best[m, n] sẽ là độ dài của dãy con chung dài nhất của hai dãy số đã cho. Hơn nữa, dựa
vào mảng best ta có thể đưa ra được một dãy con chung dài nhất.
Thuật toán có độ phức tạp thời gian là O(m×n).
Cài đặt thuật toán
program day_con_chung_dai_nhat;
const

FI = 'dccdn.in';
FO = 'dccdn.out';
var
m, n : integer;
a, b : array[1..5000] of longint;
best : array[0..5000, 0..5000] of integer;
f : text;
procedure doc;
var
i : integer;
begin
assign(f, FI); reset(f);
read(f, m, n);
for i := 1 to m do read(f, a[i]);
for i := 1 to n do read(f, b[i]);
close(f);
end;
Function max(a, b : integer) : integer;
Begin
if a > b then max := a
else max := b;
End;

5


procedure xuly;
var
i, j : integer;
begin

for i := 0 to m do best[i, 0] :=
for j := 0 to n do best[0, j] :=
for i := 1 to m do
for j := 1 to n do
if a[i] <> b[j] then best[i,
else best[i,
end;

0;
0;
j] := max(best[i, j-1], best[i-1, j])
j] := best[i-1, j-1] + 1;

procedure print(i, j : integer);
begin
if best[i, j] = 0 then exit;
if a[i] = b[j] then
begin
print(i-1, j-1);
writeln(f, a[i]);
end
else
if best[i, j] = best[i-1, j] then print(i-1, j)
else print(i, j-1);
end;
procedure ghi;
begin
assign(f, FO); rewrite(f);
writeln(f, best[m, n]);
print(m, n);

close(f);
end;
begin
doc;
xuly;
ghi;
end.

4. Kết luận
Như đã nói ở trên, chìa khóa trong thuật toán quy hoạch động là việc xây dựng các bài toán
con mà ta gọi là mảng quy hoạch động. Mảng này có thể là 1, 2 hoặc có thể nhiều chiều tùy
thuộc vào lời giải của bài toán phụ thuộc vào các loại tham số nào. Tiếp đến là cách quy nạp
thu gọn bài toán sau mỗi bước, tức là không gian bài toán (kích thước dữ liệu) nhỏ lại, cho
đến khi nào ta hoàn toàn có thể giải được bài toán nhỏ (điểm dừng của quy nạp). Bản chất của
công việc này là ta phải xây dựng được lời giải của bài toán qua các bài toán con, tức là lập
được công thức truy hồi, và dựa vào công thức truy hồi, ta sẽ biết được cần phải khởi tạo như
thế nào.
Trong các phần trên, chúng ta đã khảo sát một số bài toán có thể dùng thuật toán quy hoạch
động để giải quyết một cách hiệu quả. Những vấn đề này đều liên quan đến bài toán tìm
phương án tối ưu để thực hiện một công việc nào đó và chúng có chung một tính chất là đáp
án tốt nhất cho một bài toán con vẫn được duy trì khi bài toán con đó trở thành một phần
trong bài toán lớn hơn.

6


Thuật toán quy hoạch động thường được áp dụng để giải các bài toán tối ưu, bài toán đếm, …
Vì vậy, nếu giới hạn kích thước dữ liệu của các bài toán tối ưu lớn và việc sử dụng các thuật
toán khác (như duyệt, nhánh cận, ...) có độ phức tạp thời gian lớn thì chúng ta hãy nghĩ đến
thuật toán quy hoạch động.

5. Bài tập áp dụng
1. Triangle
Hãy xem tam giác số dưới đây. Hãy viết chương trình tính
tổng lớn nhất của các số trên đường đi bắt đầu từ đỉnh và kết
thúc đâu đó ở đáy. Mỗi bước có thể đi chéo xuống phía trái
hoặc đi chéo xuống phía phải.
Trong ví dụ trên, đường đi từ 7 đến 3 đến 8 đến 7 đến 5 sẽ
tạo ra tổng lớn nhất là 30.

8
1 0
2 4 4
4 2 6 5

Dữ liệu: Dòng đầu tiên của file vào ghi số nguyên N (1 ≤ N
≤ 1.000) là số lượng hàng của tam giác; Dòng thứ i trong số N dòng tiếp theo, ghi các số trên
hàng thứ i của tam giác. Các số ghi cách nhau bởi một dấu cách và có giá trị trong đoạn từ 0
đến 100.
Kết quả: Ghi ra file ra tổng lớn nhất của các số trên đường đi theo quy tắc nêu trên.
Ví dụ:
numtri.in
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

numtri.out
30


2. Piggy-Bank
Trước khi nhóm ACM có thể làm một việc gì đó thì cần phải chuẩn bị một ngân sách và sự hỗ
trợ tài chính cần thiết. Nguồn thu nhập chính cho việc này là từ tiền tiết kiệm. Ý tưởng này rất
đơn giản: bất cứ khi nào mỗi thành viên của nhóm ACM có một số tiền nhỏ, họ lấy tất cả các
đồng tiền đó và bỏ chúng vào con lợn đất. Bạn biết rằng, khi bỏ các đồng tiền vào thì không
thể lấy các đồng tiền đó ra nếu không đập vỡ con lợn đất. Sau một khoảng thời gian đủ dài thì
sẽ có đủ số tiền mặt có trong con lợn đất để chi trả mọi thứ cần thiết.
Nhưng có một vấn đề lớn đối với các con lợn đất là không thể xác định có bao nhiêu tiền ở
bên trong. Vì vậy, chúng ta chỉ có thể đập vỡ con lợn thành các mảnh nhỏ thì mới biết là có
bao nhiêu tiền ở bên trong nó. Rõ ràng chúng ta không muốn tình huống này xảy ra. Tuy
nhiên, có một điều mà chúng ta có thể làm được là cân con lợn đất và ước chừng có bao nhiêu
tiền ở trong đó. Giả sử rằng chúng ta xác định được chính xác trọng lượng của con lợn và
chúng ta biết trọng lượng của tất cả các đồng tiền, thì chúng ta có thể chắn chắn có một lượng
tiền tối thiểu ở trong con lợn. Nhiệm vụ của bạn là phải tìm ra trường hợp xấu nhất này và xác
định lượng tiền tối thiểu trong con lợn đất đó. Chúng tôi cần sự giúp đỡ của bạn để không có
con lợn đất nào bị đập vỡ trước thời hạn.

7


Dữ liệu: Dòng đầu tiên của file vào chứa hai số nguyên E và F. Các số này là trọng lượng của
con lợn rỗng và con lợn chứa đầy các đồng tiền. Cả hai trọng lượng là được cho bằng đơn vị
gam. Không có con lợn nào nặng quá 10 kg, điều này có nghĩa là 1 ≤ E ≤ F ≤ 10.000. Dòng
thứ hai ghi một số nguyên N (1 ≤ N ≤ 500) là số các đồng tiền khác nhau có thể được sử dụng.
Tiếp theo có đúng N dòng, mỗi dòng mô tả một loại tiền. Mỗi dòng này chứa hai số nguyên P
và W (1 ≤ P ≤ 50.000, 1 ≤ W ≤ 10.000). P là giá trị một đơn vị tiền tệ của loại đồng tiền này,
W là trọng lượng của nó tính bằng gam.
Kết quả: File ra gồm một dòng ghi đúng một số nguyên là lượng tiền nhỏ nhất có thể có được
khi sử dụng các đồng tiền trên để bỏ vào lợn có trọng lượng ban đầu E (lợn rỗng) để đạt đến

trọng lượng F. Nếu trọng lượng của lợn không thể đạt được đúng bằng F thì ghi ra câu
“impossible”
Ví dụ:
pig.in
10 110
2
1 1
30 50
1 6
2
10 3
20 4

pig.out
60

impossible

3. Little shop of flowers (IOI ’99)
Bạn muốn sắp đặt cửa sổ hiệu bán hoa của mình sao cho hấp dẫn nhất. Bạn có M bó hoa, mỗi
bó một loại khác nhau và số bó hoa không nhiều hơn số bình hoa sắp sẵn thành một hàng trên
cửa sổ. Các bình hoa được gắn cố định và được đánh số liên tục từ 1 đến N, trong đó N là số
bình, theo thứ tự từ trái sang phải sao cho bình 1 là bình bên trái nhất, còn bình N là bình bên
phải nhất. Mỗi bó hoa được gán một số nguyên duy nhất có giá trị trong khoảng giữa 1 và M
và có thể được cắm vào các bình khác nhau. Số hiệu của các bó hoa có ý nghĩa như sau: bó
hoa i nằm bên trái bó hoa j trong dãy các bình hoa nếu i < j. Ví dụ, giả sử ta có một bó hoa Đỗ
Quyên (có số hiệu là 1), một bó hoa Thu Hải Đường (có số hiệu là 2) và một bó hoa Cẩm
Chướng (có số hiệu là 3). Bây giờ tất cả các bó hoa phải được cắm vào dãy các bình hoa bảo
đảm ràng buộc về thứ tự các số hiệu. Bó hoa Đỗ Quyên phải cắm vào bình phía bên trái của
bình hoa Thu Hải Đường. Còn bó hoa Thu Hải Đường phải cắm vào bình phía bên trái của

bình hoa Cẩm Chướng. Nếu số bình hoa nhiều hơn số bó hoa thì những bình không dùng đến
sẽ để trống. Mỗi bình chỉ được cắm 1 bó hoa.
Mỗi bình hoa có đặc điểm khác nhau (cũng như bản thân các bó hoa). Vì vậy, khi một bó hoa
được cắm vào bình sẽ có một giá trị thẩm mỹ nhất định, được biểu diễn bởi một số nguyên.
Các giá trị thẩm mỹ được cho trong bảng dưới đây. Các bình để trống sẽ có giá trị thẩm mỹ
bằng 0.
Bình hoa
Hoa
1
2
3
4
5
1 (Đỗ Quyên)
7
23
-5
-24
16
2 (Thu Hải Đường)
5
21
-4
10
23
3 (Cẩm Chướng)
-21
5
-4
-20

20
Theo bảng trên, hoa Đỗ Quyên sẽ rất hấp dẫn khi được cắm vào bình 2, nhưng sẽ khó coi khi
cắm vào bình 4.
8


Để nhận được hiệu quả thẩm mỹ tốt nhất, bạn phải tìm cách cắm các bó hoa vào bình tuân
theo ràng buộc về thứ tự sao cho tổng các giá trị thẩm mỹ là lớn nhất.
Dữ liệu: Dòng đầu tiên của file vào chứa hai số M, N (1 ≤ M ≤ N ≤ 100). M dòng tiếp theo,
mỗi dòng chứa N số nguyên sao cho Aij là số thứ j trên dòng thứ (i+1), trong đó Aij là giá trị
thẩm mỹ đạt được khi cắm bó hoa i vào bình j (-50 ≤ Aij ≤ 50).
Kết quả: File ra chứa hai dòng, dòng 1 ghi giá trị tổng nhận được theo cách cắm của bạn;
dòng 2 biểu diễn cách cắm như là một danh sách M số, sao cho số thứ k trong dòng này cho
biết bình hoa dùng để cắm bó hoa có số hiệu k. Nếu có nhiều cách cắm cùng cho giá trị tổng
lớn nhất thì đưa ra cách cắm nhỏ nhất theo thứ tự từ điển.
Ví dụ:
flower.in
flower.out
3 5
53
7 23 -5 -24 16
2 4 5
5 21 -4 10 23
-21 5 -4 -20 20
4. Palindrome (IOI2000 - Beijing China)
Xâu đối xứng là xâu mà việc đọc nó từ trái sang phải cũng như từ phải sang trái. Bạn hãy viết
một chương trình, cho trước một xâu và xác định số ký tự nhỏ nhất cần chèn vào xâu để có
được một xâu đối xứng.
Ví dụ, chèn 2 ký tự vào xâu “Ab3bd” sẽ tạo thành một xâu đối xứng (“dAb3bAd” hoặc
“Adb3bdA”). Hơn nữa, việc chèn ít hơn 2 ký tự sẽ không tạo ra một xâu đối xứng.

Dữ liệu: File vào gồm 2 dòng. Dòng đầu tiên chứa một số nguyên N là độ dài của xâu vào (3
≤ N ≤ 5000); Dòng thứ hai chứa một xâu độ dài N. Xâu này chỉ gồm các chữ cái in hoa từ ‘A’
đến ‘Z’, các chữ cái in thường từ ‘a’ đến ‘z’ và các chữ số từ ‘0’ đến ‘9’. Các chữ cái in hoa
và in thường được xem là khác nhau.
Kết quả: File ra gồm một dòng chứa một số nguyên là số ký tự nhỏ nhất cần chèn như mô tả
ở trên.
Ví dụ:
palin.in
5
Ab3bd

palin.out
2

5. Truyền tin
Người ta cần truyền n gói tin được đánh số từ 1 đến n từ một điểm phát đến một điểm thu. Để
thực hiện việc truyền tin có thể sử dụng m đường truyền được đánh số từ 1 đến m. Biết rằng
nếu truyền j gói tin theo đường truyền tin i thì chi phí phải trả là sij (sij là số nguyên dương, sij
≤ 32767; i = 1, 2, ..., m; j = 1, 2, ..., n).
Hãy xác định số lượng gói tin cần truyền theo mỗi đường truyền tin để việc truyền n gói tin
được thực hiện với tổng chi phí phải trả là nhỏ nhất.

9


Dữ liệu: Dòng đầu tiên chứa hai số nguyên dương n và m (n , m ≤ 100); Dòng thứ i trong số
m dòng tiếp theo chứa n số nguyên dương si1 si2 ... sin, i = 1, 2, ..., m.
Kết quả: Dòng đầu tiên chứa S là tổng chi phí phải trả theo cách truyền tin tìm được; Dòng
thứ hai chứa m số nguyên không âm q1 q2 ... qm, trong đó qi là số gói tin cần truyền theo đường
truyền tin i.

Ví dụ:
ttin.in
3 3
20 20 20
4 3 10
1 3 20

ttin.out
4
0 2 1

6. Coin
Giả sử tại một lúc nào đó bạn có N loại tiền kim loại (dạng đồng xu) được đánh số từ 1 đến N,
mỗi loại có số lượng đồng không giới hạn. Mỗi đồng của loại tiền kim loại thứ i mang giá trị
vi xu và có trọng lượng wi gam. Vì các loại tiền được đúc bằng các kim loại khác nhau nên có
thể có những loại tiền cùng giá trị hoặc cùng trọng lượng nhưng không thể đồng thời có cùng
giá trị và trọng lượng.
Trong số N loại tiền của bạn, hãy chọn ra một số ít nhất M các đồng, sao cho chúng có tổng
giá trị là V xu và tổng trọng lượng là W gam. M sẽ nhận giá trị 0 nếu không có cách chọn các
đồng tiền thoả mãn hai giá trị V và W.
Dữ liệu: Dòng đầu tiên chứa 3 số tự nhiên N, V, W (1 ≤ N ≤ 20; 1 ≤ V, W ≤ 150); N dòng tiếp
theo, dòng thứ i chứa cặp số (vi, wi) là thông tin về một đồng của loại tiền thứ i (1 ≤ vi, wi ≤
150).
Kết quả: Nếu không tìm được cách thoả mãn thì chỉ ghi vào file ra số 0. Ngược lại, ghi ra file
ra: Dòng 1: Ghi số M thoả mãn yêu cầu đặt ra; Dòng 2: Ghi N số nguyên không âm, trong đó
số thứ i cho biết số đồng được chọn của loại tiền thứ i. Các số viết cách nhau bởi một dấu
cách. Nếu có nhiều lời giải, hãy đưa ra lời giải nhỏ nhất theo thứ tự từ điển.
Ví dụ:
coin.in
8 141 4

1 1
2 1
4 1
8 1
16 1
32 1
64 1
128 1
4 11 17
12 34 7
8 10
21 9

10

coin.out
4
1 0 1 1 0 0 0 1

0


7. Chiếc túi xách
Một người đi du lịch có n loại đồ vật có trọng lượng và giá trị khác nhau. Nhưng anh ta chỉ có
một túi xách có dung lượng w (có thể chứa được một số đồ vật sao cho tổng trọng lượng của
các đồ vật này nhỏ hơn hoặc đúng bằng w).
Bạn hãy viết chương trình giúp người đi du lịch phải chọn lựa một danh sách các đồ vật mang
đi như thế nào để tổng giá trị đồ vật mang đi là lớn nhất. Giả thiết mỗi loại đồ vật có đủ nhiều.
Dữ liệu: File vào gồm 3 dòng. Dòng đầu tiên của file vào chứa hai số nguyên dương n và w
(n, w ≤ 1000). Dòng thứ hai ghi n số nguyên dương ai (ai < 1000, i = 1, 2, ..., n). Dòng cuối

cùng ghi n số nguyên dương ci (ci < 100.000, i = 1, 2, ..., n). Các số trên một dòng cách nhau
bởi một dấu cách.
Kết quả: File ra gồm 2 dòng. Dòng thứ nhất ghi tổng giá trị đồ vật mang đi lớn nhất. Dòng
thứ hai ghi n số nguyên cách nhau bởi dấu cách, trong đó số thứ i là số lượng đồ vật i cần
mang theo (i = 1, 2, ..., n). Nếu có nhiều cách mang đồ vật đều cho tổng giá trị lớn nhất thì ghi
ra một cách bất kỳ trong chúng.
Ví dụ:
tuixach.in
5 20
1 2 3 4 6
1 2 9 8 16

tuixach.out
56
2 0 6 0 0

11



×