Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 34

THUẬT TOÁN LOANG

-----------------

I) ĐẶT VẤN ĐỀ:


Thuật toán Loang thực chất là thuật toán tìm kiếm theo chiều rộng trên đồ thị (Breadth First
Search). Để hiểu rõ bản chất của thuật toán này, ta xét bài toán ‘Thăm các đỉnh của một đồ thị’
như sau: Cho một đồ thị vô hướng G = (V,E), N đỉnh và M cạnh (số hiệu của các đỉnh là 1,2,
…,N). Bây giờ ta đưa ra thứ tự duyệt các đỉnh của đồ thị đã cho theo thuật toán tìm kiếm theo
chiều rộng; thứ tự duyệt có thể bắt đầu từ một đỉnh v nào đó. Tư tưởng của thuật toán là sử
dụng cấu trúc dữ liệu kiểu hàng đợi (QUEUE - vào trước ra trước). Phần tử được nạp vào đầu
tiên của QUEUE là đỉnh v. Sau đó cứ mỗi đỉnh p lấy ra khỏi QUEUE là ta thăm đỉnh đó đồng
thời nạp vào QUEUE những đỉnh chung cạnh với p (chỉ nạp vào những đỉnh chưa xét đến).
Quá trình trên được lặp đi lặp lại cho đến khi nào QUEUE rỗng thì dừng.

 Chương trình mô phỏng:


Ban đầu tất cả các đỉnh i (i = 1..n) đều đặt cờ chuaxet[i] = True. Nếu đỉnh nào xét rồi ta đặt cờ
của đỉnh đó sang trạng thái False.
Procedure BFS(v); Tìm kiếm theo chiều rộng bắt đầu từ đỉnh v
Begin
QUEUE = ; {Khởi tạo QUEUE ban đầu là rỗng}
QUEUE <= v; {Nạp đỉnh v vào QUEUE}
Chuaxet[v]:=False;{Đỉnh v nạp vào QUEUE là đã xét rồi => cờ của v là False}
While QUEUE ≠ do
Begin
P <= QUEUE; {Lấy p từ QUEUE}
Thăm đỉnh p;
For u € Ke(p) do {Những đỉnh u chung cạnh với đỉnh p}
If Chuaxet(u) then {Nếu đỉnh u chưa xét đến}
Begin
QUEUE <= u; {Nạp u vào QUEUE}
Chuaxet[u]:=False; {Đỉnh u đã xét rồi =>cờ của u là False }
End;
End;
End;
BEGIN {Chương trình chính}
For v € V do Chuaxet[v]:=True;
For v € V do
If Chuaxet[v] then BFS(v);
END.
Người ta thường dùng dữ liệu kiểu mảng để biểu diễn cấu trúc dữ liệu kiểu hàng đợi QUEUE
và sử dụng 2 biến Dau và Cuoi để điều khiển việc nạp vào và lấy phần tử ra (biến Dau điều
khiển thao tác lấy ra, biến Cuoi điều khiển thao tác nạp vào).
Với bài toán trên ta sử dụng mảng 1 chiều Q: Array[1..N] of Byte để biểu diễn QUEUE. Khi
đó thao tác nạp vào và lấy ra được thực hiện như sau:
FillChar(Q,SizeOf(Q),0); {Khởi tạo tất cả các phần tử của Q có giá trị 0}
Dau:=1;
Cuoi:=1;
Q[cuoi]:=v; {Ban đầu nạp đỉnh v vào Q}
Để nạp thêm đỉnh u nào đó vào Q ta thực hiện:
Cuoi:=Cuoi+1; {Hoặc dùng lệnh Inc(Cuoi)}

1
Q[Cuoi]:=u;
Để lấy một đỉnh p nào đó ra khỏi Q ta thực hiện:
P:=Q[Dau];
Inc(Dau);

Lưu ý: Ta nói lấy đỉnh p ra khỏi hàng đợi Q là lấy ra theo cơ chế điều khiển ( vì biến Dau đã
tăng lên một đơn vị qua lệnh Inc(Dau)); về mặt vật lý thì p vẫn đang nằm trong mảng Q. Như
vậy ta phải hiểu rằng các phần tử trong cấu trúc hàng đợi Q là các phần tử
Q[Dau],..,Q[Cuoi].

 Chương trình đầy đủ:


Program Thamdinhdothi;
Uses Crt;
Const
Nmax=253;
fi='TKR_DT.INP';
Var
f:Text;
s:Char;
A:Array[1..Nmax,1..Nmax] of 0..1;{Nếu có cạnh giữa đỉnh i và đỉnh j thì A[i,j]=1, ngược lại A[i,j]=0 }
Chuaxet:Array[1..Nmax] of Boolean;{Cờ của các đỉnh, có trạng thái True nếu chưa xét, ngược lại False}
Q:Array[1..Nmax] of Byte;{Biểu diễn hàng đợi QUEUE}
N,i,dem,dau,cuoi:Byte;
Procedure Doctep;
Begin
Assign(f,fi);
Reset(f);
Readln(f,N); {N:Số đỉnh của đồ thị}
For i:=1 to N-1 do
Begin
dem:=0;
While not eoln(f) do
Begin
Read(f,s);
dem:=dem+1;
If s='1' then
Begin
A[i,dem+i]:=1;
A[dem+i,i]:=1;
End;
If s='0' then
Begin
A[i,dem+i]:=0;
A[dem+i,i]:=0;
End;
End;
Readln(f);
End;
Close(f);
End;
Procedure BFS(v:Integer);{Tìm kiếm theo chiều rộng bắt đầu từ đỉnh v}
2
Var
p,u:Byte;
Begin
FillChar(Q,Sizeof(Q),0); {Khởi tạo tất cả các phần tử của mảng Q đều bằng 0}
dau:=1;
cuoi:=1;
Q[cuoi]:=v; {Nạp v vao Q}
Chuaxet[v]:=False;
While dau<=cuoi do {dau > cuối là Q rỗng }
Begin
p:=Q[dau];
dau:=dau+1; {Lấy đỉnh p ra khỏi Q }
If (dau-1) mod 14 = 0 then Writeln(p:4) {In ra số hiệu đỉnh p - thao tác thăm đỉnh p}
Else Write(p:4); {trên màn hình xuất hiện mỗi dòng không quá 14 số}
For u:=1 to N do
If (A[p,u]=1) and (Chuaxet[u]) then
Begin
cuoi:=cuoi+1;
Q[cuoi]:=u;
Chuaxet[u]:=False;
End;
End;
End;
BEGIN
Clrscr;
FillChar(A,Sizeof(A),0); {Khởi tạo tất cả các phần tử của mảng A đều bằng 0}
Doctep;
FillChar(Chuaxet,Sizeof(Chuaxet),True); {Khởi tạo cờ của tất cả các đỉnh đều ở trạng thái True - Trạng thái
chưa xét}
Writeln('Thu tu tham cac dinh cua do thi khi tim kiem theo chieu rong la:');
For i:=1 to N do
If Chuaxet[i] then BFS(i);
Writeln;
Readln;
END.
Chương trình trên thực hiện với dữ liệu vào là tệp TKR_DT.INP có cấu trúc:
- Dòng đầu tiên, được gọi là dòng 0: ghi các số nguyên dương N, x cách nhau ít nhất là
một ký tự trống (N: Số đỉnh của đồ thị; x: Đỉnh xuất phát);
- Trong các dòng tiếp theo: Dòng thứ i (i = 1..N-1) ghi N-i số 0 và 1 liên tiếp nhau cho
biết giữa đỉnh i và đỉnh j có cạnh nối với nhau hay không (j = i + 1..N). Nếu số ghi ở vị trí j-i
tính từ trái sang phải trên dòng thứ i có giá trị 1 thì có cạnh nối giữa đỉnh i và đỉnh j, nếu là giá
trị 0 thì không có cạnh nối.
Ví dụ với tệp TKR_DT.INP sau đây:
13 1
101000000010
01000000000
0001000000
011000000
10110000
1000001
000000
3
10000
0000
110
10
0
Với tệp dữ liệu vào như trên ta phải hiểu như sau:
+ Ở dòng 0: N=13, x=1;
+ Ở dòng 1: 101000000010
- giữa đỉnh 1 với đỉnh 2 có cạnh nối với nhau
- giữa đỉnh 1 với đỉnh 3 không có cạnh nối với nhau
- giữa đỉnh 1 với đỉnh 4 có cạnh nối với nhau
- giữa đỉnh 1 với đỉnh 5 không có cạnh nối với nhau
....
- giữa đỉnh 1 với đỉnh 11 không có cạnh nối với nhau
- giữa đỉnh 1 với đỉnh 12 có cạnh nối với nhau
- giữa đỉnh 1 với đỉnh 13 không có cạnh nối với nhau
+ Ở dòng 2: 01000000000
- giữa đỉnh 2 với đỉnh 3 không có cạnh nối với nhau
- giữa đỉnh 2 với đỉnh 4 có cạnh nối với nhau
....
+ Ở dòng 11: 10
- giữa đỉnh 11 với đỉnh 12 có cạnh nối với nhau
- giữa đỉnh 11 với đỉnh 13 không có cạnh nối với nhau
+ Ở dòng 12: 0
- giữa đỉnh 12 với đỉnh 13 không có cạnh nối với nhau
Quan hệ giữa các đỉnh được mô tả qua đồ thị sau:
2 3 5

