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

QUAY LUI TRÊN MẢNG HAI CHIỀU

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

PHẦN I: PHẦN MỞ ĐẦU
I. Lý do chọn đề tài:
Ngôn ngữ lập trình là một trong những nội dung được đưa vào dạy chính thức
ở bộ môn Tin học ở nhà trường phổ thông. Như ta đã biết, các câu lệnh để giải
quyết bài toán Tin học được mô tả dưới các dạng tuần tự, rẽ nhánh và lặp.
Trong đó với các phép lặp, ta giải bài toán bằng cách thực hiện liên tiếp một số
các câu lệnh trong vòng lặp cho tới khi một điều kiện nào đó được thỏa mãn.
Một kỹ thuật lập trình được sử dụng để thay thế cho các phép lặp đó là kỹ
thuật đệ quy quay lui (Back tracking). Mặt khác, trong thực tế có rất nhiều bài
toán đòi hỏi sự lặp đi lặp lại một cách phức tạp. Và đệ quy quay lui cung cấp
cho ta cơ chế giải quyết bài toán phức tạp một cách đơn giản và dễ hiểu.
Đệ quy quay lui là một trong những chiến lược để giải quyết nhiều bài toán,
đặc biệt là các bài toán đòi hỏi liệt kê mọi cấu hình thoả mãn. Đặc biệt trong đề
thi học sinh giỏi Tỉnh các năm đệ quy quay lui luôn chiếm một phần quan trọng.
Đã có nhiều bài viết đề cập đến đệ quy quay lui và các vần đề xung quanh đệ quy
quay lui nhưng trong tài liệu này, tôi muốn đề cập tới “Đệ quy quay lui trên
mảng hai chiều” để làm đề tài sáng kiến kinh nghiệm của mình.
II. Mục đích nghiên cứu:
Đề tài: “Đệ quy quay lui trên mảng hai chiều” nhằm đạt mục đích sau:


Giúp cho bản thân có thể hiểu rõ hơn, sâu hơn về kỹ thuật đệ quy

quay lui, ứng dụng đệ quy quay lui để giải các bài toán trong Tin học, từ đó nâng
cao khả năng tự học, khả năng lập trình của bản thân.
− Nâng cao chất lượng bồi dưỡng học sinh giỏi môn Tin các kỳ thi.
− Góp phần làm đa dạng tài liệu nghiên cứu của tổ bộ môn.
III. Phương pháp nghiên cứu:
Khi nghiên cứu đề tài này tôi sử dụng các phương pháp sau:



Phương pháp nghiên cứu lý thuyết.

1




Sử dụng phương pháp thu thập thông tin và tổng hợp, phân tích, so

sánh dựa trên lý thuyết về đệ quy quay lui.


Áp dụng giải với một số bài toán cụ thể.

IV. Đối tượng nghiên cứu:
Đối tượng nghiên cứu của đề tài này đó chính là đệ quy quay lui và ứng
dụng của quay lui để giải các bài toán trong Tin học có liên quan tới mảng hai
chiều và các tài liệu, các thông tin liên quan.
Nội dung nghiên cứu được trình bày theo cấu trúc sau:
1. Nhắc lại kiến thức về đệ quy quay lui (Back tracking)
2. Bài toán kinh điển: Mã đi tuần
3. Ứng dụng quay lui để giải một số bài toán xử lý trên mảng hai chiều
Kết luận

2


PHẦN II: PHẦN NỘI DUNG
1. Nhắc lại kiến thức về đệ quy quay lui (Back tracking)
1.1. Tổng quan về đệ quy quay

lui
Về bản chất, tư tưởng của phương pháp là thử từng khả năng cho đến khi tìm
thấy lời giải đúng. Đó là một quá trình tìm kiếm theo độ sâu trong một tập hợp các
lời giải. Trong quá trình tìm kiếm, nếu ta gặp một hướng lựa chọn không thỏa mãn,
ta quay lui về điểm lựa chọn nơi có các hướng khác và thử hướng lựa chọn tiếp
theo. Khi đã thử hết các lựa chọn xuất phát từ điểm lựa chọn đó, ta quay lại điểm lựa
chọn trước đó và thử hướng lựa chọn tiếp theo tại đó. Quá trình tìm kiếm thất bại
khi không còn điểm lựa chọn nào nữa.
Quy trình đó thường được cài đặt bằng một hàm đệ quy mà trong đó mỗi thể
hiện của hàm lấy thêm một biến và lần lượt gán tất cả các giá trị có thể cho biến đó,
với mỗi lần gán trị lại gọi chuỗi đệ quy tiếp theo để thử các biến tiếp theo. Chiến
lược quay lui tương tự với tìm kiếm theo độ sâu nhưng sử dụng ít không gian bộ
nhớ hơn, nó chỉ lưu giữ trạng thái của một lời giải hiện tại và cập nhật nó.
Yếu tố mấu chốt trong một thủ tục đệ quy là điều kiện duyệt và điểm dừng. Với
đặc trưng của mảng hai chiều là phần tử mảng được xác định bằng hai giá trị hàng
và cột, thông thường chúng ta vẫn sử dụng cặp giá trị (i,j) (hàng, cột của 1 ô) làm
bước duyệt khi gọi thủ tục đệ quy.
1.2. Giải thuật tổng quát
Procedure Try(i); {Chọn thực hiện bước thứ i}
Begin
For CÁC PHƯƠNG ÁN CHỌN do
If CHỌN ĐƯỢC then
Begin
- THỰC HIỆN BƯỚC ĐI THỨ i;
- IF THÀNH CÔNG then THÔNG BÁO KẾT QUẢ
Else Try(i+1);
-HỦY BƯỚC ĐI THỨ i; {Quay lui}
End;

