PHƯƠNG PHÁP QUAY LUI VÀ MỘT SỐ BÀI TẬP ÁP DỤNG
I. LÝ THUYẾT
Quay lui (tiếng Anh: backtracking) là một chiến lược tìm kiếm lời giải
cho các bài toán thỏa mãn ràng buộc. Người đầu tiên đề ra thuật ngữ này
(backtrack) là nhà toán học người Mỹ D. H. Lehmer vào những năm 1950.
Các bài toán thỏa mãn ràng buộc là các bài toán có một lời giải đầy đủ,
trong đó thứ tự của các phần tử không quan trọng. Các bài toán này bao gồm
một tập các biến mà mỗi biến cần được gán một giá trị tùy theo các ràng buộc cụ
thể của bài toán. Việc quay lui là để thử tất cả các tổ hợp để tìm được một lời
giải. Thế mạnh của phương pháp này là nhiều cài đặt tránh được việc phải thử
nhiều tổ hợp chưa hoàn chỉnh, và nhờ đó giảm thời gian chạy.
Nội dung chính của phương pháp này là việc xây dựng dần các thành
phần của cấu hình bằng cách thử tất cả các khả năng. Giả thiết cấu hình cần
được tìm được mô tả bằng một bộ giá trị (x
1
,x
2
, ,x
N
).Giả sử đã xác định được
i - 1 thành phần (x
1
,x
2
, ,x
i-1
),bây giờ ta xác định thành phần x
i
bằng cách
duyệt tất cả các khả năng có thể đề cử cho nó (đánh số từ 1 đến n
i
). Với mỗi khả
năng j, kiểm tra xem j có được chấp nhận hay không. Có thể có hai trường hợp
có thể xảy ra:
- Nếu j được chấp nhận thì xác định x
i
theo j, sau đó nếu j = N thì ta
được một cấu hình, trái lại ta tiếp tục tiến hành việc xác định x
i+1
.
- Nếu thử tất cả các khả năng mà mà không có khả năng nào được chấp
nhận thì ta sẽ lùi lại bước trước để xác định x
i-1
.
Thông thường ta phân tích quá trình tìm kiếm thành cây tìm kiếm.
Không gian tìm kiếm càng lớn hay càng nhiều khả năng tìm kiếm thì cây tìm
kiếm càng lớn, càng nhiều nhánh. Vì vậy, hạn chế và bỏ bớt các nhánh vô
nghiệm của cây tìm kiếm thì sẽ tiết kiệm được thời gian và bộ nhớ, tránh bị tràn
dữ liệu. Quá trình tìm kiếm lời giải theo thuật toán quay lui có thể được mô tả
bởi mô hình cây tìm dưới đây:
Cần phải lưu ý là ta phải ghi nhớ tại mỗi bước đã đi qua, những khả năng
nào đã thử để tránh trùng lặp. Những thông tin này được lưu trữ theo kiểu dữ
liệu ngăn xếp - Stack ( vào sau ra trước) - vì thế nên thuật toán này phù hợp thể
hiện bởi thủ tục đệ quy. Ta có thể mô tả bước xác định x
i
bởi thủ tục đệ quy sau:
Procedure Try (i: integer);
Var j : integer;
Begin
For j:= 1 to n
i
do
If <chấp nhận j> then
Begin
<xác định x
i
theo j >
if i = N then <ghi nhận một cấu hình>
else try(i+1);
End;
End;
Trong thủ tục mô tả trên, điều quan trọng nhất là đưa ra được một danh
sách các khả năng đề cử và xác định được giá trị của biểu thức logic <chấp nhận
j>. Ngoài việc phụ thuộc j, giá trị này còn phụ thuộc vào việc đã chọn các khả
năng tại i – 1 bước trước đó. Trong những trường hợp như vậy, cần ghi nhớ
trạng thái mới của quá trìnhsau khi <xác định x
i
theo j> và trả lại trạng thái cũ
sau lời gọi Try(i+1). Các trạng thái này được ghi nhận nhờ một số biến tổng thể
(global), gọi là các biến trạng thái.
Dễ thấy rằng bài toán vô nghiệm khi ta đã duyệt hết mọi khả năng mà
không có khả năng nào thoả mãn yêu cầu. Ta nói rằng là đã vét cạn mọi trường
hợp. Chú ý rằng là đến một lúc nào đó ta phải lùi liên tiếp nhiều lần.Từ đó suy ra
rằng, thông thường bài toán vô nghiệm khi không thể lùi được nữa. Thuật toán
này sẽ không có tính khả thi cao bởi dùng thủ tục đệ quy dễ bị lỗi tràn Stack.
II. BÀI TẬP
1. Bài 1: Xếp hậu
Liệt kê tất cả các cách sắp xếp những con hậu trên bàn cờ NxN sao cho
chúng không ăn được nhau.
Hướng dẫn: Ta xếp n con hậu trên n dòng, Theo nguyên lý nhân ta có n
n
cách sắp xếp thoả mãn điều kiện đầu bài. Để làm điều đó ta dùng thủ tục đệ quy
mô tả ở trên để giải. Ta đánh ghi số cột và dòng của bàn cờ từ 1 đến n, mỗi cách
sắp xếp ứng với 1 bộ gồm a
1
,a
2
, ,a
n
với a
i
= j (j=1,2, ,n) có nghĩa là con hậu
thứ i đặt vào cột j. Giả sử ta chọn được i-1 con hậu bằng cách duyệt tất cả các
khả năng của nó.
Quan trọng nhất là ta tìm điều kiện chấp nhận j, một con hậu đứng ở một
ô trong bàn cờ nó có nhiều nhất bốn hướng đi (đường dọc, đường ngang và hai
đường chéo).
2
Vậy điều kiện chấp nhận thứ i thoả mãn không nằm trên đường đi của tất
cả i-1 con hậu đã xếp. Bởi vì n con hậu xếp ở hàng nên đường đi ngang của
chúng là không chiếu nhau, do đó khi chọn con hậu thứ i chỉ cần kiểm tra xem
trên 2 đường chéo và đường dọc của chúng có chiếu vào những con hậu đã xếp
không? Để kiểm tra điều này mỗi đường ta dùng một biến trạng thái.
* Đường dọc kiểm soát bằng biến b[j],(j=1,2, ,n).
* Một đường chéo kiểm soát bằng biến c[i+j],i+j={2, ,2n}.
* Còn đường chéo kia kiểm soát bằng biến d[i-j],i-j={1-n, ,n-1}.
Các biến trạng thái này khởi gán giá trị True trong thủ tục Init. Như vậy
con hậu thứ i được chấp nhận xếp vào cột j nếu nó thoả mãn cả ba biến
b[j],c[i+j],d[i-j] đều có giá trị True. Các biến này gán giá trị False khi xếp xong
con hậu thứ i, và trả lại giá trị true sau khi gọi Result hay Try(i+1). Ta có
chương trình Pascal sau :
Program XepHau;
Uses crt;
var
n : integer;
a:array[1 30] of integer;
b:array[1 30] of boolean;
c:array[2 60]of boolean;
count,d:word;
Procedure Init;
Var
i:integer;
Begin
Write('Cho do rong ban co n= ');
Readln(n);
Count:=0;
d:=0;
For i:=1 to n do b[i]:=true;
For i:=2 to 2*n do c[i]:=true;
For i:=1-n to n-1 do d[i]:=true;
End;
Procedure Result;
Var
i:integer;
Begin
d:=d+1;
3
count:=count+1;
Write('Cach xep thu',count:5,'.');
for i:=1 to n do write(a[i]:2);
Writeln;
if d = 24 then
begin
readln;
d :
= 0;
end;
end;
Procedure try(i:integer);
Var
j : integer;
Begin
For j:=1 to n do
If (b[j]) and (c[i + j]) and (d[i - j]) then
Begin
a [i] : = j;
b [j] : = false;
c[i + j]: = false;
d [i] : = false;
if i = n then Result
else try (i+1);
b [j] : = true;
c[i + j]: = true;
d[i + j]: = true;
end;
end;
begin
clrscr;
Init;
Try(1);
Write ('An Enter de ket thuc:');
Readln;
End.
2. Bài 2 Hành trình ký tự
4
Cho tệp văn bản HT_KITU.INP chứa các dòng ký tự chiều dài không
quá 32. Hãy lập trình thực hiện các công việc sau: Lần lượt đọc các dòng vào
một xâu, sau đó từ xâu xây dựng lưới ô vuông dạng tam giác như sau: ví dụ xâu
=’Vinh’, lưới ô vuông có dạng như hình 1. Xuất phát từ ô góc trên trái (chữ V),
đi theo các hướng có thể để xây dựng lại xâu đã cho. Với mỗi hành trình thành
công hãy in ra số thứ tự của hành trình và chỉ ra hành trình trên lưới, mỗi ký tự
của hành trình thay bằng một dấu ’*’.
Ví dụ:
Sau mỗi lời giải phải ấn ENTER để in lời giải tiếp.
Hướng dẫn giải
Tổ chức hai mảng hai chiều F, Kt[1 32,1 32] of Char. Mảng Kt dùng để
tạo ra ma trận kí tự dạng tam giác như trên gồm các kí tự từ xâu S đọc từ file dữ
liệu. Mảng F dùng để ghi nhận các hành trình thành công, nếu ô (i,j) thuộc hành
trình thì F[i,j] = ’*’.
Sau khi xây dựng xong ma trận kí tự, ta dùng thủ tục đệ quy Try(i,j,h:
byte) để tìm tất cả các hành trình. Giả sử ta đang ở ô (i,j) nào đó trên hành trình
và đã được một xâu kí tự độ dài h ≤ length(S ). Nếu h = length(S )thì ta đã được
một hành trình và ta sẽ ghi nhận nó, in ra màn hình hành trình đó. Còn nếu h <
length(S ) thì từ ô (i,j) ta sẽ có thể đi theo hai hướng đó là đến ô (i,j+1) hoặclà ô
(i+1,j). Từ mỗi ô đó ta lại tiếp tục đến các ô khác để tìm hành trình. Quá trình đó
được tiếp tục thực hiện các ô đó cho đến khi duyệt được hết nghiệm của bài
toán.
Ban đầu ta xuất phát tại ô (1,1) và độ dài của xâu ta đang có là 1 nên ở
chương trình chính ta gọi thủ tục Try(1,1,1). Để ý rằng nếu độ dài xâu S là L thì
ta sẽ có tất cả 2
L-1
hành trình.
Văn bản chương trình
Program Hanh_trinh_ki_tu;
Uses Crt;
Const D : Array[1 2] of shortint= (0,1);
C : Array[1 2] of shortint= (1,0);
Fi = ’HT_KITU.INP’;
Var Kt,F : Array[1 32,1 32] of Char;
S : string;
t : word;
dem : longint;
5
Procedure Init;
Var k,i,j : byte;
G : Text;
Begin
Assign(G,Fi); Reset(G);
Read(G,S); t:= length(S);
Fillchar(F,sizeof(F),’ ’);
F[1,1]:=’*’; k:= 0;
For i:= 1 to t do
begin
For j:=1 to t do
begin
Kt[i,j]:= S[j+k];
If Kt[i,j] = #0 then Kt[i,j]:= ’ ’;
end;
inc(k);
end;
Close(G);
End;
Procedure Write_Out;
Var i,j : Byte;
Begin
Inc(dem);
TextColor(Red); Writeln(’Hanh trinh thu:’,dem);
For i:=1 to t do
begin
For j:=1 to t do
If F[i,j]=’*’ then
begin
TextColor(White); Write(’* ’)
end
Else
begin
TextColor(Green);Write(Kt[i,j],’ ’);
end;
Writeln;
end;
Readln;
End;
Procedure Try(i,j,h: byte);
Var k,x,y: byte;
Begin
6
If h = t then Write_Out else
begin
For k:=1 to 2 do
begin
x:= i + D[k]; y:= j + C[k];
F[x,y]:=’*’;
Try(x,y,h+1);
F[x,y]:=’ ’;
end;
end;
End;
BEGIN
Clrscr;
Init;
Try(1,1,1);
END.
3. Bài 3: Biểu thức zero
Cho một số tự nhiên N ≤ 9. Giữa các số từ 1 đến N hãy thêm vào các dấu
+ và - sao cho kết quả thu được bằng 0. Hãy viết chương trình tìm tất cả các khả
năng có thể.
Dữ liệu vào: Lấy từ file văn bản ZERO.INP với một dòng ghi số N.
Dữ liệu ra: Ghi vào file văn bản có tên ZERO.OUT có cấu trúc như sau:
- Dòng đầu ghi số lượng kết quả tìm được.
- Các dòng sau mỗi dòng ghi một kết quả tìm được.
Ví dụ
Hướng dẫn giải
Áp dụng thuật toán đệ quy quay lui để giải quyết bài toán này, ta sẽ dùng
thủ tục đệ quy Try(i). Giả sử ta đã điền các dấu’+’ và ’-’ vào các số từ 1 đến i,
bây giờ cần điền các dấu giữa i và i + 1. Ta có thể chọn một trong ba khả năng:
hoặc là điền dấu ’+’, hoặc là điền dấu ’-’, hoặc là không điền dấu nào cả. Khi đã
chọn một trong ba khả năng trên, ta tiếp tục lựa chọn dấu để điền vào giữa i + 1
và i + 2 bằng cách gọi đệ quy Try(i+1). Ta sẽ lần lượt duyệt tất cả các khả năng
7
đó để tìm tất cả các nghiệm của bài toán, như vậy bài toán sẽ không bị thiếu
nghiệm.
Nếu i = N ta sẽ kiểm tra xem cách điền đó có thoả mãn kết quả bằng 0
hay không. Để kiểm tra ta dùng thủ tục Test trong chương trình. Nếu tổng đúng
bằng 0 thì cách điền đó là một nghiệm của bài toán, ta ghi nhận nó. Nếu i < N thì
tiếp tục gọi Try(i+1). Trong chương trình ta dùng biến dem để đếm các cách
điền thoả mãn, còn mảng M kiểu string sẽ ghi nhận mọi cách điền dấu thoả mãn
yêu cầu bài toán.
Văn bản chương trình
Program Zero_sum;
Type MangStr = array[1 15] of string;
Const Fi =’ZERO.INP’;
Fo =’ZERO.OUT’;
Dau : array[1 3] of string[1] = (’-’,’+’,’’);
S : array[1 9] of char =(’1’,’2’,’3’,’4’,’5’,’6’,’7’,’8’,’9’);
ChuSo = [’1’ ’9’];
Var N,k,dem: byte;
D : array[2 9] of string[1];
F : Text;
St : String;
M : MangStr;
Procedure Write_out;
Var i : byte;
Begin
Assign(F,Fo); Rewrite(F);
Writeln(F,dem);
For i:= 1 to dem do writeln(F,M[i],’ = 0’);
Close(F); Halt;
End;
Procedure Read_inp;
Begin
Assign(F,Fi); Reset(F);
Read(F,N); Close(F);
If N < 3 then write_out;
End;
Function DocSo(S : String): longint;
Var M : longint;
t : byte;
Begin
M:= 0; t:= 0;
8
If S[k] in [’+’,’-’] then
begin
t:= k; Inc(k);
end;
While (k<= length(S)) and (s[k] in ChuSo) do
begin
m:= m*10 + ord(s[k]) - ord(’0’);
Inc(k);
end;
If (t <> 0) and (S[t] = ’-’) then DocSo:= -M
else DocSo:= M;
End;
Procedure Test;
Var St : string;
i : byte;
T : longint;
Begin
St:= ’1’; k:= 1; T:= 0;
For i:= 2 to N do St:= St + D[i] + S[i];
While k < length(St) + 1 do T:= T + DocSo(St);
If T = 0 then
begin
Inc(dem); M[dem]:= St;
end;
End;
Procedure Try(i: byte);
Var j : byte;
Begin
For j:= 1 to 3 do
begin
D[i]:= Dau[j];
If i = N then Test else try(i+1);
end;
End;
BEGIN
Read_inp;
Try(2);
Write_out;
END.
4. Bài 4: Xổ số điện toán
9
Có N người (đánh số từ 1 đến N) tham gia một đợt xổ số điện toán. Mỗi
người nhận được một thẻ gồm M ô (đánh số từ 1 đến M). Người chơi được chọn
K ô trong số các ô đã cho bằng cách đánh dấu các ô được chọn. Sau đó các thẻ
này được đưa vào máy tính để xử lý.
Máy tính chọn ra K ô ngẫu nhiên (gọi là các ô kết quả) và chấm điểm
từng thẻ dựa vào kết quả đã sinh. Cứ mỗi ô chọn đúng với ô kết quả thì thẻ chơi
được tính 1 điểm. Giả thiết biết các ô chọn cũng như các điểm tương ứng của
từng thẻ chơi, hãy xác định tất cả các kết quả có thể có mà máy sinh ra.
Dữ liệu vào đọc từ file vănbản XOSO.INP gồm:
- Dòng đầu ghi các số N, M, K
- Dòng thứ i trong N dòng tiếp ghi thẻ chơi của người i gồm K+1 số: K
số đầu là các số hiệu của các ô chọn, cuối cùng là điểm tương ứng.
Ghi kết quả ra file văn bản XOSO.OUT, mỗi dòng là một kết quả gồm K
số ghi số hiệu các ô mà máy đã sinh.
Ghi chú:
- Các số trên cùng một dòng trong các file vào/ ra, được ghi cách nhau ít
nhất một dấu trắng.
- Giới hạn kích thước:N ≤ 100, M ≤50, K ≤10.
- Dữ liệu vào trong cáctest là hợp lệ và đảm bảo có ít nhất một đáp án.
Ví dụ:
Hướng dẫn giải
Ta nhận thấy rằng mỗi nghiệm của bài toán chính là một cấu hình của tổ
hợp chập K của M phần tử. Ta áp dụng thuật toán quay lui để duyệt mọi cấu
hình tổ hợp để tìm ra cấu hình thoả mãn. Tuy nhiên để giảm bớt số lần duyệt ta
cần phải loại những thẻ mà chúng có tổng điểm bằng 0 và cần đánh dấu những
thẻ đã được chọn.
Dùng mảng ok[0 51] of boolean để phân biệt giữa ô có điểm và những ô
không có điểm. Nếu ok[i]= false thì cho biết thẻ thứ i không có điểm. Còn
logic[i,j] = true cho ta biết người thứ i đánh dấu vào thứ j của thẻ.
Văn bản chương trình
Program Xoso_dien_toan;
Type MangA = array[0 100,0 11] of byte;
10
MangBool = array[0 51] of boolean;
MangLogic = array[0 101,0 51] of boolean;
Cauhinh = array[0 11] of byte;
Const Fi = ’XOSO.INP’;
Fo = ’XOSO.OUT’;
var M,N,K : byte;
A : MangA;
B : Cauhinh;
Ok : MangBool;
Diem : integer;
Logic : MangLogic;
F : Text;
Procedure Init;
Begin
Fillchar(A,sizeof(A),0);
Fillchar(B,sizeof(B),0);
Fillchar(ok,sizeof(ok),1);
Fillchar(logic,sizeof(logic),0);
End;
Procedure Read_inp;
Var i,j : byte;
Begin
Assign(F,Fi); Reset(F);
Readln(F,N,M,K);
For i:= 1 to N do
begin
For j:= 1 to k do
begin
Read(f,A[i,j]); Logic[i,A[i,j]]:= true;
end;
Read(F,A[i,k+1]); Inc(diem,A[i,k+1]);
If A[i,k+1] = 0 then
For j:= 1 to k do ok[A[i,j]]:= false;
end;
Close(F);
End;
Function Chapnhan(j: byte): boolean;
Var v : byte;
Begin
Chapnhan:= false;
For v:= 1 to n do
11
If (A[v,K+1] = 0) and logic[v,j] then exit;
Chapnhan:=true;
End;
Procedure Rutgon(j: byte);
Var i : byte;
Begin
For i:= 1 to N do
If logic[i,j] then
begin
Dec(A[i,k+1]);Dec(diem);
end;
End;
Procedure Morong(j: byte);
Var i : byte;
Begin
For i:= 1 to N do
If logic[i,j] then
begin
Inc(A[i,k+1]); Inc(diem);
end;
End;
Procedure Write_out;
Var d: byte;
Begin
For d:= 1 to K do write(f,B[d],’ ’); Writeln(F);
End;
Procedure Try(i:byte);
Var j: byte;
Begin
For j:= B[i-1] + 1 to M - K + i do
If ok[j] and chapnhan(j) then
begin
B[i]:= j;
Ok[j]:= false;
Rutgon(j);
If (diem = 0) and (i = k) then write_out
else if i < k then try(i+1);
ok[j]:= true;
Morong(j);
end;
End;
12
Procedure Run;
Begin
Assign(F,Fo); Rewrite(F);
Try(1);
Close(f);
End;
BEGIN
Init;
Read_inp;
Run;
End.
5. Bài 5: Bộ bài Domino với bản đồ số
Bộ bài domino gồm 28 quân đánh số từ 1 đến 28. Mỗi quân bài là một
thanh hình chữ nhật được chia làm hai hình vuông bằng nhau. Trong đó người ta
ghi các số từ 0 (để trống) đến 6 bằng cách trổ các dấu tròn trắng. Dưới đây liệt
kê 28 quân bài domino:
Sắp xếp 28 quân bài domino ta có thể tạo ra một hìmh chữ nhật kích
thước 7*8 ô vuông. Mỗi cách sắp xếp như vậy sẽ tạo ra một bản đồ số. Ngược
lại, mỗi bản đồ số có thể tương ứng với một số cách xếp.
Ví dụ bản đồ số:
4 2 5 2 6 3 5 4
5 0 4 3 1 4 1 1
1 2 3 0 2 2 2 2
1 4 0 1 3 5 6 5
4 0 6 0 3 6 6 5
4 0 1 6 4 0 3 0
6 5 3 6 2 1 5 3
13
tương ứng với hai cách xếp mô tả bởi hai bảng số sau:
16 16 24 18 18 20 12 11
06 06 24 10 10 20 12 11
08 15 15 03 03 17 14 14
08 05 05 02 19 17 28 26
23 01 13 02 19 07 28 26
23 01 13 25 25 07 21 04
27 27 22 22 09 09 21 04
16 16 24 18 18 20 12 11
06 06 24 10 10 20 12 11
08 15 15 03 03 17 14 14
08 05 05 02 19 17 28 26
23 01 13 02 19 07 28 26
23 01 13 25 25 07 21 04
27 27 22 22 09 09 21 04
Bài toán đặt ra là cho trước một bảnng số, hãy liệt kê tất cả các cách xếp
có thể tạo ra từ nó.
Dữ liệu vào từ file DOMINO.INP là ma trận 7*8 mô tả bản đồ số ban đầu.
Kết quả ghi ra file DOMINO.OUT dòng đầu là số lượng p cách xếp tìm
được. Tiếp theo là p nhóm dòng, mỗi nhóm gồm 7 dòng ghi các dòng của các
bảng tương ứng với một bảng số tìm được.
Hướng dẫn giải
Với mỗi quân bài domino ta có thể có hai khả năng xếp vào hình chữ
nhật: hoặc là đặt nằm ngang, hoặc là đặt nằm dọc. Ta sẽ thử tất cả các cách để
đặt chúng vào hình chữ nhật cho đến khi nào đặt được cả 28 quân bài vào hình
chữ nhật thì đó là một trong các cách xếp thoả mãn. Mỗi cách xếp thoả mãn sẽ
được lưu vào mảng L[1 10,1 7,1 8].
Văn bản chương trình
Program Bo_bai_domino_voi_cac_ban_do_so;
Type Bandoso = array[1 7,1 8] of byte;
Sothutu = array[0 6,0 6] of byte;
Cauhinh = array[1 10,1 7,1 8] of byte;
Const Fi = ’DOMINO.INP’;
Fo = ’DOMINO.OUT’;
D : array[1 2] of byte = (0,1);
C : array[1 2] of byte = (1,0);
Var A,B : Bandoso;
L : Cauhinh;
Gt : Sothutu;
14
TS : set of byte;
T,dem: byte;
F : Text;
Procedure Read_inp;
Var i,j,k : byte;
Begin
Assign(F,Fi); Reset(F); dem:= 0;
For i:= 1 to 7 do
begin
For j:= 1 to 8 do read(F,A[i,j]);
Readln(F);
end;
For i:= 0 to 6 do
For j:= i to 6 do
begin
Inc(k); Gt[i,j]:= k; Gt[j,i]:= k;
end;
Close(F);
End;
Function Sott(x: byte) : String;
Var S : String;
Begin
Str(X,S);
If length(S) = 1 then S:= ’0’ + S;
Sott:= S;
End;
Procedure Result;
Var i,j : byte;
Begin
Inc(dem);
For i:= 1 to 7 do
For j:= 1 to 8 do L[dem][i,j]:= B[i,j];
End;
Procedure Try(i,j : byte);
Var k,u,v,x : byte;
Begin
While (j < 8) and (B[i,j] > 0) do inc(j);
If (j = 8) and (B[i,j] > 0) then Try(i+1,1) else
For k:= 1 to 2 do
begin
u:= i + D[k]; v:= j + C[k];
15
If (u in [1 7]) and (v in [1 8]) and (B[u,v] = 0) then
begin
x:= Gt[A[i,j],A[u,v]];
If not (x in Ts) then
begin
Inc(t); Ts:= Ts + [x];
B[i,j]:= x; B[u,v]:= x;
If t = 28 then Result else
If v = 8 then Try(i+1,1) else Try(i,v+1);
Dec(t); Ts:= Ts - [x];
B[i,j]:= 0; B[u,v]:= 0;
end
end;
end;
End;
Procedure Write_out;
Var k,i,j : byte;
Begin
Assign(F,Fo); Rewrite(F);
Writeln(dem);
For k:= 1 to dem do
begin
For i:= 1 to 7 do
begin
For j:= 1 to 8 do write(F,Sott(L[dem][i,j]),’ ’);
Writeln(F);
end;
Writeln(F); Writeln(F);
end;
Close(F);
End;
BEGIN
Read_inp;
Try(1,1);
Write_out;
END.
Bài 5: Robot quét vôi
Có 9 căn phòng (đánh số từ 1 đến 9) đã được quét vôi với mầu trắng, xanh
hoặc vàng. Có 9 rôbôt (đánh số từ 1 đến 9) phụ trách việc quét vôi các phòng.
Mỗi rôbôt chỉ quét vôi một số phòng nhất định. Việc quét vôi được thực hiện
nhờ một chương trình cài sẵn theo qui tắc:
- Nếu phòng đang có mầu trắng thì quét mầu xanh
16
- Nếu phòng đang có mầu xanh thì quét mầu vàng
- Nếu phòng đang có mầu vàng thì quét mầu trắng.
Cần phải gọi lần lượt một số các rôbôt ra quét vôi (mỗi lần một rôbôt, một
rôbôt có thể gọi nhiều lần và có thể có rôbôt không được gọi. Rôbôt được gọi sẽ
quét vôi tất cả các phòng mà nó phụ trách) để cuối cùng các phòng đều có mầu
trắng. Hãy tìm một phương án như vậy sao cho lượng vôi phải quét là ít nhất.
Giả thiết rằng luợng vôi cho mỗi lượt quét đối với các phòng là như nhau.
Dữ liệu: đọc từ file văn bản ROBOT.INP gồm các dòng:
- 9 dòng đầu, mỗi dòng mô tả danh sách các phòng được quét vôi bởi một
rôbôt theo thứ tự từ rôbôt 1 đến rôbôt 9. Mỗi dòng như vậy gồm các số hiệu
phòng viết sát nhau. Chẳng hạn dòng thứ 3 có nội dung: 2356 mô tả rôbôt 3 phụ
trách việc quét vôi các phòng 2, 3, 5, 6.
- Dòng cuối mô tả mầu vôi ban đầu của các phòng. Dòng gồm 9 ký tự viết
sát nhau, ký tự thứ i biểu diễn mầu vôi của phòng i với quy ước: ký tự T chỉ mầu
trắng, ký tự X chỉ mầu xanh, ký tự V chỉ mầu vàng.
Kết quả: đưa ra file văn bản ROBOT.OUT gồm một dòng dưới dạng:
- Nếu không có phương án thì ghi một chữ số 0,
- Trái lại ghi dãy thứ tự các rôbôt được gọi (các số hiệu rôbôt viết sát
nhau).
Ví dụ
Hướngdẫn giải
Ta sẽ giải bài toán bằng cách duyệt theo cây tìm kếm. Với mỗi con robot
ta có thể không gọi hoặc sẽ gọi tối đa là hai lần, do đó là sẽ có ba cách lựa chọn.
Ta sẽ lần lượt duyệt các danh sách để gọi các con robot. Vì có tất cả 9 danh sách
nên ta phải duyệt tối đa là 3
9
cách gọi. Do bài toán đòi hỏi là lượng vôi ít nhất
nên ta sẽ tìm cách gọi nào là tối ưu nhất. Để giảm bớt số lần duyệt ta có thể dùng
thêm cận để kiểm tra điều kiện có thực hiện tiếp hay không. Nếu ở bước thứ i ta
cần gọi là S robot là số lần gọi tối ưu lúc đó là Min thì nếu S > min thì ta có thể
17
nhánh này của cây và quay lại bước thứ i − 1, nếu S < min thì ta có thể tiếp tục
duyệt.
Bài 6: Nhà du hành vũ trụ
Một nhà du hành vũ trụ bị lạc vào một hành tinh được thống trị bởi các
con robot. Giả sử hành tinh được chia thành một mảng hình chữ nhật các ô
vuông kích thước M X N ( M,N ≤ 8).
Tất cả mọi con robot đều tìm cách tiến lại nhà du hành để tiêu diệt, còn
nhà du hành thì tìm cách tránh xa các con robot. Cứ sau một dơn vị thời gian
nhà du hành có thể đi theo 4 hướng Đông, Tây, Nam, Bắc hoặc đứng yên. Tất cả
các con robot được lập trình đi theo 1 trong 8 hướng kể cả đường chéo sao cho
khoảng cách từ con robot đến nhà du hành là nhỏ nhất (khoảng cách này không
lớn hơn khoảng cách ở thời điểm trước) trong đó đường chéo được tính theo
công thức hình học thông thường. Các con robot luôn chuyển động theo nhịp
thời gian.
Nếu hai con robot cùng đến một ô thì cả hai con robot này bị nổ tung và
để lại trên ô đó một nhiệt độ huỷ diệt được duy trì mãi và nhiệt độ này đủ để phá
huỷ các con robot hoặc nhà du hành chẳng may đặt chân lên ô đó. Ô này trở
thành nguy hiểm.
Tất cả các con robot không được lập trình để tránh các ô nguy hiểm hoặc
tránh đụng độ nhau mà chỉ được lập trình để gần nhà du hành một cách máy
móc.
Khi một hoặc nhiều robot cùng du hành ở cùng một ô vuông thì nhà du hành bị
tiêu diệt. Nhà du hành không thể đi tới một ô đang có robot.
Bài toán đặt ra:cho bản đồ của hành tinh (có vị trí của nhà du hành và các
con robot), hãy tìm cách di chuyển nhà du hành theo một lịch trình sao cho nhà
du hành có thể tồn tại lâu nhất trên hành tinh (thông báo trường hợp nhà du hành
tồn tại mãi mãi trên hành tinh).
Dữ liệu vào có trong file văn bản DUHANH.INP có cấu trúc:
- Dòng đầu tiên chứa hai số M và N,
- Trong M dòng tiếp theo chứa bản đồ của hành tinh: mỗi dòng chứa một
xâu văn bản (đặt ngay từ đầu dòng) gồm các kí tự ’0’, ’1’, ’2’ với ý nghĩa sau:
’0’: ô rỗng ’1’: ô có một robot ’2’: ô có nhà du hành.
Kết quả được in ra màn hình và ghi vào văn bản DUHANH.OUT có nội
dung như sau:
- Dòng đầu ghi số đơn vị thời gian nhà du hành tồn tại trên hành tinh (quy
ước ghi −1 nếu anh ta tồn tại mãi mãi).
- ứng với mỗi thời điểm có M dòng trên file output bản đồ hành tinh trong
lịch trình, mỗi thời điểm cách nhau một dòng trống.
Ví dụ: Phần đầu của một lịch trình được thể hiện như sau:
18
Ví dụ:
Hướng dẫn giải
Sau mỗi bước hoặc robot gần người thêm một dơn vị khoảng cách hoặc
không gian bị hạn chế thêm một đơn vị độ dài cả về chiều rộng lẫn chiều dài, do
đó ta đi đến khẳng định: nếu người tồn tại sau K = Min{M,N} bước thì người
tồn tại mãi mãi hay nếu người bị robot ăn thịt thì không thể tồn tại sau K bước
di chuyển.
19
Với khẳng định trên đây lặp không quá K bước trong đó mỗi bước lặp liên
quan đến việc thực hiện 5 cách đi trong bước. Như vậy tổng số bước không vượt
quá 5
K
thuật toán.
Để lưu trữ về tình trạng bản đồ của hành tinh ta dùng mảng
A[1 K,1 M,1 N]. Sau mỗi bước thử cách đi cho nhà du hành ta sẽ sử dụng một
thủ tục để cho các con robot di chuyển tiến lại gần nhà du hành.
20
Lớp các bài toán tối ưu tổ hợp rất rộng. Phần lớn các bài toán đó trong
trường hợp tổng quát chỉ có thuật toán tối ưu duy nhất là vét cạn. Tuy nhiên,
nhược điểm của phương pháp vét cạn là độ phức tạp tính toán rất lớn do hiện
tượng bùng nổ tổ hợp.
Có 2 giải pháp khắc phục vấn đề này. Trong đó có phương pháp vét cạn
bằng kỹ thuật nhánh cận, tức là loại bỏ ngay các hướng đi chắc chắn không dẫn
đến phương án tối ưu. Đó cũng là vấn đề hay của phương pháp này và tôi sẽ giới
thiệu cho các bạn kỹ thuật này trong thời gian tới.
21