7 8
4
1 6 9

12 13

10 11

Thứ tự các đỉnh được nạp vào hàng đợi Q và lấy ra tuần tự như sau:

Các phần tử Các phần tử Các phần tử Phần tử lấy


nạp vào Q có trạng thái trong Q ra khỏi Q
cờ là False
1 1 1
2,4,12 (Khi lấy 1 ra) 2,4,12 2,4,12 1
Không nạp phần tử nào 4,12 2
khi lấy 2 ra khỏi Q
6,7 6,7 12,6,7 4
10,11 10,11 6,7,10,11 12
5,13 5,13 7,10,11,5,13 6
3 3 10,11,5,13,3 7
Không nạp phần tử nào 11,5,13,3 10

4
khi lấy 10 ra khỏi Q
Không nạp phần tử nào 5,13,3 11
khi lấy 11 ra khỏi Q
8,9 8,9 13,3,8,9 5
Không nạp phần tử nào 3,8,9 13
khi lấy 13 ra khỏi Q
Không nạp phần tử nào 8,9 3
khi lấy 3 ra khỏi Q
Không nạp phần tử nào 9 8
khi lấy 8 ra khỏi Q
Không nạp phần tử nào Rỗng 9
khi lấy 9 ra khỏi Q

Các phần tử tuần tự được lấy ra khỏi hàng đợi Q chính là các đỉnh được duyệt:
1, 2, 4, 12, 6, 7, 10, 11, 5, 13, 3, 8, 9

Qua đó cho thấy từ một đỉnh ta thăm đến các đỉnh khác liên quan đến nó theo chiều rộng.
Chính vì vậy thuật toán tìm kiếm theo chiều rộng được gọi là thuật toán Loang.

Lưu ý:
+ Với những bộ test dữ liệu bé thì ta có thể tạo một cách thủ công. Nhưng với những bộ test
dữ liệu lớn thì ta phải tạo bằng chương trình. Sau đây xin giới thiệu chương trình tạo bộ test
với số lượng các đỉnh của đồ thị là 253:
Program Taotes_baithamdinh;
Uses Crt;
Const
MN=253;
fi='TKR_DT.INP';
Var
f:Text;
Procedure Gen;
Var
i,j:Integer;
Begin
Assign(f,fi);
Rewrite(f);
Writeln(f,MN);
Randomize;
For i:=1 to MN-1 do
Begin
For j:=1 to MN-i do
Write(f,Random(2)); {Random(2) cho các giá trị ngẫu nhiên tù 0 đến 1}
Writeln(f);
End;
Close(f);
End;
BEGIN
Gen;
END.

5
+ Đối với dữ liệu vào mà quan hệ giữa các đỉnh có dạng là một ma trận đầy đủ (ma trận đối
xứng) và các số ghi trên mỗi dòng cách nhau ít nhất là một ký tự trống thì ta thay thủ tục đọc
tệp bởi thủ tục sau đây:
Procedure Doctep;
Var
i,j: Byte;
Begin
Assign(f,fi);
Reset(f);
Readln(f,N);
For i:=1 to N do
Begin
For j:=1 to N do
Read(f,A[i,j]);
Readln(f);
End;
End;
Bạn đọc tự viết thủ tục đọc tệp trong trường hợp dữ liệu vào mà quan hệ giữa các đỉnh có dạng
là nữa trên hoặc nữa dưới của một ma trận đầy đủ và các số ghi trên mỗi dòng cách nhau ít
nhất là một ký tự trống hoặc liền nhau.

II) CÁC BÀI TOÁN ÁP DỤNG:


Bài 1: Tìm đường đi qua ít đỉnh nhất
Cho đồ thị vô hướng G = (V,E) có N đỉnh và M cạnh (các đỉnh có số hiệu là 1,2,...,N). Mối
quan hệ giữa các đỉnh được cho bởi ma trận kề A[i,j]: nếu đỉnh i và đỉnh j có chung cạnh thì
A[i,j] = 1, ngược lại A[i,j] = 0.
Yêu cầu:
1) Tìm đường đi qua ít đỉnh nhất giữa 2 đỉnh bất kỳ nào đó của đồ thị.
2) Tìm số lượng các thành phần liên thông của đồ thị (các đỉnh thuộc một vùng liên
thông nếu luôn tồn tại đường đi giữa 2 đỉnh bất kỳ trong các đỉnh đó)

6
Bàn về dung lượng bộ nhớ: Chương trình trên thực hiện với giá trị tối đa là 253 đỉnh của đồ
thị. Vậy với đồ thị có số đỉnh lớn hơn 253 thì ta giải quyết thế nào?
Bài toán sau đây đưa ra một giải pháp để xử lý:
Bài 2: Đường đi trong đồ thị có nhiều đỉnh
Cho một đồ thị vô hướng có N đỉnh (N<=500000), các đỉnh có số hiệu là 1..N. Hãy tìm đường
đi qua ít đỉnh nhất giữa 2 đỉnh x và y nào đó cho trước của đồ thị.
Dữ liệu vào là tệp văn bản dothi.inp có cấu trúc:
- Dòng đầu tiên ghi các số N, x, y;
- Các dòng tiếp theo, mỗi dòng ghi 2 số là số hiệu của 2 đỉnh có chung cạnh;
- Các số ghi trên mỗi dòng cách nhau ít nhất là một ký tự trống.
Dữ liệu ra là tệp văn bản dothi.out ghi số hiệu các đỉnh trong đường đi tìm được giữa 2 đỉnh x
và y, mỗi số ghi trên 1 dòng. Nếu không tồn tại đường đi thì ghi thông báo ‘Khong ton tại
duong di’.
Ví dụ:
Tệp dothi.inp Tệp dothi.out Tệp dothi.inp Tệp dothi.out
13 1 6 1 13 1 8 1
12 4 12 4
14 6 14 6
1 12 1 12 5
21 21 8
24 24
37 37
41 41
42 42
47 47
46 46
67 67
64 64
65 65
6 13 6 13
56 56
58 58
59 59
73 73
74 74
76 76
85 85
89 89
95 95
98 98
10 11 10 11
10 12 10 12
11 10 11 10
11 12 11 12
12 1 12 1
12 10 12 10
12 11 12 11
13 6 13 6

Phân tích:

7
Nhìn dạng bài toán, chúng ta nhận thấy ngay thuật toán tối ưu để xử lý là thuật toán Loang.
Nhưng vì số đỉnh của đồ thị nằm trong phạm vi quá lớn (N<=500000) nên ta không thể sử
dụng kiểu dữ liệu mảng để lưu trữ thông tin về các đỉnh và biểu diễn cấu trúc hàng đợi. Vậy
làm thế nào?
Tôi xin đưa ra một giải pháp: Lưu trữ các thông tin trong quá trình xử lý cũng như biểu diễn
cấu trúc hàng đợi để ‘Loang’ là bởi các tệp văn bản (vì chúng ta đã biết tệp là kho chứa dữ
liệu dường như vô tận).
Cụ thể như sau:
- Sử dụng hệ thống tệp văn bản *.ke để lưu các đỉnh chung cạnh với đỉnh có số hiệu
là giá trị số của phần đầu tên tệp: Ví dụ tệp 2.ke sẽ lưu trữ số 1 và số 4, mỗi số ghi
trên một dòng.
- Sử dụng hệ thống các tệp văn bản *.dd chứa số 1 hoặc 0 cho biết đỉnh có số hiệu là
giá trị số của phần đầu tên tệp đã bị đánh dấu hay chưa; giá trị 1: đỉnh đó đã xét rồi,
ngược lại là chưa xét. Ví dụ tệp 5.dd chứa số 1 có nghĩa là đỉnh 5 đã được xét rồi
trong quá trình ‘Loang’ (đã được nạp vào hàng đợi).Hệ thống tệp này khởi tạo ban
đầu là chứa số 0.
- Sử dụng hệ thống tệp văn bản *.tr chứa một số nguyên là số hiệu của đỉnh trước
đỉnh có số hiệu là giá trị số của phần đầu tên tệp khi nạp vào hàng đợi. Ví dụ tệp
6.tr chứa số 4 có nghĩa là đỉnh 6 được nạp vào hàng đợi nhờ đỉnh 4.
- Sử dụng hệ thống các tệp *.que để biểu diễn cấu trúc hàng đợi. Ví dụ dau = 1 và
cuoi = 4 thì đỉnh đầu tiên của hàng đợi là đỉnh có số hiệu là số được chứa trong tệp
1.que. Tương tự, với đỉnh thứ 2, thứ 3, thứ 4, ... của hàng đợi.
- Sử dụng hệ thống các tệp văn bản *.kq để lưu các đỉnh trong quá trình lấy kết quả.
Hệ thống các tệp văn bản trên được đưa ra ở phần khai báo hằng:
Const
ke =’.ke’;
dd =’.dd’;
tr =’.tr’;
que =’.que’;
kq =’.kq’
Khởi tạo hệ thống các tệp *.dd và *.tr :
{Biến i kiểu Longint, biến s kiểu String}
For i:=1 to N do
Begin
Str(i,s); {Thủ tục chuyển số i sang dạng xâu là s}
Assign(f,s+dd);
Rewrite(f);
Write(f,0);
Close(f);
Assign(f,s+tr);
Rewrite(f);
Write(f,0);
Close(f);
End;
Để lấy đỉnh u ra khỏi hàng đợi thực hiện:
Str(dau,s);
Assign(f,s+que);
Reset(f);
Read(f,u);
Close(f);
Inc(dau);
8
Để nạp đỉnh v vào hàng đợi ta thực hiện:
Inc(cuoi);
Str(cuoi,s);
Assign(f,s+que);
Rewrite(f);
Writeln(f,v);
Close(f);
Toàn văn chương trình:
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M 16384,0,655360}
Program Timduongdivoisodinhlon;
Uses Crt;
Const
fi='dothi.inp';
fo='dothi.out';
ke='.ke';
que='.que';
dd='.dd';
tr='.tr';
kq='.kq';
Var
n,x,y:Longint;
time:longint; {Để phục vụ tính thời gian chạy chương trình}
f,f1,f2,f3:Text;
Procedure Doctep;
Var
i,u,v:Longint;
s:string;
Begin
Assign(f,fi);
Reset(f);
Readln(f,n,x,y);
For i:=1 to N do
Begin
Str(i,s); {Thủ tục đổi số u thành dạng ký tự s của nó}
Assign(f1,s+ke);
Rewrite(f1);
Close(f1);
Assign(f1,s+tr);
Rewrite(f1);
Write(f1,0);
Close(f1);
End;
While not seekeof(f) do {Nếu con trỏ tệp chưa di chuyển đến cuối tệp}
Begin
Readln(f,u,v);
Str(u,s);
Assign(f1,s+ke);
Append(f1);
Writeln(f1,v);

9
Close(f1);
Assign(f1,s+dd);
Rewrite(f1);
Writeln(f1,0);
Close(f1);
Str(v,s);
Assign(f1,s+dd);
Rewrite(f1);
Write(f1,0);
Close(f1);
End;
Close(f);
End;
Procedure Loang;
Var
u,v,dau,cuoi:Longint;
s1,s2,s3:String;
ok:Byte;
Begin
dau:=1;
cuoi:=1;
Str(x,s1);
Assign(f,s1+dd);
Rewrite(f);
Write(f,1); {Đánh đấu đỉnh x}
Close(f);
Assign(f,s1+tr);
Rewrite(f);
Writeln(f,0);
Close(f);
Assign(f,'1'+que);
Rewrite(f);
Writeln(f,x);
Close(f);
While dau<=cuoi do
Begin
Str(dau,s1);
Assign(f,s1+que);
Reset(f);
Readln(f,u); {Lấy phần tử đầu tiên trong hàng đợi}
Close(f);
Inc(dau);
Str(u,s1);
Assign(f,s1+ke); {Đọc các phần tử kề với u}
Reset(f);
While not Seekeof(f) do
Begin
Readln(f,v);
Str(v,s2);
Assign(f1,s2+dd);
Reset(f1);
10
Readln(f1,ok); {Kiểm tra xem v đã bị đánh dấu hay chưa}
Close(f1);
If ok=0 then
Begin
Assign(f1,s2+dd);
Rewrite(f1);
Writeln(f1,1); {Đánh dấu v}
Close(f1);
Inc(cuoi);
Str(cuoi,s3);
Assign(f2,s3+que);
Rewrite(f2);
Writeln(f2,v); {Nạp v vào hàng đợi}
Close(f2);
Assign(f2,s2+tr);
Rewrite(f2);
Writeln(f2,u); {Lưu lại đỉnh trước của v}
Close(f2);
End;
End;
Close(f);
End;
End;
Procedure Ketqua;
Var
i,t,dem:Longint;
s,s1:String;
Begin
Assign(f,fo);
Rewrite(f);
Str(y,s);
Assign(f1,s+tr);
Reset(f1);
Read(f1,t);
Close(f1);
Assign(f1,'1'+kq);
Rewrite(f1);
Write(f1,y); {Ghi số y vào tệp 1.kq}
Close(f1);
If t=0 then Write(f,'Khong ton tai duong di')
Else
Begin
i:=t;
dem:=1;
While i<>x do
Begin
Inc(dem);
Str(dem,s1);
Assign(f1,s1+kq);
Rewrite(f1);
Writeln(f1,i);
11
Close(f1);
Str(i,s1);
Assign(f1,s1+tr);
Reset(f1);
Read(f1,i);
Close(f1);
End;
Inc(dem);
Str(dem,s1);
Assign(f1,s1+kq);
Rewrite(f1);
Writeln(f1,i);
Close(f1);
For i:=dem downto 1 do
Begin
Str(i,s1);
Assign(f1,s1+kq);
Reset(f1);
Read(f1,t);
Close(f1);
Writeln(f,t);
End;
End;
Close(f);
End;
BEGIN
Clrscr;
time:=MemL[0:$46C]; {Mốc thời gian bắt đầu chạy chương trình}
Doctep;
Loang;
Ketqua;
Writeln((MemL[0:$46C] - time)/18.21:10:10); {Thời gian chạy chương trình}
END.
Lưu ý:
+ Vì khi chương trình trên thực hiện với bộ dữ liệu lớn thì sẽ tạo ra rất nhiều tệp văn bản, mà
những tệp này đến một lúc nào đó ta phải xoá đi để mở rộng không gian lưu trữ cho thiết bị
nhớ. Bởi vậy khi cài đặt nên tạo ra một thư mục để chứa các tệp văn bản đó (khi cần xoá, ta
xoá cả thư mục hoặc tất cả các tệp trong thư mục đó). Bạn đọc có thể sử dụng thêm một biến
kiểu String chứa đường dẫn để thay đổi một số câu lệnh trong chương trình trên sao cho việc
tạo hoặc truy cập các tệp văn bản cho đúng theo thư mục đã tạo.
+ Ta có thể tham khảo chương trình sau đây để tạo các bộ test dữ liệu lớn:
Program Taotes_duongdivoisodinhlon;
Uses Crt;
Const
MN=500000;
fi='dothi.inp';
Var
f:Text;
x,xn,yd:Longint;
Procedure Gen;

12
Var
i,j:Longint;
Begin
Assign(f,fi);
Rewrite(f);
Randomize;
xn:=Random(MN+1);
If xn=0 then
While xn=0 do xn:=Random(MN+1);
yd:=Random(MN+1);
If (yd=0) or (yd=xn) then
While (yd=0) or (yd=xn) do yd:=Random(MN+1);
Writeln(f,MN,’ ‘,xn,’ ‘,yd);
For i:=1 to MN do
Begin
x:=Random(MN);
If x=i then
While x=i do x:=Random(MN);
If x<>0 then
Begin
For j:=1 to x do
Writeln(f,i,’ ‘,x);
For j:=1 to x do
Writeln(f,x,’ ‘,i);
End;
End;
Close(f);
End;
BEGIN
Repeat
Gen;
Writeln(‘Hai đinh can tim duong di la:’,xn,’ ‘,yd);
Until Readkey = #27; {Khi nào thấy đạt yêu cầu rồi thì ấn phím ESC để dừng}
END.
+ Nếu yêu cầu dữ liệu ra là tệp văn bản như trên nhưng dòng đầu tiên ghi số lượng đỉnh trong
đường đi tìm được thì ta xử lý thế nào? Bạn đọc tự giải quyết, coi như là một bài tập.
Bài 4: Otomat
Có một Otomat được ghép từ các chi tiết có một trong hai trạng thái 0 hoặc 1 như hình 1.
Otomat có cấu trúc như hình 2 gồm 8 chi tiết G1, G2,..., G8 với 3 lối vào A,B,C. Mỗi trạng
thái của Otomat được được thể hiện bởi xâu nhị phân độ dài 8 là các trạng thái tương ứng của
G1, G2,...,G8.

