Professional Documents
Culture Documents
CĐ Loang
CĐ Loang
-----------------
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].
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:
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.
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.
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.
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.
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.
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:
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.
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