3



End;

- Thủ tục trên sẽ được khởi động bởi lệnh: Try(1);
2. Bài toán kinh điển: Mã đi tuần
2.1. Đề bài:
Mã đi tuần (hành trình của quân mã) là bài toán về việc di chuyển một
quân mã trên bàn cờ vua (8 x 8). Quân mã được đặt ở một ô trên một bàn cờ trống
nó phải di chuyển theo quy tắc của cờ vua để đi qua mỗi ô trên bàn cờ đúng
một lần. Hãy viết chương trình đặt quân mã vào một vị trí bất kỳ trên bàn cờ.
Tìm chu trình của quân mã để ghé thăm một lần duy nhất tất cả các ô còn lại
- Quy tắc di chuyển của quân mã trên bàn cờ vua: quân mã đi theo hình chữ
L

2.2. Phân tích bài toán:
Có rất nhiều lời giải cho bài toán này, chính xác là 26.534.728.821.064 lời giải
trong đó quân mã có thể kết thúc tại chính ô mà nó khởi đầu. Một hành trình như
vật được gọi là hành trình đóng.
Có những hành trình, trong đó quân mã sau khi đi hết tất cả 64 ô của bàn cờ
(kể cả ô xuất phát), thì từ ô cuối của hành trình không thể đi về ô xuất phát chỉ
bằng một nước đi. Những hành trình như vậy được gọi là hành trình mở.
Đối với bài toán mã đi tuần ta nhận thấy rằng, ban đầu tọa độ của quân mã là
(i,j) bất kỳ trên bàn cờ. Tại vị trí này theo quy luật di chuyển của quân mã trên bàn
cờ thì quân mã có tối đa 8 hướng đi tiếp theo. Rõ ràng để đi đến các lời giải ta
phải thử tất cả các trường hợp quân mã có thể đi đến ở bước 1. Với mỗi vị trí thử
như vậy ta lại thử tất cả các các trường hợp của quân mã có thể đi đến ở bước 2,…
4



Cứ tiếp tục như vậy cho đến khi quân mã đi qua tất cả các ô trên bàn cờ vua thì
dừng lại. Như vậy, tính chất đệ quy của bài toán đó thể hiện trong các phép thử
hướng đi của quân mã ở trên.
Kết quả ta tìm được nhiều chu trình đi khác nhau nhưng số bước đi đều là 63.
Sử dụng giải thuật đệ quy quay lui cho bài toán mã đi tuần, ta có:
- Try(k,i,j): Chọn thực hiện bước đi thứ k, với ô xuất phát là (i,j);
- Các phương án chọn: Có 8 phương án (tương ứng với 8 trường hợp có
thể đi đến của quân mã):
+ Kí hiệu phương án: m = 1, 2, …, 8.
+ Thiết lập hai mảng một chiều d,c lưu trữ tọa độ tương ứng 8 khả
năng di chuyển của quân mã:
d

-1

1

2

2

1

-1

-2

-2

Chỉ số m


1

2

3

4

5

6

7

8

c

-2

-2

-1

1

2

2


1

-1

Chỉ số m

1

2

3

4

5

6

7

8

Khi đó: phương án m là di chuyển mã đến ô (i+d[m], j+c[m]).
- Chọn được: nếu như ô (i+d[m], j+c[m]) quân mã chưa đi qua và 1<=
i+d[m]<=8 và 1<=j+c[m]<= 8.
- Thực hiện bước đi thứ k:
+ Tổ chức
1..8] A[i,j] =


lưu