Trạng thái 1 Trạng thái 0

Hình 1

13
A B C

G1 G2 G3

G4 G5

G6 G7 G8

Hình 2

Otomat hoạt động như sau: Khi thả một quả cầu vào một lối vào nào đó, sau khi quả cầu đi từ
một chi tiết nào đó, chi tiết đó thay đổi trạng thái từ 0 thành 1 hoặc từ 1 thành 0. Hoạt động
của Otomat được thể hiện bởi một xâu ký tự S chỉ gồm các chữ cái in hoa A, B, C mà mỗi ký
tự trong xâu S thể hiện việc quả cầu vào lối vào với tên ký tự đó.
Ví dụ: S=AABC có nghĩa là ta lần lượt thả quả cầu vào các lối A, A, B, C.
Bài toán đặt ra như sau: Cho hai trạng thái bất kỳ T1, T2 của Otomat. Hãy tìm một ký tự S
ngắn nhất có thể được thể hiện hoạt động của Otomat chuyển từ trạng thái T1 đến trạng thái
T2.
Dữ liệu vào là tệp otomat.inp cócấu trúc:
- Dòng thứ nhất ghi 8 số 0 và 1 biểu diễn trạng thái T1;
- Dòng thứ hai ghi 8 số 0 và 1 biểu diễn trạng thái T2;
Các số trên mỗi dòng được ghi liên tiếp nhau.
Dữ liệu ra là tệp otomat.out ghi xâu ký tự S tìm được. Nếu không tìm được xâu S thì ghi ký tự
‘#’.
Ví dụ:
Otomat.inp Otomat.out Otomat.inp Otomat.out
10011010 AAABBC 00010010 #
00101001 00101001

Phân tích:
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M 16384,0,655360}
Program OTOMAT;
Uses Crt;
Const
Nmax=256;
fi='otomat.inp';
fo='otomat.out';
Type
14
otomat=String[8];
Var
f:Text;
S:string;
T1,T2,O:otomat;
i,dau,cuoi,dem:Integer;
Q:Array[1..Nmax] of otomat;
truoc:Array[1..Nmax] of Integer;
Procedure Doctep;
Begin
Assign(f,fi);
Reset(f);
Readln(f,T1);
Readln(f,T2);
Close(f);
End;
Function Ktra(T:otomat;N:integer):Boolean;
Var
j:Integer;
Begin
Ktra:=True;
For j:=1 to N do
If T=Q[j] then
Begin
Ktra:=False;
Exit;
End;
End;
Function Tha_A(T:otomat):otomat;
Begin
If T[1]='0' then
Begin
T[1]:='1';
If T[6]='0' then T[6]:='1'
else T[6]:='0';
End
else {T[1]='1'}
Begin
T[1]:='0';
If T[4]='0' then
Begin
T[4]:='1';
If T[6]='0' then T[6]:='1'
else T[6]:='0';
End
else {T[4]='1'}
Begin
T[4]:='0';
If T[7]='0' then T[7]:='1'
else T[7]:='0';
End;
15
End;
Tha_A:=T;
End;
Function Tha_B(T:otomat):otomat;
Begin
If T[2]='0' then
Begin
T[2]:='1';
If T[4]='0' then
Begin
T[4]:='1';
If T[6]='0' then T[6]:='1'
Else T[6]:='0';
End
Else {T[4]='1'}
Begin
T[4]:='0';
If T[7]='0' then T[7]:='1'
Else T[7]:='0';
End;
End
Else {T[2]='1'}
Begin
T[2]:='0';
If T[5]='0' then
Begin
T[5]:='1';
If T[7]='0' then T[7]:='1'
else T[7]:='0';
End
Else {T[5]='1'}
Begin
T[5]:='0';
If T[8]='0' then T[8]:='1'
else T[8]:='0';
End;
End;
Tha_B:=T;
End;
Function Tha_C(T:otomat):otomat;
Begin
If T[3]='0' then
Begin
T[3]:='1';
If T[5]='0' then
Begin
T[5]:='1';
If T[7]='0' then T[7]:='1'
else T[7]:='0'
End
Else {T[5]='1'}
16
Begin
T[5]:='0';
If T[8]='0' then T[8]:='1'
else T[8]:='0';
End;
End
Else {T[3]='1'}
Begin
T[3]:='0';
If T[8]='0' then T[8]:='1'
else T[8]:='0';
End;
Tha_C:=T;
End;
Procedure Loang(T:otomat);
Var
p:otomat;
Begin
dau:=1;
cuoi:=1;
Q[cuoi]:=T;
While dau<=cuoi do
Begin
P:=Q[dau];
Inc(dau);
For i:=1 to 3 do
Begin
If i=1 then O:=Tha_A(P);
If i=2 then O:=Tha_B(P);
If i=3 then O:=Tha_C(P);
If Ktra(O,cuoi) then
Begin
Inc(cuoi);
Q[cuoi]:=O;
Truoc[cuoi]:=dau-1;
If i=1 then S[cuoi]:='A';
If i=2 then S[cuoi]:='B';
If i=3 then S[cuoi]:='C';
End;
If O=T2 then exit;
End;
End;
End;
Procedure Ketqua(v:integer);
Var
l:integer;
Begin
If v>0 then
Begin
l:=truoc[v];
Ketqua(l);
17
Write(f,s[v])
End;
End;
BEGIN
FillChar(truoc,Sizeof(truoc),0);
Doctep;
Loang(T1);
Assign(f,fo);
Rewrite(f);
If O<>T2 then Writeln(f,'#')
Else Ketqua(cuoi);
Close(f);
END.

Bài 5: Máy đổi thẻ tự động


Có một máy giải trí tự động có M cửa dùng để đổi thẻ. Có các thẻ mã số từ 1,..,N. Nếu ta bỏ
thẻ có mã số i vào một cửa nào đó thì máy sẽ thu thẻ đó và cho ra một thẻ có mã số nào đó
trong khoảng 1..N. Máy đổi thẻ hoạt động theo thông tin ghi trong tệp văn bản the.inp :
- Dòng đầu tiên ghi các số N, M;
- N dòng tiếp theo, mỗi dòng chứa M số tạo thành một bảng có kích thước N x M. Phần tử
nằm trên dòng i, cột j của bảng này cho biết nếu ta bỏ thẻ có số hiệu i vào cửa j thì sẽ thu được
thẻ có số hiệu chính là giá trị của phần tử đó.
Các số trên mỗi dòng của tệp ghi cách nhau ít nhất là một ký tự trống.
Yêu cầu:
Với mỗi cặp thẻ có số hiệu x và y cho trước (x<>y), hãy cho biết có cách nào nhanh nhất
để dùng thẻ có số hiệu x thu được thẻ có số hiệu y hay không?
Dữ liệu ra là tệp văn bản the.out trình bày theo dạng:
Bỏ thẻ x vào cửa ... thu được thẻ ...
.........
Bỏ thẻ ... vào cửa ... thu được thẻ y
Nếu không tìm được cách để từ thẻ có số hiệu x thu được thẻ có số hiệu y thì ghi vào tệp
thông báo ‘Tu the x khong thu duoc the y’.
Ví dụ:
Tệp Tệp Tệp Tệp
Doithe.inp Doithe.out Doithe.inp Doithe.out
5421 Bo the 2 vao cua 1 thu duoc the 3 5 4 4 5 Tu the 4 khong thu duoc the 5
2351 Bo the 3 vao cua 1 thu duoc the 1 2 2 2 4
3443 1444
1524 1524
5321 1122
3424 3424