trữ: Dùng mảng 2 chiều A có kích thước 8x8: A[1..8,
0 : nếu quân mã chưa đi qua ô (i,j)

k : nếu quân mã đã đi qua ô (i,j) ở bước thứ k
+ Lưu

vết: A([i+d[m], j+c[m]):=k.

- Thành công: k=64 (hoặc k>63).
- Thông báo kết quả: In mảng A.
- Lời gọi đệ quy tiếp theo: Try(k+1, i+d[m], j+c[m]).
5


- Hủy bước đi thứ k: A([i+d[m], j+c[m]):=0.
* Thủ tục đệ quy quay lui cho nài toán này được viết như sau:
Procedure Try(k,i,j);
Begin
for m:=1 to 8 do
If (A(i+d[m],j+c[m])=0) and ((i+d[m]) in S) and ((j+c[m]) in S)
Then
Begin
A(i+d[m], j+c[m]) = k;
If k=64 Then Xuat
Else Try(k+1, i+d[m], j+c[m]); A(i+d[m], j+c[m]) = 0;
End;
End;


2.3. Cài đặt chương trình:
Program Ma_di_tuan;
Const
d:array[1..8] of integer=(-2,-2,-1,1,2,2,1,-1);
c:array[1..8] of integer=(-1,1,2,2,1,-1,-2,-2);
Var
A:array[1..8,1..8] of integer;
S:set of 1..8;
i, j: integer;
{-----------------thủ tục nhập--------------------}
Procedure Nhap;
Begin
fillchar(A,sizeof(A),0);
writeln('Cho biet toa do ban dau cua ma: ');
readln(i,j);
A[i,j]:=1;
S:=[1,2,3,4,5,6,7,8];
End;
{----------------thủ tục in dữ liệu---------------}
Procedure xuat;

6


var x, y:integer; Begin
for x:=1 to 8 do
begin
for y:=1 to 8 do write(A[x,y]:5);
writeln;
end;

End;
{------------thủ tục đệ quy quay lui------------}
Procedure Try(k,i,j);
var m:byte;
Begin
for m:=1 to 8 do
If (A(i+d[m], j+c[m]) = 0) and ((i+d[m]) in S) and
((j+c[m]) in S) Then
Begin
A(i+d[m], j+c[m]) = k;
If k=64 Then Xuat Else Try(k+1, i+d[m], j+c[m]);
A(i+d[m], j+c[m]) = 0;
End;
End;
{-------------------------------------------------}
BEGIN
Nhap;
Try(2, i, j);
Readln;
END.

7


* Chạy thử chương trình: Dưới đây là một đường đi của quân mã:

3. Ứng dụng quay lui để giải một số bài toán xử lý trên mảng hai chiều
3.1. Ứng dụng quay lui để giải một số bài toán xử lý trên mảng hai chiều kích
thước NxN.
3.1.1. Kiểm tra có đường đi giữa 2 đỉnh của đồ thị

a, Đề bài: Sơ đồ đường đi giữa N điểm (có số hiệu 1, 2, …, N) trong thành phố X
được cho bởi ma trận A kích thước NxN. Mỗi phần tử của ma trận A chỉ có thể là 0
hoặc 1. Nếu phần tử ở hàng i, cột j của A bằng 1 thì có đường đi từ địa điểm i tới địa
điểm j, ngược lại nếu không có đường đi (1 ≤ i,j ≤ N ).
Hãy kiểm tra giữa hai địa điểm P, Q có đường đi hay không.
Dữ liệu vào là tệp văn bản LIENTHONG.INP có cấu trúc:
- Dòng đầu tiên ghi số N (0- N dòng tiếp theo, mỗi dòng gồm N số là các giá trị của ma trận A
- Dòng cuối cùng ghi 2 địa điểm P, Q. Các số cách nhau ít nhất một ký tự trống
Dữ liệu ra là tệp LIENTHONG.OUT ghi dòng thông báo “Co duong di:” cùng với
đường đi đầu tiên tìm được đi nối từ P sang Q hoặc dòng thông báo “Khong co
duong di”.

8


Ví dụ:
Tệp LIENTHONG.INP
4
0101
1001
0001
1110
13
6
011000
100100
100100
011010
001100

000000
16

Tệp LIENTHONG.OUT
Co duong di: 1->2->4->3

Khong co duong di

b, Phân tích: Ta coi mỗi điểm là một đỉnh của đồ thị thì bài này chính là duyệt
sâu trong đồ thị N đỉnh.
Để tìm đường đi từ P sang Q thì ta bắt đầu từ việc tìm đường đi từ P tới các điểm j
có đường nối trực tiếp với P, tức là A[p,j]=1 và chưa được xét (cx[j]=true) thì ta
đánh dấu là j đã được kết nạp. Nếu điểm j vừa kết nạp chính là Q thì thông báo có
đường đi và kết thúc việc tìm kiếm, ngược lại thì thử với điểm j vừa được kết nạp.
Lặp lại quá trình trên cho tới khi tất cả các đỉnh đã được xét tới. Vì bài toán này chỉ
cần kiểm tra có đường đi từ điểm P sang Q nên ta không cần trả lại trạng thái cho
đỉnh vừa được kết nạp.
Nếu sau khi quá trình lặp kết thúc, ta xét cx[Q] = True thì chứng tỏ không có
đường đi từ P sang Q.
c, Chương trình:
PROGRAM KIEM_TRA_LIEN_THONG_GIUA_2_DINH_P_Q_QUAY_LUI;
const fi='LIENTHONG.INP'; fo='LIENTHONG.OUT';
var x:array[1..100] of integer;
a:array[1..100,1..100] of integer;
N,K,I,j,p,q,dem:INTEGER;
Cx:array[1..100] of BOOLEAN;
f:text;

9



{---------Thu tuc doc du lieu---------}
procedure doc;
var i,j:integer;
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;
read(f,p,q);
close(f);
end;
{---------Thu tuc in ket qua---------}
procedure inkq;
var i:integer;f:text;
begin
Assign(f,fo); Rewrite(f);
write(F,'Co duong di:');
for i:=1 to dem-1 do

write(f,x[i], '->');

Write(f,X[dem]);
close(f);
end;
{---------Thu tuc try---------}
procedure try(d,c:integer);
var j:integer;

begin
for j:=1 to n do {xét các địa điểm của thành phố}
if (a[d,j]=1) and (cx[j]) then {nếu có đường đi từ d sang j và điểm
j chưa xét}
begin
cx[j]:=false;

dem:=dem+1;

x[dem]:=j;
{nếu điểm j vừa xét chính là điếm cuối Q thì in kết quả và kết thúc}
if j=Q then begin

inkq ; exit; end

else try(j,c); {ngược lại thì xét giữa điểm j và điểm cuối có
đường nối không}

10


end;
end;
BEGIN
doc; {Khoi tao}

FOR I:=1 TO N DO Cx[I]:=TRUE;

dem:=1; cx[P]:=false;x[dem]:=P;
Try(P,Q);

if cx[Q] then
begin assign(f,fo);rewrite(f);
write(f,'Khong co duong di');close(f);
end;
END.

3.1.2. Kiểm tra tính liên thông giữa nhiều cặp đỉnh của đồ thị
a, Đề bài: Sơ đồ đường đi giữa N điểm (có số hiệu 1, 2, …, N) trong thành phố X
được cho bởi ma trận A kích thước NxN. Mỗi phần tử của ma trận A chỉ có thể là 0
hoặc 1. Nếu phần tử ở hàng i, cột j của A bằng 1 thì có đường đi từ địa điểm i tới địa
điểm j, ngược lại nếu không có đường đi (1 ≤ i,j ≤ N ).
Hãy kiểm tra giữa hai địa điểm P, Q có đường đi hay không.
Dữ liệu vào là tệp văn bản LTN.INP có cấu trúc:
- Dòng đầu tiên ghi số N (0- N dòng tiếp theo, mỗi dòng gồm N số là các giá trị của ma trận A
- Các dòng tiếp theo mỗi dòng ghi 2 địa điểm P, Q bất kỳ
Các số cách nhau ít nhất một ký tự trống
Dữ liệu ra là tệp LTN.OUT ghi dòng thông báo “Co duong di” hoặc “Khong co
duong di” tương ứng với mỗi cặp địa điểm.
Ví dụ:
Tệp LTN.INP
5
01010
10010
00100
11100
00000
25
13


Tệp LTN.OUT
Khong co duong di tu 2 den 5
Co duong di tu 1 sang 3

11


b, Phân tích
Đối với bài này không chỉ có một cặp địa điểm nên việc đọc và ghi dữ liệu phải
thực hiện đồng thời.
Thủ tục Try (d,c) hoàn toàn tương tự bài 3.1.
Trong thủ tục Doc_xuly cần lưu ý điểm sau:
- Mỗi lần đọc cặp địa điểm thì thực hiện thủ tục Try(d,c), sau khi xét có đường đi
giữa 2 địa điểm vừa xét thì cần trả về trạng thái khởi tạo cho mảng đánh dấu cx để
xét tiếp cho các cặp địa điểm tiếp theo.
c, Chương trình
PROGRAM LIEN_THONG_GIUA_NHIEU_CAP_DINH_P_Q;
uses crt;
var a:array[1..100,1..100] of integer;
N,K,I,j,p,q:INTEGER;
Cx:array[1..100] of BOOLEAN;
f,g:text;
{---------Thu tuc try---------}
Procedure try(d,c:integer);
var j:integer;
begin
for j:=1 to n do
if (a[d,j]=1) and (cx[j]) then
begin
cx[j]:=false;

if j=q then
begin
assign(g,'LTN.OUT');append(g);
writeln(g,'Co duong di tu ',p,' sang ',q);
CLOSE(G);
exit;
end
else try(j,c);
end;
end;
{---------Thu tuc vua doc du lieu và xu ly---------}

12


Procedure doc_xuly;
var i,j:integer;
begin
assign(f,'LTN.INP');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;
FOR I:=1 TO N DO Cx[I]:=TRUE;
WHILE NOT EOF(F) DO
BEGIN
readln(f,p,q);
cx[p]:=false;
TRY(P,Q);

if cx[q] then
BEGIN
assign(g,'LTN.OUT');append(g);
writeln(g,'Khong co duong di tu ',p,' den ', q);
CLOSE(G);
END;
FOR I:=1 TO N DO Cx[I]:=TRUE; {sua lai trang thai ban dau cho
mang cx}
END;
close(f);
end;
BEGIN
clrscr;
doc_xuly;
END.

3.1.3. Liệt kê tất cả các đường đi từ địa điểm P đến Q trong thành phố (Bài 3
– Đề thi chọn học sinh giỏi Tin 12 – Hà Tĩnh năm 2011 – 2012)
a, Đề bài: Tương tự bài 3.1 nhưng yêu cầu là liệt kê tất cả các đường đi khác
nhau giữa hai đỉnh P và Q trong thành phố X. Số hiệu mỗi địa điểm xuất hiện không
quá một lần.
13


Ví dụ:
Tệp DUONG.INP
4
0101
1001
0001

1110
13

Tệp DUONG.OUT
1->2->4->3
1->4->3

b, Phân tích: Tương tự bài 3.1 nhưng có 3 điểm cần bổ sung:
- Để tìm hết tất cả các đường đi thì cần trả lại trạng thái cho điểm vừa kết nạp để
tiến tới việc xét các điểm khác nhằm tìm ra con đường đi mới.
- Cần có một biến ktra lưu kết quả có đường đi hay không phục vụ cho việc in ấn
kết quả. (Vì mỗi lần ta lại trả lại trạng thái cho điểm mới kết nạp nên cx[Q] cuối
cũng vẫn là True)
- Trong thủ tục in kết quả cần sử dụng thủ tục append để ghi dữ liệu.
c, Chương trình:
PROGRAM LIET_KE_DUONG_DI_GIUA_2_DINH_P_Q;
const fi='DUONG.INP'; fo='DUONG.OUT';
var x:array[1..100] of integer;
a:array[1..100,1..100] of integer;
N,K,I,j,p,q,dem:INTEGER;
Cx:array[1..100] of BOOLEAN;
f:text;Ktra:boolean;
{---------Thu tuc doc du lieu---------}
procedure doc;
var i,j:integer;
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;
read(f,p,q);

14


close(f);
end;
{---------Thu tuc in ket qua---------}
procedure inkq;
var i:integer;f:text;
begin
Assign(f,fo); Append(f);
write(F,'Co duong di:');
for i:=1 to dem-1 do

write(f,x[i], '->');

Writeln(f,X[dem]);
close(f);
end;
{---------Thu tuc try---------}
procedure try(d,c:integer);
var j:integer;
begin
for j:=1 to n do
if (a[d,j]=1) and (cx[j]) then
begin
cx[j]:=false;

dem:=dem+1;
x[dem]:=j;
if j=Q then begin ktra:=true;

inkq ; end

else try(j,c);
dem:=dem-1;
cx[j]:=true;
end;
end;
BEGIN
doc;
{Khoi tao}
FOR I:=1 TO N DO Cx[I]:=TRUE;
dem:=1; cx[P]:=false;x[dem]:=P;
ktra:=false; {ban dau cho rang khong co duong di}
Try(P,Q);
if ktra=false then
begin assign(f,fo);rewrite(f);

15


write(f,'Khong co duong di');close(f);
end;
END.

3.2. Ứng dụng quay lui để giải một số bài toán xử lý trên mảng hai chiều kích
thước MxN.

3.2.1. Đếm số miền liên thông, miền liên thông lớn nhất.
a, Đề bài: Cho ma trận A cấp MxN, trên mỗi ô A[i,j] của lưới ghi số 0 hoặc 1.
Hai ô được gọi là liên thông trực tiếp nếu nó chung cạnh và cùng giá trị 1, ngược lại
có giá trị 0. Hãy cho biết lưới ô vuông có bao nhiêu miền liên thông và miền liên
thông nào là lớn nhất (có nhiều ô có giá trị 1 nhất) cùng với chỉ số của các ô đó.
Dữ liệu vào: Tệp văn bản DTLT.INP có dạng:
- Dòng đầu tiên gồm 2 số M, N.
- M dòng tiếp theo mỗi dòng ghi N số 0 hoặc 1
Dữ liệu ra: Tệp văn bản DTLN.OUT có dạng:
- Dòng đầu tiên ghi số liên thông.
- Dòng tiếp theo ghi dòng thông báo diện tích vùng liên thông lớn nhất
- Các dòng tiếp theo ghi các vị trí của các ô thuộc vùng liên thông lớn nhất.
Mỗi số phân cách nhau ít nhất một ký tự trống
Ví dụ:
Tệp DTLT.INP
44
1100
0110
0011
1100

Tệp DTLT.OUT
So lien thong:2
Vung lien thong dien tich lon nhat=6
Tai cac vi tri:
11
12
22
23
33

34

b, Phân tích
Ta thấy rằng mỗi một ô tại vị trí [i,j] có thể liên thông với 4 ô xung quanh (Trên,
dưới, phải, trái) của nó. Vì thế sử dụng 2 hằng mảng một chiều thể hiện độ biến
thiên của hàng, cột:
16


dx:array[1..4] of integer=(-1,1,0,0);
dy:array[1..4] of integer=(0,0,1,-1);

{Trên,dưới,phải,trái}

Ta sẽ xét tất cả các ô có trong lưới MxN nếu có giá trị là 1 và chưa được xét thì
ghi nhận có một miền liên thông. Để xác định ô đã được xét hay chưa ta dùng một
mảng hai chiều b. Ban đầu cho b toàn số 0. Sau khi thực hiện việc kiểm tra tại ô
a[i,j] nào đó, nếu nó thuộc miền liên thông nào thì sẽ xác định lại giá trị ô b[i,j] bằng
chính chỉ số miền liên thông vừa tìm được, đồng thời tăng diện tích miền liên thông
(biến dem) lên 1 đơn vị.
Với mỗi miền liên thông ta so sánh với giá trị max, nếu số ô trong miền liên thông
vừa tìm lớn hơn max thì ta gán giá trị của biến dem cho max, chỉ số miền liên thông
lớn nhất csmax chính bằng solt. Từ ô này ta xét các ô khác xung quanh chưa được
xét và có giá trị bằng 1 để kết nạp ô đó vào miền liên thông vừa xác định được. Lặp
lại việc này cho tới khi hết các ô trong luới.
Lưu ý:
- Ta phải làm công việc bao quanh lưới MxN một loạt các số 0 để các ô ở biên
của lưới cũng xét tương tự như các ô khác.
- Để biết được ô [i,j] nào đó thuộc miền liên thông nào ta căn cứ vào mảng b để
xác định được miền liên thông nào là lớn nhất cùng vị trí các ô trong miền.

c, Chương trình
program VUNG_LIEN_THONG_LON_NHAT;
uses crt;
const fi='DTLT.INP';fo='DTLT.OUT';maxM=50;
dx:array[1..4] of integer=(-1,1,0,0);
dy:array[1..4] of integer=(0,0,1,-1);

{Len,xuong,phai,trai}

VAR a,b:array[0..maxM+1,0..maxM+1] of byte;
solt,m,n,k,h,dem,csMAX,max:integer;
f:text;
{---------Thu tuc doc du lieu---------}
PROCEDURE DOCTEP;
var i,j:integer;

17


begin
assign(f,fi);reset(f); readln(f,m,n);
fillchar(a,sizeof(a),0); {Tao mang a toan so 0}
for i:=1 to m do
begin
for j:=1 to n do read(f,a[i,j]);
readln(f);
end;
close(f);
end;
{---------Thu tuc try---------}

PROCEDURE Try(r,c:integer);
var i:integer;
begin
for i:=1 to 4 do

{xet 4 vi tri xung quanh diem (r,c)}

if (a[r+dx[i],c+dy[i]]=1) and (b[r+dx[i],c+dy[i]]=0) then
begin
dem:=dem+1;
b[r+dx[i],c+dy[i]]:=solt;
try(r+dx[i],c+dy[i]);
end;
end;
{---------Thu tuc xu ly---------}
PROCEDURE XULY;
var h,k:integer;
begin solt:=0;
for k:=1 to m do
for h:=1 to n do
begin
if (a[k,h]=1) and (b[k,h]=0) then
begin
solt:=solt+1;

b[k,h]:=solt;

dem:=1;
try(k,h);
IF dem>MAX THEN begin max:=dem;csMAX:=solt;end;

end;
end;

18


end;
{---------Thu tuc in ket qua---------}
PROCEDURE INKQ;
var i,j:integer;
Begin
assign(f,fo);rewrite(f);
writeln(F,'So lien thong:',solt);
writeln(F,'Vung lien thong co dien tich lon nhat=',max);
writeln(F,'Tai cac vi tri:');
FOR I:=1 TO M DO
FOR J:=1 TO N DO
IF B[I,J]=csMAX THEN WRITELN(F,I,' ',J);
close(f);
end;
BEGIN
clrscr;DOCTEP;
fillchar(b,sizeof(b),0); max:=0;
XULY;
INKQ;
END.

3.2.2. Bài toán tần số phát sóng: (Bài 3 - Đề thi chọn HSG Tin 12 năm học
2013 – 2014)
Người ta phân hoạch một vùng đất hình chữ nhật thành MxN ô vuông bởi các

đường kẻ song song với các đường biên của vùng đất đó. Vị trí của mỗi mảnh đất ô
vuông được xác định bởi cặp số (x, y) trong đó x là số thứ tự dòng tính từ trên xuống
dưới, y là thứ tự cột tính từ trái sang phải của mảnh đất ô vuông đó khi ta nhìn từ
một mặt phẳng song song phía trên mặt phẳng vùng đất. Tại mỗi ô vuông của vùng
đất được đặt một máy phát sóng vô tuyến điện tử. Mỗi ngày có thể phát ra các loại
sóng có dải tần A hoặc B; tại mỗi thời điểm phát sóng chỉ phát ra mỗi loại sóng có
một dải tần số nào đó mà thôi. Ta có thể di chuyển từ vị trí máy phát sóng này đến vị
trí máy phát sóng khác nếu hai máy phát sóng đó nằm trên hai mảnh đất ô vuông có
cạnh chung. Một vùng máy phát sóng là tập hợp các mảnh đất ô vuông có trên đó
đặt các máy phát sóng phát ra loại sóng có cùng dãy tần số. Việc xác định hai máy
19


phát sóng nào đó nằm trên hai mảnh đất ô vuông có cùng một vùng phát sóng tại
một thời điểm phát sóng nào đó hay không có ý nghĩa cho việc lên kế hoạch phát
sóng trong lần phát sóng kế tiếp.
Cho trước vị trí của hai mảnh đất ô vuông, hãy xác định hai mảnh đất đó có cùng
một vùng phát sóng tại một thời điểm phát sóng nào đó hay không?
Dữ liệu vào là tệp văn bản TSPS.INP có cấu trúc:
− Dòng đầu tiên ghi 2 số M, N (1≤M≤100, 1≤N≤100)
− M dòng tiếp theo, mỗi dòng ghi N ký tự ‘A’ hoặc ‘B’ nếu máy phát sóng đặt
trên mảnh đất ô vuông tương ứng phát sóng loại sóng có dải tần số A hoặc B
trong một lần phát sóng.
− Các dòng tiếp theo, mỗi dòng ghi 4 số mô tả vị trí của hai mảnh đất ô vuông
mà ta cần kiểm tra chúng có cùng một vùng phát sóng hoặc không. 2 số đầu
ghi thứ tự dòng và số thứ tự cột của mảnh đất ô vuông thứ nhất, 2 số sau là
thứ tự dòng và số thứ tự cột của mảnh đất ô vuông thứ hai.
− Các ký tự trên cùng một dòng được ghi liên tiếp nhau. Các số trên cùng một
dòng được ghi cách nhau ít nhất một ký tự trống.
Dữ liệu ra là tệp TSPS.OUT có số dòng là số cặp mảnh đất ô vuông mà ta cần

kiểm tra xem chúng có cùng một vùng phát sóng hay không? Mỗi dòng ghi một số
là số 1 hoặc là số 0 nếu cặp mảnh đất ô vuông tương ứng có cùng một vùng phát
sóng hay không.
Ví dụ:
Tệp TSPS.INP
46
AABBAA
BAABAA
BBAABB
AABBBB
1234
2316

Tệp TSPS.OUT
1
0

b, Phân tích

20


Bài toán này giải quyết hoàn toàn tương tự như bài 3.1.2 – Kiểm tra tính liên
thông giữa nhiều cặp đỉnh của đồ thị. Ở đây chỉ khác là ma trận kích thước MxN nên
thủ tục Try phải bổ sung các tham số là chỉ số dòng và cột của hai điểm.
c, Chương trình
program Bai_3_HSG12_2013_2014;
uses crt;
const fi='TSPS.INP';fo='TSPS.OUT';maxM=100;
dx:array[1..4] of integer=(-1,1,0,0);

dy:array[1..4] of integer=(0,0,1,-1);

{Len,xuong,phai,trai}

VAR a:array[0..maxM+1,0..maxM+1] of CHAR;
CX:array[0..maxM+1,0..maxM+1] of boolean;
m,n,p,q,k,h:integer;
f,g:text;
{---------Thu tuc try---------}
PROCEDURE TRY(X1,Y1,X2,Y2:integer);
var i:integer;
begin
for i:=1 to 4 do {xet 4 vi tri xung quanh diem (X1,Y1)}
if (a[X1+dx[i],Y1+dy[i]]=a[X1,Y1]) and (CX[X1+dx[i],Y1+dy[i]])
then
begin
CX[X1+dx[i],Y1+dy[i]]:=FALSE;
IF (X1+dx[i]=X2) AND (Y1+dy[i]=Y2) THEN
BEGIN
ASSIGN(G,Fo);append(g);
WRITELN(G,'1');
CLOSE(G);
END
ELSE

TRY(X1+dx[i],Y1+dy[i],X2,Y2);

end;
end;
{---------Thu tuc vua doc va xu ly---------}

Procedure doc_XULY;
var i,j:integer;
begin

21


assign(f,fi);reset(f);
readln(f,M,n);
fillchar(a,sizeof(a),0); {Tao mang a toan so 0}
for i:=1 to M do
begin for j:=1 to N do read(f,a[i,j]);
readln(f);
end;
FILLCHAR(CX,SIZEOF(CX),TRUE);
WHILE NOT EOF(F) DO
BEGIN
readln(f,p,q,K,H);
cx[p,Q]:=false;
TRY(P,Q,K,H);
if cx[K,H] then
begin assign(g,fo); append(g); writeln(G,'0');close(g);end;
FILLCHAR(CX,SIZEOF(CX),TRUE); {sua lai trang thai ban dau cho
mang cx}
END;
close(f);
end;
BEGIN clrscr; doc_XULY; END.

3.2.3. Trò chơi Sukudo

Sukudo là trò chơi đã thịnh hành từ lâu và rất phổ biến. Sudoku là một loại trò
chơi lôgic và cách chơi là điền số từ 1 đến 9 vào những ô trống sao cho mỗi cột dọc,
mỗi hàng ngang, mỗi phân vùng nhỏ (ô 3x3) có đủ các số từ 1 đến 9 mà không được
lặp lại.
Bảng câu đố hình vuông, mỗi chiều có 9 ô nhỏ, hợp thành 9 cột, 9 hàng và được
chia thành 9 ô lớn 3x3. Một vài ô nhỏ được đánh số, đó là những manh mối duy nhất
để bạn tìm lời giải. Tuỳ theo mức độ nhiều hay ít của các manh mối, các câu đố
được xếp loại dễ, trung bình, khó hay cực khó. Ngoài ra, còn những bảng như
16x16, 25x25 hay thậm chí 100x100.

22


3
6
4
1
9
2
8
7
5

1
5
9
7
8
3
2

6
4

2
8
7
4
5
6
9
1
3

6
9
1
2
4
5
7
3
8

8
2
5
3
7
1
4

9
6

7
4
3
8
6
9
5
2
1

4
1
2
5
3
7
6
8
9

9
3
8
6
2
4
1

5
7

5
7
6
9
1
8
3
4
2

Một ví dụ về trò chơi Sudoku
Ở đây tôi muốn đề cập tới việc giải SUKUDO kích thước 9x9 ở mức độ dễ bằng
quay lui. Để giải trò chơi này ta cần có:
- Mảng a: lưu bảng khởi tạo ban đầu.
- Mảng ah: ah[i,j]=true, nếu số j đã có trong hàng i, ngược lại ah[i,j]=false
- Mảng ac: ac[i,j]=true, nếu số j đã có trong cột i, ngược lại ac[i,j]=false
- Mảng 3 chiều ac: dùng để lưu việc kiểm tra số i nào đó thuộc vùng kích thước
(3x3) nào. Nếu đã có trong vùng đó thì av[xv,yv, i] nhận giá trị true, ngược lại nhận
giá trị false.
Trong khi đọc dữ liệu từ tệp văn bản chứa dữ liệu vào là bảng trò chơi ban đầu,
nếu giá trị tại ô nào đó khác 0 thì ta xác định lại trạng thái của các ô trong ma trận
ah, ac và av.
Việc xác định số a[i,j] nào đó thuộc phân vùng (3x3) dựa vào công thức:
xv:=((i-1)div 3)+1;
yv:=((j-1)div 3)+1;

Trong đó xv là chỉ số dòng của vùng, yv là chỉ số cột của vùng (1≤xv, yv≤3).

Thủ tục Try(x,y,stop) : Sẽ thử đặt lần lượt các số từ 1 - 9 vào ô ở hàng x, cột y,
nếu vẫn còn ô trống - ô chưa được đặt số nào.
23


- Tìm ô trống đầu tiên, xác định ô đó thuộc phân vùng (3x3) nào bằng công thức ở
trên.
- Duyệt các số i chạy từ 1 – 9 nếu số đó chưa thuộc hàng x, chưa thuộc cột y và
phân vùng (xv, yv) thì ghi nhận thử số i này vào ô trống đó.
- Nếu ô trống này là ô cuối cùng của cột cuối, hàng cuối thì kết thúc việc điền các
giá trị. Ngược lại chưa phải hàng cuối thì gọi thủ tục Try cho ô ở cột đầu tiên của
dòng tiếp theo tức là Try(x+1,1,stop), chưa phải là cột cuối thì gọi thủ tục Try
cho vị trí ô kế tiếp cùng hàng tức là Try(x,y+1,stop) .
- Lặp đi lặp lại quá trình trên với các ô trống mới, nếu chưa giải ra thì quay lui để
điền lại các khả năng khác. Đồng thời đánh dấu lại trạng thái ở các mảng a, ah, ac và
av.
Cài đặt chương trình:
program sudoku;
uses crt;
var
a:array[1..9,1..9] of byte;
ah,ac:array[1..9,1..9] of boolean;
av:array[1..3,1..3,1..9] of boolean;
stop:boolean;
{----------------------------------------}
procedure DOCTEP;
var f:text; i,j,xv,yv,k:byte;
begin
assign(f,'sudoku.inp');
reset(f);

for i:=1 to 9 do
for j:=1 to 9 do
begin
read(f,a[i,j]);
if a[i,j]<>0 then
begin
xv:=((i-1)div 3)+1;
yv:=((j-1)div 3)+1;

24


ah[i,a[i,j]]:=true; {so a[i,j] da co trong hang i}
ac[j,a[i,j]]:=true;

{so a[i,j] da co trong cot j}

av[xv,yv,a[i,j]]:=true; {so a[i,j] da co trong phan vung
(3x3) xc,yc}
end;
end;
close(f);
end;
{----------------------------------------}
Procedure INKQ;
var i,j:byte; f:text;
begin
assign(f,'sudoku.out');rewrite(f);
for i:=1 to 9 do
begin

for j:=1 to 9 do

write(f,a[i,j],' ');

writeln(f);
end;
close(f);
end;
{----------------------------------------}
Procedure Try(x,y:byte;var stop:boolean);
var
i,xv,yv:byte; ktra:boolean;
begin
if a[x,y]<>0 then
begin
repeat
inc(y); {thu cot ke ben}
if y=10 then
begin {xuong hang ke tiep}
y:=1;
inc(x);
end;
stop:=x=10;
until(a[x,y]=0)or(stop);
if not(stop) then try(x,y,stop);

25



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×