Phân tích: Bài toán trên thuộc lớp các bài toán biến đổi trạng thái. Hơn nữa yêu cầu tìm kết
quả qua ít bước thao tác nhất nên ta sử dụng thuật toán Loang là thích hợp nhất.
Cụ thể như sau:
Ta sử dụng ma trận A[i,j] (i = 1..N, j = 1...M) để lưu giữ thông tin quan hệ giữa thẻ - cửa đã
cho trong tệp dữ liệu vào. Ta phải hiểu: Bỏ thẻ i vào cửa j thu được thẻ A[i,j].
Cấu trúc dữ liệu hàng đợi được biểu diễn bởi mảng một chiều Q.
Ta phải tìm các thao tác sao cho từ thẻ x thu được thẻ y một cách nhanh nhất.
Đầu tiên ta cho thẻ x vào Q.
18
Cứ mỗi lần lấy một thẻ p ra từ Q ta lại nạp vào Q những thẻ có thể thu được từ thẻ p (chỉ nạp
những thẻ chưa có trong Q, hàm vitri(v) cho biết thẻ v có ở trong Q hay không: Giá trị của
hàm bằng 0 thì thẻ v chưa có trong Q, ngược lại thì đã có).
Khi nạp một thẻ vào Q ta phải lưu giữ thông tin thẻ đó có được từ thẻ nào và bỏ vào cửa nào
để sau này truy xuất kết quả (mảng một chiều TRUOC có nhiệm vụ đó).
Quá trình trên lặp đi lặp lại cho đến khi hàng đợi Q rỗng (dau > cuoi)
Kết thúc quá trình ‘Loang’, dựa vào mảng TRUOC ta truy xuất kết quả bằng thủ tục đệ quy
Ketqua(y).
Khi truy xuất kết quả ta phải hiểu rằng nếy TRUOC[y].the = 0 thì có nghiã là thẻ y không thu
được từ một thẻ nào cả.
Lưu ý: một thẻ i bỏ vào một của j nào đó thì vẫn có thể thu được thẻ i.

Sau đây là toàn văn chương trình:


{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M 16384,0,655360}
Program Doithe;{ Thuat toan Loang}
Uses Crt;
Const
Mmax=200;
Nmax=200;
fi='the.inp';
fo='the.out';
Type
rc=record
the,cua:byte;
End;
Var
A:Array[1..Nmax,1..Mmax] of Byte; {Quan hệ Thẻ - Cửa}
Q:Array[1..Nmax] of Byte; {Hàng đợi - chứa các thẻ thu được từ thẻ x}
truoc:Array[1..Nmax] of rc;
N,M,x,y,i,j,dau,cuoi:Byte;
f:Text;
Procedure Doctep;
Begin
Assign(f,fi);
Reset(f);
Readln(f,N,M,x,y);
For i:=1 to N do
Begin
For j:=1 to M do
Read(f,A[i,j]);
Readln(f);
End;
End;
Function Vitri(v:Byte):Integer;
Var
l:Byte;
Begin
vitri:=0;
For l:=cuoi downto 1 do
If v=Q[l] then
19
Begin
vitri:=l;
exit;
End;
End;
Procedure Loang;
Var
p:Byte;
Begin
dau:=1;
cuoi:=1;
Q[cuoi]:=x; {Nap x vao Q}
While dau<=cuoi do
Begin
p:=Q[dau];
Inc(dau);
For i:=1 to M do
Begin
If vitri(A[p,i])=0 then
Begin
Inc(cuoi);
Q[cuoi]:=A[p,i];
truoc[A[p,i]].the:=p;
truoc[A[p,i]].cua:=i;
End;
End;
End;
End;
Procedure Ketqua(y:Integer);
Var
k,p,l:Byte;
Begin
If truoc[y].the<>0 then
Begin
k:=truoc[y].the;
p:=truoc[y].cua;
l:=y;
Ketqua(k);
Writeln(f,'Bo the ',k,' vao cua ',p,' duoc the ',l);
End;
End;
Procedure Xuatketqua;
Begin
Assign(f,fo);
Rewrite(f);
If truoc[y].the=0 then Writeln(f,'Tu the ',x,' khong thu duoc the ',y)
Else
Ketqua(y);
Close(f);
End;
BEGIN
20
Clrscr;
FillChar(Q,SizeOf(Q),0);
FillChar(truoc,SizeOf(truoc),0);
Doctep;
Loang;
Xuatketqua;
END.

Bài tập: Cũng bài toán trên nhưng yêu cầu từ thẻ x thu được thẻ có số hiệu lớn nhất một cách
nhanh nhất (thẻ có số hiệu lớn nhất trong các thẻ thu được từ thẻ x).

Bài 5: Đường đi của Robot (Đề thi HSG lớp 12 năm học 2009 - 2010, Tỉnh Hà Tĩnh)
Một bảng hình chữ nhật có kích thước MxN (M,N nguyên dương và không lớn hơn 100)
được chia thành các ô vuông đơn vị bằng các đường thẳng song song với các cạnh. Một số ô
vuông nào đó có thể đặt các vật cản. Từ một ô vuông, Robot có thể đi đến một ô vuông kề
cạnh với nó nếu ô vuông đó không có vật cản. Hỏi rằng nếu Robot bắt đầu xuất phát từ một ô
vuông không có vật cản thuộc dòng K, cột L thì có thể đi đến được ô vuông không có vật cản
thuộc dòng H, cột O hay không? Nếu có thì hãy chỉ ra đường đi qua ít ô vuông nhất.
Dữ liệu vào là tệp văn bản BAI3.INP có cấu trúc:
- Dòng đầu tiên ghi các chữ số M, N, K, L, H, O. Các số ghi cách nhau ít nhất một ký tự
trống;
- M dòng tiếp theo, mỗi dòng ghi N số 1 hoặc 0 tuỳ thuộc vào ô vuông tương ứng trong
bảng hình chữ nhật nêu trên có vật cản hay không (ghi số 1 nếu có vật cản); các số trên mỗi
dòng ghi liên tiếp nhau.
Dữ liệu ra là tệp văn bản BAI3.OUT có cấu trúc:
Nếu Robot có thể đi được từ ô vuông thuộc dòng K, cột L đến ô vuông thuộc dòng H, cột
O thì:
- Dòng đầu tiên ghi ‘Co duong di ‘;
- Các dòng tiếp theo, mỗi dòng ghi 2 số là chỉ số dòng và chỉ số cột của các ô vuông trong
đường đi tìm được từ ô vuông thuộc dòng K, cột L đến ô vuông thuộc dòng H, cột O mà qua ít
ô vuông nhất. Hai số trên mỗi dòng ghi cách nhau ít nhất một ký tự trống;
- Ngược lại, nếu Robot không thể đi được từ ô vuông thuộc dòng K, cột L đến ô vuông
thuộc dòng H, cột O thì ghi ‘Khong co duong di’.
Ví dụ 1:
Tệp robot.inp: Tệp robot.out:
473426 Co duong di
1000000 34
0010100 35
0000000 36
1101000 26

Ví dụ 2:
Tệp robot.inp: Tệp robot.out:
472213 Khong co duong di
1010000
0010100
0100000
1101000

Phân tích:

21
Yêu cầu của bài toán thực chất là tìm đường đi từ ô [K,L] đến ô [H,O] sao cho qua ít ô vuông
nhất. Ta dễ thấy thuật toán để xử lý một cách hợp lý nhất là thuật toán Loang. Ta bắt dầu
“loang” từ ô [K,L], nếu “loang” đến được ô [H,O] thì có đường đi, ngược lại không có đường
đi.
Hàng đợi phục vụ “loang” được thể hiện bởi mảng 2 chiều Q:Array[1..2,Mmax*Max] of Byte;
hàng thứ 1 của Q để lưu thông tin chỉ số hàng, hàng thứ 2 lưu thông tin của chỉ số cột của các
ô khi nạp vào Q.
Mảng A lưu thông tin tình trạng các ô - có vật cản hay không của bảng hình chữ nhật chứa các
ô vuông.
Mảng P dùng để đánh dấu những ô đã “loang” đến; đồng thời để phục vụ cho việc truy xuất
đường đi sau này nên khi ô [i,j] được “loang” đến thì P[i,j] được gán giá trị là r (r là giá trị
tương ứng với hướng mà ô trước đó “loang” đến, hướng nào tương ứng với giá trị k bao nhiêu
tuỳ theo quy định, ví dụ r = 1 - sang phải, 2 - đi xuống, 3 - sang trái, 4 - đi lên).
Sau khi thực hiện xong việc “loang”, nếu P[H,O] = 0 thì điều có có nghĩa là ô [H,O] chưa
được “loang” đến (không có đường đi), nếu P[H,O] = r (r=1..4 - loang theo 4 hướng) thì dựa
vào hướng “loang” đến mà ta tìm được ô trước đó, rồi ta lại dựa vào giá trị k của ô tìm được ta
tìm được ô trước đó nữa ... quá trình trên kết thúc khi tìm được ô [K,L].
Sau khi “loang” xong thì giá trị các phần tử trong mảng Q không còn giá trị sử dụng nữa nên
ta có thể dùng mảng Q phục vụ cho việc truy xuất kết quả.
Toàn văn chương trình:
Program Bai3_hsg12_09;
Uses Crt;
Const Nmax=100;
Mmax=100;
fi='robot.inp';
fo='robot.out';
Var
f:Text;
s:char;
A,P:Array[1..Mmax,1..Nmax] of Byte;
Q:Array[1..2,1..Mmax*Nmax] of Byte;
M,N,k,l,i,j,dem,dau,cuoi,so,H,O:Byte;
Procedure Doctep;
Begin
Assign(f,fi);
Reset(f);
Readln(f,M,N,k,l,h,o);
For i:=1 to M do
Begin
dem:=0;
While not eoln(f) do
Begin
Read(f,s);
dem:=dem+1;
if s='1' then
A[i,dem]:=1;
if s='0' then
A[i,dem]:=0;
End;
Readln(f);
End;
22
Close(f);
End;
Procedure Loang;
Begin
dau:=1;
cuoi:=1;
Q[1,dau]:=k;
Q[2,dau]:=l;
Fillchar(P,sizeof(P),0); {Mảng để đánh dấu ô đã duyệt hay chưa, khác 0 là đã duyệt rồi}
P[k,l]:=5; {Đánh dấu để những lớp sau không phải “loang” đến ô này nữa }
While dau<=cuoi do
Begin
i:=Q[1,dau];
j:=Q[2,dau];
dau:=dau+1;
If (i=h) and (j=o) then Exit;{Den o can den}
If (i>1) and (P[i-1,j]=0) and (A[i-1,j]=0) then
Begin
P[i-1,j]:=4;{o truoc do ben duoi}
cuoi:=cuoi+1;
Q[1,cuoi]:=i-1;
Q[2,cuoi]:=j;
End;
If (i<M) and (P[i+1,j]=0) and (A[i+1,j]=0) then
Begin
P[i+1,j]:=2;
cuoi:=cuoi+1;
Q[1,cuoi]:=i+1;
Q[2,cuoi]:=j;
End;
If (j>1) and (P[i,j-1]=0) and (A[i,j-1]=0) then
Begin
P[i,j-1]:=3;
cuoi:=cuoi+1;
Q[1,cuoi]:=i;
Q[2,cuoi]:=j-1;
End;
If (j<N) and (P[i,j+1]=0) and (A[i,j+1]=0) then
Begin
P[i,j+1]:=1;
cuoi:=cuoi+1;
Q[1,cuoi]:=i;
Q[2,cuoi]:=j+1;
End;
End;
End;
Procedure Ketqua;
Begin
Assign(f,fo);
Rewrite(f);
If P[h,o]=0 then Writeln(f,'Khong co duong di')
23
Else
Begin
so:=1;
Writeln(f,'Co duong di');
i:=H;
j:=O;
Q[1,so]:=i;
Q[2,so]:=j;
While (i<>K) or (j<>L) do
Begin
If P[i,j]=1 then
Begin
j:=j-1;
so:=so+1;
Q[1,so]:=i;
Q[2,so]:=j;
End;
If P[i,j]=2 then
Begin
i:=i-1;
so:=so+1;
Q[1,so]:=i;
Q[2,so]:=j;
End;
If P[i,j]=3 then
Begin
j:=j+1;
so:=so+1;
Q[1,so]:=i;
Q[2,so]:=j;
End;
If P[i,j]=4 then
Begin
i:=i+1;
so:=so+1;
Q[1,so]:=i;
Q[2,so]:=j;
End;
End;
For i:=so downto 1 do
Writeln(f,Q[1,i]:4,Q[2,i]:4);
End;
Close(f)
End;
BEGIN
Doctep;
Loang;
Ketqua;
END.
Để dễ hiểu nên chương trình trên được trình bày tách bạch từng đối tượng, mỗi đối tượng thực
hiện từng chức năng của nó (ví dụ mảng A, mảng P, mỗi mảng có mỗi chức năng riêng ...), và
24
điều kiện để thực hiện “loang” theo 4 hướng cũng được nêu ra rất rõ (ví dụ điều kiện để loang
được đến ô phía trên là: (i>1) and (P[i-1,j]=0) and (A[i-1,j]=0,...).
Tuy nhiên ta nhận thấy rằng trong mảng A thông tin của các ô đã “loang” đến rồi thực sự
không còn quan trọng nữa nên ta có thể dùng chính mảng A để thay thế chức năng của mảng P.
Hàng đợi Q có thể thay đổi mỗi phần tử là một record HC:
Type
HC = Record
h,c:Byte; {h: lưu chỉ số hàng, c: lưu chỉ số cột}
End;
Từ một ô trong lưới ô vuông (có giá trị các ô là 0/1) “loang” theo 4 hướng, ngoài cách trình
bày như chương trình trên ra, người ta sử dụng kỷ thuật “rào” và 2 mảng hằng Hi=(-1,0,1,0)
Hj=(0,-1,0,1) để cài đặt chương trình một cách gọn hơn, trong sáng hơn:
- Kỷ thuật “rào”: Khai báo mảng A: Array[0..Mmax+1,0..Nmax+1] of Byte; rồi sau đó
dùng lệnh SetfillChar(A,SizeOf(A),1); (mảng A được “rào” xung quanh bởi số 1), nhờ vậy mà
khi “loang” không cần phải kiểm tra điều kiện của chỉ số hàng, cột các ô.
- Cách sử dụng 2 mảng hằng Hi=(-1,0,1,0), Hj=(0,-1,0,1):
Khai báo:
Const
Hi:Array[1..4] of Integer=(-1,0,1,0); {Tương ứng với chỉ số hàng, -1:lên, 1:xuống}
Hj:Array[1..4] of Integer=(0,-1,0,1); {Tương ứng với chỉ số cột, -1:trái, 1:phải}
Thực hiện “loang” theo 4 hướng:
.......
i:=Q[dau].h;
j:=Q[dau].c;
Inc(dau);
For r:=1 to 4 do {r=1:lên 2:trái 3:xuống 4:phải}
Begin
x:=i+Hi[r];
y:=j+Hj[r];
If A[x,y]=0 then
Begin
Inc(cuoi);
Q[cuoi].h:=x;
Q[cuoi].c:=y;
A[x,y]:=r;
End;
End;
Trong đoạn chương trình trên ta thấy:
- Với r =1 => Hi[r] = Hi[1] = -1 => i + Hi[1] = i – 1, có nghĩa là chỉ số hàng giảm đi một
đơn vị (1)
Hj[r] = Hj[1] = 0 => j + Hj[1] = j – 0 , có nghĩa là chỉ số cột không đổi (2)
(1) và (2) <=> thao tác di chuyển đến ô chung cạnh ở phía trên (đi lên).
- Với r =2 => Hi[r] = Hi[2] = 0 => i + Hi[2] = i + 0, có nghĩa là chỉ số hàng không đổi (3)
Hj[r] = Hj[2] = -1 => j + Hj[2] = j – 1 , có nghĩa là chỉ số cột giảm đi một
đơn vị (4)
(3) và (4) <=> thao tác di chuyển đến ô chung cạnh ở phía bên trái (sang trái).
- Tương tự với các giá trị khác của r.
Với những vấn đề nêu ra ở trên, chương trình có thể cải tiến lại như sau:
Program Bai3_hsg12_09;
Uses Crt;
Const
25
Hi:Array[1..4] of Integer=(-1,0,1,0);
Hj:Array[1..4] of Integer=(0,-1,0,1);
Nmax=100;
Mmax=100;
fi='robot.inp';
fo='robot.out';
Type
HC=Record
h,c:Byte;
End;
Var
f:Text;
s:char;
A:Array[0..Mmax+1,0..Nmax+1] of Byte;
Q:Array[1..Mmax*Nmax] of HC;
M,N,k,l,i,j,dem,dau,cuoi,h,o:Integer;
Procedure Doctep;
Begin
Fillchar(A,Sizeof(A),1);{Rao xung quanh}
Assign(f,fi);
Reset(f);
Readln(f,M,N,k,l,h,o);
For i:=1 to M do
Begin
dem:=0;
While not eoln(f) do
Begin
Read(f,s);
dem:=dem+1;
if s='1' then
A[i,dem]:=1;
if s='0' then
A[i,dem]:=0;
End;
Readln(f);
End;
Close(f);
End;
Procedure Loang_Rao;
Var
x,y,r:Byte;
Begin
dau:=1;
cuoi:=1;
Q[dau].h:=k;
Q[dau].c:=l;
A[k,l]:=5;
While dau<=cuoi do
Begin
i:=Q[dau].h;
j:=Q[dau].c;
26
Inc(dau);
If (i = H) and (j = O) then Exit;
For r:=1 to 4 do {r=1:lên 2:trái 3:xuống 4:phải}
Begin
x:=i+Hi[r];
y:=j+Hj[r];
If A[x,y]=0 then {Nếu ô [x,y] chưa “loang” đến}
Begin
Inc(cuoi);
Q[cuoi].h:=x;
Q[cuoi].c:=y;
A[x,y]:=r;
End;
End;
End;
End;
Procedure Ketqua;
Begin
Assign(f,fo);
Rewrite(f);
If A[H,O]=0 then Writeln(f,'Khong co duong di')
Else
Begin
Writeln(f,'Co duong di');
dem:=1;
Q[dem].h:=h;
Q[dem].c:=o;
i:=h;
j:=o;
While (i<>k) or (j<>l) do
Begin
Inc(dem);
If A[i,j]=1 then
Begin
Q[dem].h:=i+1;
Q[dem].c:=j;
End;
If A[i,j]=2 then
Begin
Q[dem].h:=i;
Q[dem].c:=j+1;
End;
If A[i,j]=3 then
Begin
Q[dem].h:=i-1;
Q[dem].c:=j;
End;
If A[i,j]=4 then
Begin
Q[dem].h:=i;
Q[dem].c:=j-1;
27
End;
i:=Q[dem].h;
j:=Q[dem].c;
End;
For i:=dem downto 1 do
Writeln(f,Q[i].h,' ',Q[i].c);
End;
Close(f);
End;
BEGIN
Doctep;
Loang_Rao;
Ketqua;
END.

Bài 6: Gặp gỡ của hai Robot.


Trên một lưới ô vuông MxN (M,N<100), người ta đặt Robot A ở góc trái trên, Robot B ở
góc phải dưới. Mỗi ô của lưới ô có thể đặt một vật cản hoặc không (ô trái trên và ô phải dưới
không có vật cản). Hai Robot bắt đầu di chuyển đồng thời với tốc độ như nhau và không
Robot nào được dừng lại trong khi Robot kia di chuyển (trừ khi nó không thể đi được nữa).
Tại mỗi bước, Robot chỉ có thể di chuyển theo 4 hướng - đi lên, đi xuống, sang trái, sang phải
- vào các ô kề cạnh. Hai Robot gặp nhau nếu chúng cùng đứng trong một ô vuông. Bài toán
đặt ra là tìm cách di chuyển ít nhất mà 2 Robot phải thực hiện để có thể gặp nhau.
Dữ liệu vào cho bởi tệp robot.inp:
- Dòng đầu ghi 2 số M, N cách nhau ít nhất một ký tự trống;
- M dòng tiếp theo, mỗi dòng ghi N số 0 hoặc 1 liên tiếp nhau mô tả trạng thái của các ô
vuông: 1 - có vật cản, 0 - không có vật cản.
Dữ liệu ra ghi vào tệp robot.out:
- Nếu 2 Robot không thể gặp nhau thì ghi ký tự ‘#’.
- Ngược lại, ghi hai dòng, mỗi dòng là một dãy các ký tự viết liền nhau mô tả các bước đi
của Robot: U - đi lên, D - đi xuống, L - sang trái, R - sang phải. Dòng đầu là các bược đi của
Robot A, dòng sau là các bước đi của Robot B.
Ví dụ:
robot.inp robot.out robot.inp robot.out
46 DRRR 34 #
011000 LULU 0000
000001 0000
001001 0000
010100

Hướng dẫn:
Với dạng bài toán như vậy thì ta nghĩ ngay đến thuật toán Loang để tìm đường đi cho 2 Robot.
Như vậy là phải “loang” từ 2 phía (loang của Robot A và loang của Robot B). Nhưng vì 2
Robot di chuyển đồng thời trong khi không cho phép ta cài đặt việc “loang” song song từ 2
phía nên ta phải thiết kế “loang” thế nào cho hợp lý.
Xin đề xuất một ý tưởng “loang” như sau: Cứ Robot A loang 1 lớp thì dừng lại để Robot B
loang 1 lớp, quá trình đó được lặp đi lặp lại cho đến khi 2 Robot gặp nhau tại một ô hoặc 1
trong 2 Robot dừng “loang”. Một lớp “loang” ở đây là “loang” từ các phần tử hiện có trong
hàng đợi (từ phần tử Queue[dau] đến phần tử Queue[cuoi]). Sau mỗi lớp “loang”, biến dau và
biến cuoi lại được điều chỉnh để trở thành vị trí đầu và vị trí cuối của các phần tử mới trong

28
Queue. Ta có thể mô tả cụ thể các lớp “loang” của 2 Robot với dữ liệu vào là tệp robot.inp thứ
2 ở trên:

Lớp 1 Lớp 2 Lớp 3


Queue 1 2 1 3 1 2 1 .....
Robot A 1 1 2 1 1 2 3

Queue 3 3 2 3 2 3 1 ......
Robot B 4 3 4 2 3 4 4
Lớp 1 Lớp 2 Lớp 3

Q1,Q2 là 2 mảng dùng để biểu diễn cấu trúc hàng đợi để phục vụ việc “loang” của 2 Robot.
Trong quá trình “loang” ta phải lưu giữ thông tin hàng, cột của ô khi “loang” đến, bởi vậy các
phần tử của Q1, Q2 là các record có kiểu HC
HC = Record
h,c:Byte; {h: lưu chỉ số hàng, c: lưu chỉ số cột}
end;
Hai hàng đợi Q1, Q2 được khởi tạo như sau:
Procedure KT_Queue;
Begin
dau1:=1;
cuoi1:=1;
Q1[cuoi1]:=1;
Q1[cuoi1]:=1; {Robot A xuất phát từ ô [1,1]}
dau2:=1;
cuoi2:=1;
Q2[cuoi2]:=M;
Q2[cuoi2]:=N; {Robot B xuất phát từ ô [M,N]}
End;
Ngay sau khi khởi tạo thì trong Q1 chứa ô [1,1], Q2 chứa ô [M,N]. Đó là các ô xuất phát để
“loang” của 2 Robot.
Mỗi Robot từ một ô có thể “loang” theo bốn hướng: đi xuống, sang trái, đi lên, sang phải; nên
để thuận tiện cho việc cài đặt ta sử dụng kỷ thuật “rào”: Mảng A[i,j] chứa thông tin các ô trong
lưới ô vuông được khai báo A:Array[0..Mmax + 1,0..Nmax + 1] of Byte (chứ không phải như
thông thường là [1..Mmax,1..Nmax]) và được khởi tạo FillChar(A,SizeOf(A),1) (như vậy là
xung quanh lưới ô vuông được “rào” bới số 1); đồng thời sử dụng 2 mảng hằng Hi=(1,0,-1,0),
Hj=(0,-1,0,1).
Khi đó việc “loang” theo lớp của Robot A được thực hiện như sau:
Procedure Loang1;
Var
k:Byte;
Begin
j:=Cuoi1;
For i:=dau1 to cuoi1 do
For k:=1 to 4 do
Begin
h:= Q1[i].h + Hi[k]; {k=1 - đi xuống, 2 - sang trái, 3 - đi lên, 4 - sang phải}
c:= Q1[i].c + Hj[k];
If A[h,c] = 0 then {ô [h,c] không có vật cản và chưa “loang” đến}
Begin

29
Inc(j);
Q1[j].h:= h;
Q1[j].c:= c; {Nạp ô [h,c] vào hàng đợi Q1}
A[h,c]:=k;{Đánh dấu ô bằng cách gán giá trị tương ứng với hướng loang}
B[h,c]:=True; {Dấu hiệu cho Robot B nhận biết đã gặp Robot A}
End;
End;
dau1:=cuoi1 + 1;
cuoi1:=j; {Điều chỉnh lại biến dau1, cuoi1 cho các phần tử mới trong Q1}
If dau1 > cuoi1 then ST:=True; {ST=True là Q1 rỗng, kết thúc “loang”}
End;
Việc “loang” theo lớp của Robot B cũng tương tự như Robot A nhưng chỉ khác ở chổ khi
“loang” đến một ô [h,c] nào đó thì phải xét dấu hiệu B[h,c] xem thử đã gặp Robot A chưa:
........
If B[h,c] then {Nếu tại ô [h,c] Robot B gặp Robot A thì}
Begin
lk:=k; {Lưu lại giá trị tương ứng với hướng “loang” để lấy kết quả}
hm:=h; {Lưu lại chỉ số hàng của ô mà 2 Robot gặp nhau để lấy kết quả}
cm:=c; {Lưu lại chỉ số cột của ô mà 2 Robot gặp nhau để lấy kết quả}
TT:=True; {Dấu hiệu dừng “loang” của 2 Robot vì đã gặp nhau}
Exit;
End;
.........
Sở dĩ ta phải lưu lại giá trị tương ứng với hướng “loang” (lk:=k) là vì tại ô gặp nhau [h,c]
Robot A đã “loang” đến trước nên đã gán giá trị của A[h,c] bằng giá trị tương ứng với hướng
“loang” đến nên khi Robot B “loang” đến ô [h,c] buộc ta phải lưu lại giá trị tương ứng với
hướng “loang” vào biến lk để sau này truy xuất đường đi của Robot B.
Quá trình “loang” theo từng lớp của 2 Robot được thực hiện như sau:
Procedure Loang_lop;
Begin
TT:=False;
ST:=False;
While (ST=False) and (TT=False) do
Begin
FillChar(B,SizeOf(B),False); {Đánh dấu theo từng lớp loang}
Loang1;
Loang2;
End;
End;
Lệnh đánh dấu theo từng lớp “loang” tại vị trí như ở trên: FillChar(B,SizeOf(B),False) là rất
quan trọng vì Robot B gặp Robot A tại ô [h,c] chỉ khi B[h,c] = True tại thời điểm lớp “loang”
của Robot A cùng lớp “loang” với Robot B. Còn nếu B[h,c] = True của lớp “loang” trước nào
đó của Robot A thì không thể kết luận 2 Robot gặp nhau vì khi đó 2 Robot sẽ di chuyển khập
khểnh chứ không đồng thời.
Việc lấy kết quả dựa vào giá trị của biến TT: TT=True - Hai Robot gặp nhau, TT=False - Hai
Robot không gặp nhau.
Trong trường hợp gặp nhau thì dựa vào việc đã lưu thông tin ô gặp nhau vào 2 biến hm ,cm
(hm - chỉ số hàng, cm - chỉ số cột) ta sẽ truy xuất đường đi của 2 Robot.

Toàn văn chương trình:


{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
30
{$M 16384,0,655360}
Program Gapgo;
Uses Crt;
Const
Mmax=100;
Nmax=100;
Hi:Array[1..4] of Integer=(1,0,-1,0);
Hj:Array[1..4] of Integer=(0,-1,0,1);
fi='robot.inp';
fo='robot.out';
Type
HC=Record
h,c:Byte;
End;
Var
A:Array[0..Mmax+1,0..Nmax+1] of Byte;
B:Array[0..Mmax+1,0..Nmax+1] of Boolean;
Q1,Q2:Array[1..Mmax*Nmax] of HC;
M,N,i,j,dem,dau1,cuoi1,dau2,cuoi2,hm,cm,lk:Word;
f:Text;
s:char;
ST,TT:Boolean; {Robot1 hoặc Robot2 loang hết thì ST=TRue; nếu gặp nhau thì TT=True}
Procedure Doctep;
Begin
FillChar(A,Sizeof(A),1);{Rao xung quanh}
Assign(f,fi);
Reset(f);
Readln(f,M,N);
For i:=1 to M do
Begin
dem:=0;
While not eoln(f) do
Begin
dem:=dem+1;
Read(f,s);
If s='0' then A[i,dem]:=0
else A[i,dem]:=1;
End;
Readln(f);
End;
Close(f);
End;
Procedure KT_Queue; {Khởi tạo 2 hàng đợi Q1 và Q2}
Begin
dau1:=1;
cuoi1:=1;
Q1[cuoi1].h:=1;
Q1[cuoi1].c:=1;
dau2:=1;
cuoi2:=1;
Q2[cuoi2].h:=M;
31
Q2[cuoi2].c:=N;
End;
Procedure Loang1;
Var
k,h,c:Byte;
Begin
j:=cuoi1;
For i:=dau1 to cuoi1 do
For k:=1 to 4 do
Begin
h:=Q1[i].h + Hi[k];
c:=Q1[i].c + Hj[k];
If A[h,c] = 0 then
Begin
Inc(j);
Q1[j].h:=h;
Q1[j].c:=c;
A[h,c]:=k;
B[h,c]:=True;
End;
End;
dau1:=cuoi1+1;
cuoi1:=j;
If dau1>cuoi1 then ST:=True;
End;
Procedure Loang2;
Var
k,h,c:Byte;
Begin
j:=cuoi2;
For i:=dau2 to cuoi2 do
For k:=1 to 4 do
Begin
h:=Q2[i].h + Hi[k];
c:=Q2[i].c + Hj[k];
If A[h,c]=0 then
Begin
Inc(j);
Q2[j].h:=h;
Q2[j].c:=c;
A[h,c]:=k;
End;
If B[h,c] then
Begin
lk:=k;
hm:=h;
cm:=c;
TT:=True;
Exit;
End;
End;
32
dau2:=cuoi2+1;
cuoi2:=j;
if dau2>cuoi2 then ST:=True;
End;
Procedure Loang_lop;
Begin
TT:=False;
ST:=False;
While (ST=False) and (TT=False) do
Begin
FillChar(B,SizeOf(B),False); {Danh dau theo tung lop loang}
Loang1;
Loang2;
End;
End;
Procedure Ketqua;
Var
k:Byte;
s1,s2:String;
Begin
s1:='';
s2:='';
Assign(f,fo);
Rewrite(f);
If TT=False then Writeln(f,'#')
else
Begin
{Duong di cua Robot1}
i:=hm;
j:=cm;
Repeat
k:=A[i,j];
Case k of
1: s1:=s1+'D';
2: s1:=s1+'L';
3: s1:=s1+'U';
4: s1:=s1+'R';
End;
i:=i+Hi[k];
j:=j+Hj[k];
Until (i=1) and (j=1);
{Duong di cua Robot2}
i:=hm;
j:=cm;
A[i,j]:=lk;
Repeat
k:=A[i,j];
Case k of
1: s2:=s2+'D';
2: s2:=s2+'L';
3: s2:=s2+'U';
33
4: s2:=s2+'R';
End;
i:=i+Hi[k];
j:=j+Hj[k];
Until (i=M) and (j=N);
For i:=Length(s1) downto 1 do
Write(f,s1[i]);
Writeln(f);
For i:=Length(s2) downto 1 do
Write(f,s2[i]);
End;
Close(f);
End;
BEGIN
Doctep;
KT_Queue;
Loang_lop;
Ketqua;
END.

Bài 7: Chuyển bi
Người ta vẽ N vòng tròn (N≤100) và tô màu cho chúng (có thể có một số vòng tròn có cùng
màu), các vòng tròn được đánh số từ 1 tới N. Các cặp vòng tròn được nối với nhau bằng các
cung định hướng, mỗi cung có một màu nhất định. Các màu (của cung và của vòng tròn) được
đánh sốtừ 1 tới N.
Người ta chọn 3 số nguyên khác nhau L, K và Q (1 ≤ L,K,Q ≤ N). Đặt vào trong các vòng
tròn số L và K mỗi vòng một hòn bi, sau đó bắt đầu di chuyển bi theo nguyên tắc:
- Bi chỉ được chuyển theo cung có màu trùng với màu của vòng tròn chứa viên bi thứ hai;
- Bi chỉ được chuyển theo chiều cung;
- Hai viên bi không được đồng thời ở trong cùng một vòng tròn;
- Quá trình di chuyển kết thúc khi một trong hai viên bi tới vòng tròn Q.
Hãy xác định cách di chuyển để chấm dứt quá trình sau một số ít nhất các bước chuyển.
Dữ liệu vào là tệp văn bản bi.inp có cấu trúc:
- Dòng đầu ghi 4 số nguyên N, L, K, Q;
- Dòng thứ 2 ghi số N số nguyên C1, C2, …. , CN (Ci là màu của vòng tròn thứ i);
- Dòng thứ 3 ghi số nguyên nguyên M (0 ≤ M ≤ 10000);
- M dòng tiếp theo, mỗi dòng ghi 3 số nguyên A i, Bi, Di xác định cung màu Di đi từ vòng
tròn Ai tới vòng tròn Bi ;
- Các số trên mỗi dòng ghi cách nhau ít nhất là một ký tự trống.
Dữ liệu ra là tệp văn bản bi.out ghi số nguyên xác định số bước chuyển tối thiểu nếu kết thúc
được quá trình di chuyển. Nếu không kết thúc được quá trình di chuyển thì ghi ký tự ‘#’..

34

You might also like