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

SKKN môn Tin học_ Thuật toán loang

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 (608.85 KB, 78 trang )

MỤC LỤC
A. PHẦN MỞ ĐẦU 2
I. Lí do chọn đề tài 2
II. Đối tượng nghiên cứu 2
III. Phạm vi nghiên cứu 2
IV. Mục đích nghiên cứu 2
V. Nhiệm vụ nghiên cứu 2
VI. Giả thiết khoa học 3
VII. Phương pháp nghiên cứu 3
VIII. Dự kiến đóng góp của đề tài 3
B. NỘI DUNG 4
I. Cơ sở lý luận và thực tiễn của đề tài 4
1. Cơ sở lý luận 4
2. Cơ sở thực tiễn 4
II. THUẬT TOÁN LOANG VÀ ỨNG DỤNG TÌM ĐƯỜNG ĐI TỐI ƯU TRÊN LƯỚI Ô VUÔNG 5
1. Thuật toán Loang 5
2. Áp dụng thuật toán loang tìm đường đi ngắn nhất trên lưới ô vuông 7
III. THUẬT TOÁN QUY HOẠCH ĐỘNG VÀ ỨNG DỤNG TÌM ĐƯỜNG ĐI TỐI ƯU TRÊN LƯỚI Ô VUÔNG 44
1. Thuật toán Quy hoạch động 44
2. Áp dụng thuật toán quy hoạch động giải một số bài tập tìm đường đi tối ưu trên lưới ô vuông 44
IV. BÀI TẬP TỰ LUYỆN 71
V. Đánh giá bài viết 76
VI. Quá trình thực nghiệm sư phạm 76
C. PHẦN KẾT LUẬN 77
1. Ý nghĩa của đề tài 77
2. Kiến nghị 77


A. PHẦN MỞ ĐẦU
I. Lí do chọn đề tài
Qua thực tiễn giảng dạy bộ môn tin học ở THPT, đặc biệt quá trình bồi dưỡng học


sinh dự thi chọn HSG tỉnh gặp một số khó khăn nhất định một trong số đó là tài liệu chọn
lọc phục vụ cho việc ôn thi còn hạn chế. Từ thực tế tôi nhận thấy để đạt kết quả cao học
sinh phải nhận dạng được bài toán và áp dụng các thuật toán phù hợp. Trong các đề thi
thường có 1 câu về tìm đường đi tối ưu trên lưới ô vuông. Để giải quyết được bài toán
này không chỉ đòi hỏi học sinh có kiến thức về mảng 2 chiều mà còn phải nắm vững
thuật toán loang, quy hoạch động, kỷ năng, tư duy và tính sáng tạo trong lập trình.
Kiến thức về loang, quy hoạch động trên lưới ô vuông không chỉ giúp học sinh khá
giỏi có thêm một công cụ không thể thiếu khi làm bài thi mà còn giúp các em nắm được
nhiều kiến thức khoa học thực tiễn, liên quan đến nhiều vấn đề thường gặp trong cuộc
sống như việc thiết kế, tìm kiếm đường đi tối ưu…
Đối với một số giáo viên mới ra trường, kinh nghiệm bồi dưỡng học sinh giỏi còn
hạn chế thì đây là một trong những nguồn tài liệu bổ ích, rút ngắn được quá trình tìm
kiếm, nghiên cứu mà vẫn đạt được nhiều kiến thức chuyên môn.
Vì vậy tôi chọn đề tài “Đường đi tối ưu trên lưới ô vuông” với mong muốn giúp
học sinh, một số giáo viên có thêm tài liệu để có thể hiểu sâu và áp dụng giải dạng bài
toán này.
II. Đối tượng nghiên cứu
- Cách lưu trữ lưới ô vuông trên máy tính
- Lý thuyết về thuật toán loang, thuật toán Quy hoạch động và áp dụng để giải các
bài toán tìm đường đi tối ưu trên lưới ô vuông.
III. Phạm vi nghiên cứu
- Tìm hiểu về thuật toán loang và cách áp dụng giải bài toán tìm đường đi ngắn nhất
trên lưới ô vuông.
- Tìm hiểu về thuật toán quy hoạch động và cách áp dụng giải bài toán tìm đường đi
tối ưu trên lưới ô vuông.
IV. Mục đích nghiên cứu
Giúp học sinh nhận dạng và áp dụng giải quyết một số bài toán tìm đường đi tối ưu
trên lưới ô vuông bằng thuật toán loang và quy hoạch động.
V. Nhiệm vụ nghiên cứu
- Tìm hiểu các kiến thức về mảng 2 chiều, thuật toán loang, quy hoạch động.


2


- Đưa ra được các dạng bài tập về đường đi tối ưu trên lưới ô vuông và cách giải
quyết.
VI. Giả thiết khoa học
Từ kiến thức Sách giáo khoa và vận dụng thêm phần kiến thức về Loang, Quy
hoạch động qua đây giáo viên rèn luyện tư duy và kỹ năng giải các bài toán và kỹ năng
lập trình cho học sinh.
VII. Phương pháp nghiên cứu
- Phương pháp nghiên cứu lý thuyết: nghiên cứu sách, báo và các tài liệu điện tử,
website về lập trình.
- Phương pháp phỏng vấn chuyên gia: tiếp thu ý kiến cố vấn, đánh giá của các giáo
viên có kinh nghiệm.
- Tham khảo ý kiến của đồng nghiệp.
- Phương pháp thực nghiệm: Áp dụng vào giảng dạy, bồi dưỡng học sinh giỏi Tin
học khối 11,12.
VIII. Dự kiến đóng góp của đề tài
- Về lý luận: Đã hệ thống được kiến thức, dạng bài tập về tìm đường đi tối ưu trên
lưới ô vuông. Các loại bài tập có tính phân hóa cao, chú trọng phát triển cả tư duy lập
trình.
- Về thực tiễn: Sử dụng đề tài làm tài liệu tham khảo để giảng dạy bộ môn tin học,
đặc biệt là quá trình bồi dưỡng, luyện thi học sinh giỏi.

3


B. NỘI DUNG
I. Cơ sở lý luận và thực tiễn của đề tài

1. Cơ sở lý luận
Trong quá trình dạy và học bộ môn tin học lớp 11, việc nắm bắt tư tưởng thuật toán,
lựa chọn và cài đặt chương trình là yếu tố cốt lõi. Việc nhận dạng thuật toán, cài đặt
chương trình giúp củng cố đào sâu, mở rộng những kiến thức cơ bản của thuật toán, xây
dựng kỹ năng kỹ xảo vận dụng lý thuyết vào thực tiễn. Đây cũng là một biện pháp hữu
hiệu để phát triển năng lực tư duy của học sinh.
Dạy học sinh THPT lập trình là một công việc khó khăn và ở đó bộc lộ rõ nhất trình
độ của người giáo viên trong việc hướng dẫn hoạt động trí tuệ của học sinh. Trong số đó
việc bồi dưỡng học sinh giỏi vừa có những thuận lợi và khó khăn nhất định. Thuận lợi ở
chỗ những học sinh trong đội tuyển đa số có năng lực tư duy thuật toán từ mức khá trở
lên. Tuy vậy để học sinh có thể nắm bắt tốt nội dung kiến thức, giải quyết tốt các bài toán
khi tham gia các kỳ thi thì quan trọng nhất là các em phải nắm vững được thuật toán, biết
lựa chọn thuật toán phù hợp với bài toán và kỷ năng cài đặt chương trình. Vì vậy trong
quá trình giảng dạy cần phân dạng các bài toán và hướng dẫn học sinh sử dụng các thuật
toán để cài đặt các dạng bài toán đó. Trong tin học có rất nhiều dạng bài khác nhau, bài
toán tìm đường đi tối ưu trên lưới ô vuông là một trong những số đó. Và việc tìm hiểu
phương pháp giải quyết bài toán này là vấn đề quan trọng với cả giáo viên và học sinh để
dạy và học tốt môn Tin học.
2. Cơ sở thực tiễn
Bài toán nghiên cứu về đường đi tối ưu trên lưới ô là dạng bài tập hết sức quen
thuộc với các em học sinh tham gia các lớp bồi dưỡng luyện thi học sinh giỏi. Đặc biệt
trong các đề thi học sinh giỏi tỉnh và thi Olympic Tin học, dạng bài này thường xuyên
xuất hiện. Các bài toán thường gặp nhất đó là: Tìm đường đi ngắn nhất, tìm đường đi tối
ưu với chi phí thấp nhất, cao nhất…
Tuy nhiên, trong chương trình sách giáo khoa Tin học 11 không có nội dung này để
học sinh được tiếp cận. Với lí do đó, trong đề tài này của mình tôi đã trình bày cách nhận
dạng, sử dụng thuật toán loang và quy hoạch động để giải bài toán tìm đường đi tối ưu
trên lưới ô vuông một cách có hệ thống nhằm giúp cho học sinh có thể tiếp cận và giải
quyết được bài toán này.
Trong đề tài này tôi đưa ra 2 thuật toán cơ bản thường được sử dụng để giải quyết

dạng bài toán này. Đó là thuật toán loang và quy hoạch động. Đối với mỗi bài toán chúng
ta cần nhận dạng và sử dụng thuật toán phù hợp để chương trình cài đặt có hiệu quả cao
nhất.
4


II. THUẬT TOÁN LOANG VÀ ỨNG DỤNG TÌM ĐƯỜNG ĐI TỐI ƯU TRÊN
LƯỚI Ô VUÔNG
1. Thuật toán Loang
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). Tư tưởng của thuật toán tím kiếm theo chiều rộng là “lập lịch”
duyệt các đỉnh. Việc thăm một đỉnh sẻ lên lịch duyệt các đỉnh nối từ nó sao cho thứ tự
duyệt là ưu tiên theo chiều rộng (đỉnh nào gần đinh xuất phát s hơn sẻ được duyệt trước).
Đầu tiên ta thăm đỉnh s. Việc thăm đỉnh s sẻ phát sinh thứ tự thăm các đỉnh u 1, u2, …nối
từ s (những đỉnh gần s nhất). Tiếp theo ta thăm đỉnh u 1, khi thăm đỉnh u1 sẻ lại phát sinh
yêu cầu thăm những đỉnh v1, v2,..nối từ u1. Nhưng rõ ràng những đỉnh v này xa đỉnh s hơn
đỉnh u nên chúng chỉ được thăm khi tất cả các đỉnh u đã được thăm. Tức là thứ tự duyệt
đỉnh sẻ là s, u1, u2, …,v1, v2,…Thuật toán tìm kiếm theo chiều rộng sử dụng một danh
sách để chứa những đỉnh đang “chờ” thăm. Tại mỗi bước ta thăm các đỉnh gần đầu danh
sách, loại nó ra khỏi danh sách và cho những đỉnh chưa “xếp hàng” kề với nó xếp hàng
thêm vào cuối danh sách. Thuật toán sẻ kết thúc khi danh sách rỗng.
Vì nguyên tắc vào trước ra trước, danh sách chứa những đỉnh đang chờ thăm được
tổ chức dưới dạng hàng đợi (Queue).
 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(s); Tìm kiếm theo chiều rộng bắt đầu từ đỉnh s
Begin
QUEUE =


; {Khởi tạo QUEUE ban đầu là rỗng}

QUEUE <= s; {Nạp đỉnh s vào QUEUE}
Chuaxet[s]:=False;{Đỉnh s nạp vào QUEUE là đã xét rồi => cờ của s 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 }
5


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]:=s; {Ban đầu nạp đỉnh s vào Q}
Để nạp thêm đỉnh u nào đó vào Q ta thực hiện:
Cuoi:=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);
Trong bài toán tìm đường đi ngắn nhất trên lưới ô vuông dữ liệu kiểu hàng đợi
thường được sử dụng là mảng 1 chiều kiểu bản ghi (kiểu bản ghi chứa thông tin về chỉ số
hàng và cột).
Ví dụ: Type hc=record
H,c:integer;
End;
Var q:array[1..1000] of hc;
Hoặc cũng có thể sử dụng 2 hàng đợi là mảng 1 chiều, trong đó mỗi hàng
đợi lưu chỉ số hàng và cột tương ứng
6


Ta có cách khai báo như sau:
var

qx:array[1..1000000] of longint;{Queue chỉ số hàng}
qy:array[1..1000000] of longint; {Queue chỉ số cột}


2. Áp dụng thuật toán loang tìm đường đi ngắn nhất trên lưới ô vuông
Dạng 1: Bài toán tìm đường đi đơn giản.
Đây là một số bài tập áp dụng thuật toán loang để tìm đường đi ngắn nhất. Tuy
nhiên yêu cầu của đề bài chưa phức tạp nên chỉ cần nắm vững kỷ thuật cài đặt thuật toán
loang sẻ giải quyết được.
Bài 1. Gặm cỏ
Một đồng cỏ hình chữ nhật thành các ô vuông nhỏ với R (1 <= R <= 100) hàng và C
(1 <= C <= 100) cột. Trên mỗi ô được đánh dấu với ký hiệu ‘*’ là đá và ‘.’ là cỏ. Một chú
bò đứng ở vị trí có toạ độ R_b,C_b và muốn ăn cỏ theo cách của mình, từng ô vuông một
và trở về chuồng ở ô 1,1 ; bên cạnh đó đường đi này phải là ngắn nhất.
Chú bò có thể đi từ 1 ô vuông sang 4 ô vuông khác kề cạnh.
Dưới đây là một bản đồ ví dụ [với đá ('*'), cỏ ('.'), chuồng bò ('B'), và chú bò ('C') ở
hàng 5, cột 6 và một bản đồ cho biết hành trình tối ưu của chú bò, đường đi được dánh
dấu bằng chữ ‘m’.
Bản đồ
123 456
1B. . . *.
2. . * . . .
3. ** . *.
4. . ** *.
5*. . * .C

Đường đi tối ưu
1
2
3
1B m
m
2.
.

*
3.
*
*.
4.
.
*
5*
.
.

4
m
m
.
*
*

5
*
m
*
*
.

6
.
m
m
m

m

Cho bản đồ, hãy tính xem có bao nhiêu ô cỏ mà chú bò sẽ ăn được trên con đường
ngắn nhất trở về chuồng
Dữ liệu: Tệp gamco.inp
Dòng 1: 2 số nguyên cách nhau bởi dấu cách: R và C
Dòng 2..R+1: Dòng i+1 mô tả dòng i với C ký tự
Kết quả: Tệp gamco.out
Một số nguyên duy nhất là số ô cỏ mà chú bò ăn được trên hành trình ngắn nhất trở
về chuồng.
Gamco.inp
56
B...*.
..*...
.**.*.
..***.
*..*.C

Gamco.out
9

7


Ý tưởng:
Thực hiện loang tại vị trí của bò để tìm đường đi ngắn nhất về tại vị trí chuồng.
Dùng mảng a đánh dấu các ô đã đi, thực hiện loang theo 4 hướng, nếu tại lần duyệt nào
đó gặp ô (1,1) thì lập tức thông báo kết quả và dừng chương trình. Để đếm được số bước
đi, ta sẽ dùng 1 biến đếm và thêm 1 biến hỗ trợ trong khi duyệt hàng đợi có tác dụng
nhận biết các ô có cùng số bước đi.

Chương trình
Program gamco_loang;
Const
fi='gamco.inp';
fo='gamco.out';
var
a:array[0..101,0..101] of boolean;
qx,qy:array[1..10000] of byte;
x:array[1..4] of shortint=(-1,0,1,0);
y:array[1..4] of shortint=(0,-1,0,1);
i,j,k,r,c,t,rb,rc:word;
f:text;
procedure doc;
var
ch:char;
begin
assign(f,fi);
reset(f);
readln(f,r,c);
fillchar(a,sizeof(a),false);
for i:=1 to r do
begin
for j:=1 to c do
begin
read(f,ch);
if ch='.' then a[i,j]:=true;
if ch='C' then
begin
rb:=i;
rc:=j;

end;
end;
readln(f);
end;
close(f);
a[1,1]:=true;
end;
procedure ketqua;
begin
assign(f,fo);
rewrite(f);
8


writeln(f,t);
close(f);
halt;

end;
procedure xuly;
var dau,cuoi,se:integer;
begin
dau:=1;
cuoi:=1;
qx[1]:=rb;
qy[1]:=rc;
t:=0;
while dau<=cuoi do
begin
inc(t);

se:=cuoi;
while dau<=se do
begin
for k:=1 to 4 do
if a[qx[dau]+x[k],qy[dau]+y[k]] then
begin
a[qx[dau]+x[k],qy[dau]+y[k]]:=false;
if (qx[dau]+x[k]=1) and (qy[dau]+y[k]=1) then
ketqua;
inc(cuoi);
qx[cuoi]:=qx[dau]+x[k];
qy[cuoi]:=qy[dau]+y[k];
end;
inc(dau);
end;
end;
end;
begin
doc;
xuly;
end.
Dạng 2: Bài toán tìm đường đi tối ưu có điều kiện.
Tùy thuộc vào yêu cầu của bài toán mà trong quá trình thực hiện loang chúng ta
phải linh hoạt sử dụng các kỷ thuật cài đặt để đáp ứng.
Bài 1. ĐƯỜNG ĐI
Cho lưới ô vuông gồm n dòng, n cột chứa các giá trị 0 hoặc 1. Từ một ô có giá trị 0
được phép đi sang một ô có giá trị 0 và có chung cạnh với ô đó. Không được đi vào bất
cứ ô nào có giá trị 1. Độ dài đường đi được xác định bởi số các ô vuông thuộc đường đi
đó. Đường đi ngắn nhất là đường đi có độ dài nhỏ nhất.


9


Một người xuất phát từ một ô có giá trị 0 trong lưới. Hãy tìm đường đi ngắn nhất để
người đó đi ra ngoài, tức là đi đến một ô có giá trị 0 nằm ở biên của lưới (ô có ít nhất một
cạnh nằm ở biên của lưới)
Input: Cho trong tệp văn bản DUONGDI.INP gồm:


Dòng đầu chứa số nguyên dương n cho biết lưới có n hàng, n cột. (n<=1000)



Dòng thứ 2 chứa 2 số x0 và y0 là số dòng và số cột của ô xuất phát (1


N dòng tiếp theo mỗi dòng ghi n số 0 hoặc 1 lần lượt là giá trị các ô của lưới



Các số trên cùng dòng cách nhau ít nhất 1 khoảng trắng

Output : xuất ra tập tin văn bản DUONGDI.OUT gồm một dòng ghi đường đi ngắn
nhất. Trường hợp không có đường đi ra ngoài thì ghi số -1.
Ví dụ:
DUONGDI1.INP
7
4 4
1 1 1 1 1 1 1

1 1 0 0 1 0 0
1 1 1 0 0 0 1
1 0 0 0 1 0 1
1 0 1 1 1 0 1
1 0 0 1 0 0 1
1 1 0 1 0 1 1

DUONGDI1.OUT
6

Ý tưởng: Sử dụng thuật toán loang từ vị trí xuất phát ra biên của lưới
- Hướng loang: Bài toán cho biết từ một ô giá trị 0 được loang sang các ô chung
cạnh nên ta sẻ thực hiện loang theo 4 hướng. Từ ô (i,j) có thể loang sang 1 trong 4 ô (i1,j); (i,j-1); (i+1,j) hoặc (i,j+1) với điều kiện ô được loang đến có giá trị 0.
- Kết thúc loang: Bắt gặp 1 ô (x,y) có giá trị 0 nằm ở biên của lưới. Thuộc 1 trong 4
trường hợp sau: x=1; x=m; y=1; y=n.
Program duongdi;
const fi='duongdi.inp';
fo='duongdi.out';
nmax=1000;
hi:array[1..4] of integer=(-1,0,1,0);
hj:array[1..4] of integer=(0,-1,0,1);
type hc=record
h,c:integer;
end;
var a:array[0..nmax+1,0..nmax+1] of byte;
f:text;
q:array[1..nmax*nmax] of hc;
chon:array[1..nmax,1..nmax] of boolean;
n,i,xo,yo:integer;
10



procedure doc;
var i,j:integer;
s:string;
ch:char;
begin
assign(f,fi);
reset(f);
readln(f,n);
readln(f,xo,yo);
for i:=1 to n do
begin
for j:=1 to n do
read(f,a[i,j]);
readln(f);
end;
for i:=0 to n+1 do
begin
a[0,i]:=2;
a[n+1,i]:=2;
a[i,0]:=2;
a[i,n+1]:=2;
end;
end;
procedure loang(xo,yo:integer);
var dau,cuoi,r,x,y,xi,yi:integer;
begin
dau:=1;
cuoi:=1;

q[dau].h:=xo;
q[dau].c:=yo;
chon[xo,yo]:=false;
a[xo,yo]:=1;
while dau<=cuoi do
begin
x:=q[dau].h;
y:=q[dau].c;
inc(dau);
if (x=1) or (x=n) or (y=1) or (y=n) then
begin
writeln(f,a[x,y]);
close(f);
halt;
end;
for r:=1 to 4 do
begin
xi:=x+hi[r];
yi:=y+hj[r];
11


if (chon[xi,yi]) and (a[xi,yi]=0) then
begin
inc(cuoi);
q[cuoi].h:=xi;
q[cuoi].c:=yi;
a[xi,yi]:=a[x,y]+1;
end;
end;

end;
end;
begin
doc;
assign(f,fo);
rewrite(f);
fillchar(chon,sizeof(chon),true);
loang(xo,yo);
writeln(f,-1);
close(f);
end.
Bài 2. Quân tượng
Xét bàn cờ vuông kích thước n×n. Các dòng được đánh số từ 1 đến n, từ dưới lên
trên. Các cột được đánh số từ 1 đến n từ trái qua phải.
Ô nằm trên giao của dòng i và cột j được gọi là ô (i,j). Trên bàn cờ có m (0 ≤ m ≤ n)
quân cờ. Với m > 0, quân cờ thứ i ở ô (r i, ci), i = 1,2,..., m. Không có hai quân cờ nào ở
trên cùng một ô. Trong số các ô còn lại của bàn cờ, tại ô (p, q) có một quân tượng. Mỗi
một nước đi, từ vị trí đang đứng quân tượng chỉ có thể di chuyển đến được những ô trên
cùng đường chéo với nó mà trên đường đi không phải qua các ô đã có quân.
Cần phải đưa quân tượng từ ô xuất phát (p, q) về ô đích (s,t). Giả thiết là ở ô đích
không có quân cờ. Nếu ngoài quân tượng không có quân nào khác trên bàn cờ thì chỉ có 2
trường hợp: hoặc là không thể tới được ô đích, hoặc là tới được sau không quá 2 nước đi
Khi trên bàn cờ còn có các quân cờ khác, vấn đề sẽ không còn đơn giản như vậy.
Yêu cầu: Cho kích thước bàn cờ n, số quân cờ hiện có trên bàn cờ m và vị trí của
chúng, ô xuất phát và ô đích của quân tượng. Hãy xác định số nước đi ít nhất cần thực
hiện để đưa quân tượng về ô đích hoặc đưa ra số -1 nếu điều này không thể thực hiện
được.
- Input: File quantuong.inp
Dòng đầu tiên chứa 6 số nguyên n, m, p, q, s, t.
Nếu m > 0 thì mỗi dòng thứ i trong m dòng tiếp theo chứa một cặp số nguyên ri , ci

xác định vị trí quân thứ i.
Hai số liên tiếp trên cùng một dòng được ghi cách nhau ít nhất một dấu cách.
- Output: File quantuong.out
12


Gồm 1 dòng duy nhất là số nước đi tìm được hoặc số -1.
Quantuong.inp
837214
54
34
47

Quantuong.out
3

Ý tưởng:
Sử dụng thuật toán loang. Tại ô (i,j) ta sẽ loang theo 4 đường chéo có dạng (i-x,j-x),
(i+x, j+x), (i-x, j+x), (i+x, j-x) với điều kiện: nếu gặp ô đang có quân cờ hoặc ra ngoài
khỏi bàn cờ thì thoát khỏi hướng đi đó ngay. Để tránh loang lặp đi lặp lại của những ô có
thể đi nhiều lần, ta dùng thêm 1 mảng 2 chiều B với ý nghĩa giá trị của B(i,j) là vị trí của
nó trong hàng đợi. Do bài toán cần tìm ra đường đi ngắn nhất nên trong quá trình duyệt
nếu gặp ô cần tới thì thông báo kết quả và dừng chương trình, ngược lại nếu không tìm
thấy thì in -1.
Chương trình:
program quantuong_loang;
const
fi='quantuong.inp';
fo='quantuong.out';
var

a:array[0..201,0..201] of boolean;
b:array[0..201,0..201] of word;
qx,qy:array[1..100000] of word;
n,m,p,q,s,t,i,j,cc,k:integer;
f:text;
procedure doc;
var i,j,z:integer;
begin
assign(f,fi);
reset(f);
readln(f,n,m,p,q,s,t);
for i:=1 to n do
for j:=1 to n do
begin
a[i,j]:=true;
b[i,j]:=0;
end;
for z:=1 to m do
begin
readln(f,i,j);
a[i,j]:=false;
end;
for i:=0 to n+1 do
begin
13


a[0,i]:=false;
a[n+1,i]:=false;
a[i,0]:=false;

a[i,n+1]:=false;
end;
close(f);
end;
procedure inketqua;
begin
assign(f,fo);
rewrite(f);
writeln(f,k);
close(f);
halt;
end;
procedure xuly;
var d,c:integer;
begin
d:=1;
c:=1;
k:=0;
qx[d]:=p;
qy[d]:=q;
b[p,q]:=1;
while d<=c do
begin
cc:=c;
inc(k);
while d<=cc do
begin
i:=d;
while a[qx[i]+1,qy[i]+1] do
if b[qx[i]+1,qy[i]+1]=0 then

begin
inc(c);
qx[c]:=qx[i]+1;
qy[c]:=qy[i]+1;
b[qx[c],qy[c]]:=c;
if (qx[c]=s) and (qy[c]=t) then inketqua;
i:=c;
end
else
i:=b[qx[i]+1,qy[i]+1];
i:=d;
while a[qx[i]-1,qy[i]-1] do
if b[qx[i]-1,qy[i]-1]=0 then
begin
14


inc(c);
qx[c]:=qx[i]-1;
qy[c]:=qy[i]-1;
b[qx[c],qy[c]]:=c;
if (qx[c]=s) and (qy[c]=t) then inketqua;
i:=c;
end
else
i:=b[qx[i]-1,qy[i]-1];
i:=d;
while a[qx[i]-1,qy[i]+1] do
if b[qx[i]-1,qy[i]+1]=0 then
begin

inc(c);
qx[c]:=qx[i]-1;
qy[c]:=qy[i]+1;
b[qx[c],qy[c]]:=c;
if (qx[c]=s) and (qy[c]=t) then inketqua;
i:=c;
end
else
i:=b[qx[i]-1,qy[i]+1];
i:=d;
while a[qx[i]+1,qy[i]-1] do
if b[qx[i]+1,qy[i]-1]=0 then
begin
inc(c);
qx[c]:=qx[i]+1;
qy[c]:=qy[i]-1;
b[qx[c],qy[c]]:=c;
if (qx[c]=s) and (qy[c]=t) then inketqua;
i:=c;
end
else
i:=b[qx[i]+1,qy[i]-1];
inc(d);
end;
end;
end;
begin
doc;
xuly;
begin

assign(f,fo);
rewrite(f);
writeln(f,-1);
close(f);
end;
15


end.
Bài 3. Mê cung
Để cứu được công chúa, các hiệp sĩ phải vượt qua một thử thách. Lão phù thủy nhốt
công chúa trong một mê cung có kích thước MxN (1 ≤ M, N ≤1000). Trong mê cung đầy
những bức tượng đá, vốn là các hiệp sĩ do không vượt qua thử thách nên bị biến thành
tượng đá. Tuy nhiên trên dòng M và cột N của mê cung có một vị trí không có tượng đá,
giúp “thoát” ra khỏi mê cung.
Tuy nhiên lão đoán rằng các hiệp sĩ đã vào đến đây thì thuật toán chắc không tầm
thường, nên thay vì cho biết vị trí công chúa và đố tìm đường đi ngắn nhất thoát khỏi mê
cung, thì lại đố tìm vị trí công chúa biết rằng nàng ở nơi xa nhất trong những đường ngắn
nhất thoát khỏi mê cung, thời gian tìm kiếm trong khoảng 1 giây.
Yêu cầu: Cho ma trân MxN, mỗi ô chứa giá trị nhị phân, 0 là rỗng và 1 là có tượng
đá. Tìm độ dài đường đi xa nhất trong những đường đi ngắn nhất thoát khỏi mê cung và
số vị trí công chúa có thể đứng. Biết rằng, độ dài đường đi là số ô của đường đi, chỉ được
đi qua các ô kề cạnh, không được đi qua ô có tượng đá, mọi ô rỗng đều có đường thoát.
Dữ liệu: Vào từ file văn bản mecung.inp: dòng đầu là M và N, các dòng sau chứa
ma trận nhị phân.
Kết quả: Đưa ra file văn bản mecung.out: ghi hai số nguyên là độ dài và số vị trí
thỏa yêu cầu.
mecung.inp mecung.out
58
11111111

10100011
10001100
11100001
11101111

10 2

Giải thích: Trong hình vẽ trên, đường đi xa nhất được tô nền, vị trí công chúa
được in đậm.
Nhận xét: Theo đề bài trên hàng m và cột n của mê cung có vị trí thoát ra. Vì vậy
chúng ta xác định vị trí A[m,j] =0 và A[i,n] =0. Sau đó thực hiện 2 thủ tục loang tại vị trí
(m,j) và (i,n) để tìm độ dài đường đi xa nhất trong đường đi ngắn nhất tới vị trí của công
chúa và số vị trí công chúa có thể đứng. Để tìm đường đi dài nhất ta gọi thủ tục loang,
đồng thời tính A[i,j] là độ dài đường đi lớn nhất. Sau khi thực hiện xong 2 thủ tục loang
duyệt lại mảng A đề đếm số lượng những ô có A[i,j]=max, đây chính là số vị trí công
chúa có thể đứng.
program mecung;
const
fi='mecung.inp';
fo='mecung.out';
type
16


mang=array[0..1001,0..1001] of boolean;
var
a:array[0..1001,0..1001] of longword;
kt:mang;
i,j,m,n,k,max:longword;
f:text;

procedure doc;
var
s:string;
i,j:integer;
begin
assign(f,fi);
reset(f);
readln(f,m,n);
fillchar(kt,sizeof(kt),false);
for i:=1 to m do
begin
readln(f,s);
for j:=1 to n do
begin
val(s[j],k);
a[i,j]:=k;
if a[i,j]=0 then kt[i,j]:=true;
end;
end;
close(f);
end;
procedure inketqua;
var i,j:integer;
begin
assign(f,fo);
rewrite(f);
k:=0;
for i:=1 to m do
for j:=1 to n do
if a[i,j]=max then inc(k);

writeln(f,max,' ',k);
close(f);
end;
procedure BFS(p,q:longword;b:mang);
var
x:array[1..4] of shortint=(-1,0,1,0);
y:array[1..4] of shortint=(0,-1,-1,1);
qx,qy:array[1..1000] of longword;
d,c,t,cc,l:longword;
begin
17


d:=1;
c:=1;
t:=0;
qx[1]:=p;
qy[1]:=q;
b[p,q]:=false;
while d<=c do
begin
cc:=c;
inc(t);
if t>max then max:=t;
while d<=cc do
begin
if t>a[qx[d],qy[d]] then a[qx[d],qy[d]]:=t;
for l:=1 to 4 do
if b[qx[d]+x[l],qy[d]+y[l]] then
begin

inc(c);
qx[c]:=qx[d]+x[l];
qy[c]:=qy[d]+y[l];
b[qx[d]+x[l],qy[d]+y[l]]:=false;
end;
inc(d);
end;
end;
end;
begin
doc;
max:=0;
for i:=1 to m do
begin
if a[i,n]=0 then BFS(i,n,kt);
end;
for i:=1 to n do
begin
if a[i,m]=0 then BFS(m,i,kt);
end;
inketqua;
end.
Bài 4. Quân mã.
Trong luật cờ vua, mỗi nước đi của quân mã được quy định như sau: quân mã đang
ở vị trí X như hình bên dưới có thể đi đến một trong các ô mà mũi tên chỉ đến (theo
đường chéo của hình chữ nhật 2x3).
số

Yêu cầu: Cho trước bàn cờ kích thước m x n ô. Hãy đếm
nước đi ít nhất để quân mã di chuyển từ ô có tọa độ (x1,

y1) đến ở có tọa độ (x2, y2). Trong trường hợp không đến
được thì xuất ra giá trị -1.
18


Dữ liệu vào: quanma.inp


Dòng 1 ghi 2 số nguyên dương m,n (2 <= n, m <= 1000).



Dòng 2 ghi 2 số nguyên x1, y1 (1 <= x1 <= m, 1 <= y1 <= n).



Dòng 3 ghi 2 số nguyên x2, y2 (1 <= x2 <= m, 1 <= y2 <= n).

Các số ghi trên cùng một dòng cách nhau ít nhất một kí tự trắng.
Kết quả: quanma.out
Ghi ra một số nguyên duy nhất cho biết số nước đi ít nhất để quân mã di chuyển từ
ô (x1, y1) đến ô (x2, y2). Nếu không đến được thì ghi số -1.
Quanma.inp
46
11
24

Quanma.out
2


Ý tưởng: Loang từ vị trí ô (x1,y1). Nếu bắt gặp ô (x2,y2) thì thông báo số bước di
chuyển, ngược lại nếu đi hết vị trí có thể mà không đến được ô (x2,y2) thì thông báo -1.
Để thực hiện thủ tục loang ta cần chú ý xác định đúng số vị trí có thể di chuyển được của
quân mã trên bàn cờ. Tại vị trí (i,j) quân mã có thể đi vào 8 ô với độ lệch so với ô ban đầu
như sau:
Thứ tự
1
Lệch hàng -1
Lệch cột
-2

2
-2
-1

3
-2
1

4
-1
2

5
1
2

6
2
1


7
2
-1

8
1
-2

Chương trình:
program quanma;
const fi='quanma.inp';
fo='quanma.out';
var
a:array[-1..1002,-1..1002] of boolean;
qx,qy:array[1..1000000] of word;
x:array[1..8] of shortint=(-1,-2,-2,-1,1,2,2,1);
y:array[1..8] of shortint=(-2,-1,1,2,2,1,-1,-2);
i,j,m,n,t,x1,y1,x2,y2,k:longint;
f:text;
procedure doc;
var i,j:integer;
begin
assign(f,fi);
reset(f);
readln(f,m,n);
readln(f,x1,y1);
readln(f,x2,y2);
for i:=-1 to m+2 do
for j:=-1 to n+2 do

if (i>0) and (i<=m) and (j>0) and (j<=n) then
a[i,j]:=true else a[i,j]:=false;
end;
procedure inketqua;
19


begin
writeln(f,t);
close(f);
halt;
end;
procedure xuly;
var k,dau,cuoi,se:integer;
begin
dau:=1;
cuoi:=1;
qx[1]:=x1;
qy[1]:=y1;
t:=0;
a[x1,y1]:=false;
while dau<=cuoi do
begin
inc(t);
se:=cuoi;
while dau<=se do
begin
for k:=1 to 8 do
if a[qx[dau]+x[k],qy[dau]+y[k]] then
begin

a[qx[dau]+x[k],qy[dau]+y[k]]:=false;
if (qx[dau]+x[k]=x2) and (qy[dau]+y[k]=y2) then inketqua;
inc(cuoi);
qx[cuoi]:=qx[dau]+x[k];
qy[cuoi]:=qy[dau]+y[k];
end;
inc(dau);
end;
end;
end;
begin
doc;
assign(f,fo);
rewrite(f);
xuly;
writeln(f,-1);
close(f);
end.

Bài 5. Thỏ và cà rốt
Trong một mảnh vườn hình chữ nhật có cạnh m, n người ta trồng cà rốt trong những
ô đơn vị hình vuông có cạnh 1. Trong mảnh vườn này có một chú thỏ ở trong một hang
chiếm diện tích 1 ô vuông đơn vị, chú thỏ này cần xác định miền người ta đã trồng cà rốt
có diện tích (Hai miền khác nhau không có một cạnh ô vuông nào chung) lớn nhất trong
mảnh vườn để đào một đường hầm ngắn nhất theo phương dọc hoặc phương ngang từ
hang đến phần diện tích lớn nhất đó.
20


Dữ liệu vào từ file văn bản carot.inp:

Dòng đầu tiên ghi 4 số M , N , x, y với x, y là hàng và cột của hang thỏ trong mảnh
vườn (1≤ M,N≤ 100).
Trong M dòng tiếp theo, mỗi dòng có N ký tự 0 hoặc 1 thể hiện hàng thứ i của mảnh
vườn với ý nghĩa 0 là không trồng cà rốt, 1 là có trồng cà rốt.
Giả thiết vị trí của thỏ luôn đến được vùng có nhiều cà rốt nhất.
Kết quả: Ghi ra file văn bản carot.out:
Dòng đầu ghi S là chiều dài của đường hầm (S=0 nếu hang thỏ đang ở trên phần
trồng cà rốt có diện tích lớn nhất
Nếu S>0 thì trong các dòng tiếp theo lần lượt ghi hàng và cột của các ô trên đường
hầm bắt đầu từ hang thỏ đến vùng diện tích lớn nhất trồng cà rốt
CAROT.INP
6611
000011
000011
000011
000011
000011
111000

CAROT.OUT
4
11
12
13
14
15

Ý tưởng: - Thực hiện loang tìm vùng cà rốt lớn nhất, đếm số lượng cà rốt trong
vùng đó được số max và đánh dấu tất cả vùng loang đó là max. Nếu vị trí thỏ thuộc vùng
cà rốt đó thì ghi 0 và kết thúc, nếu vị trí thỏ không thuộc vùng cà rốt thì tiếp tục loang để

lưu vết đường đi. Trong quá trình loang để lưu vết sử dụng mảng Truoc (Truoc[cuoi]=dau
với ý nghĩa trong hàng đợi để đi đến vị trí cuoi thì trước đó là vị trí dau). Ban đầu gán
truoc[1]=0 để phục vụ cho quá trình lưu vết.
Program tho_carot;
const
fi='CAROT.INP';
fo='CAROT.OUT';
x:array[1..4] of shortint=(-1,0,1,0);
y:array[1..4] of shortint=(0,1,0,-1);
var
a:array[0..101,0..101] of integer;
n,m,i,j,k,l,xx,yy,max:word;
qx,qy,truoc:array[1..10001] of word;
f:text;
procedure doc;
var i,j:integer;
begin
assign(f,fi);
reset(f);
21


readln(f,m,n,xx,yy);
for i:=1 to m do
begin
for j:=1 to n do read(f,a[i,j]);
readln(f);
end;
for i:=1 to m do
begin

a[i,0]:=-1;
a[i,n+1]:=-1;
end;
for j:=1 to n do
begin
a[0,j]:=-1;
a[m+1,j]:=-1;
end;
close(f);

end;
procedure inketqua(i,j:integer);
begin
if j<>0 then inketqua(i+1,truoc[j]) else
begin
writeln(f,i-1);
exit;
end;
writeln(f,qx[j],' ',qy[j]);
end;
procedure loang1;
var d,c,i,j,l,k:integer;
begin
max:=0;
for i:=1 to m do
for j:=1 to n do
if a[i,j]=1 then
begin
d:=1;
c:=1;

qx[1]:=i;
qy[1]:=j;
k:=0;
while d<=c do
begin
for l:=1 to 4 do
if a[qx[d]+x[l],qy[d]+y[l]]=1 then
begin
inc(k);
inc(c);
qx[c]:=qx[d]+x[l];
22


qy[c]:=qy[d]+y[l];
a[qx[c],qy[c]]:=-1;
end;
inc(d);
end;
if k>max then max:=k;
for l:=1 to c do a[qx[l],qy[l]]:=k;
end;

end;
procedure loang2;
var d,c,i,j:integer;
begin
d:=1;
c:=1;
qx[1]:=xx;

qy[1]:=yy;
truoc[1]:=0;
while d<=c do
begin
for i:=1 to 4 do
if a[qx[d]+x[i],qy[d]+y[i]]>=0 then
begin
inc(c);
qx[c]:=qx[d]+x[i];
qy[c]:=qy[d]+y[i];
truoc[c]:=d;
if a[qx[c],qy[c]]=max then
begin
inketqua(0,c);
close(f);
halt;
end;
a[qx[c],qy[c]]:=-1;
end;
inc(d);
end;
end;
begin
doc;
loang1;
assign(f,fo);
rewrite(f);
if a[xx,yy]=max then writeln(f,0) else loang2;
close(f);
end.

Bài 6. Đường đi của chuột
23


Một mê cung có dạng 1 hình chữ nhật
m*n ô, trong đó có 1 số ô là tường. Một con
chuột và 1 miếng bánh nằm ở các ô cho
trước. Ở mỗi bước di chuyển, con chuột có
thể di chuyển sang các ô kề cạnh nhưng
không thể rẻ trái và không được đi vào vị
trí các ô là tường.
Yêu cầu: Đếm số bước di chuyển ít nhất để
con chuột có thể ăn được bánh

Dữ liệu vào: Tệp chuot.inp chứa dãy số nguyên, bắt đầu là kích thước của mê cung
(M,N<=100), tiếp theo là các tọa độ Xr, Yr của chuột và Xf, Yf của bánh. Tiếp theo là số
nguyên k cho biết có k bức tường và k cặp tọa độ của chúng.
Dữ liệu ra: Tệp chuot.out
- Dòng đầu ghi số bước di chuyển ít nhất
- Dòng tiếp theo ghi tọa độ các bước đã đi của chuột.
Chuot.inp
5612143132344

Chuot.out
10
22
32
42
32
33

34
35
34
24

Mô tả hướng đi của chuột

14

Ý tưởng: Điểm mấu chốt để giải quyết được bài toán này là ta phải cài đặt được
chương trình thỏa mãn yêu cầu trong đường đi của chuột không được rẻ trái. Để giải
quyết được vấn đề này ta căn cứ vào 2 mảng hi, hj trong phần khai báo. Giả sử ta khai
báo 2 mảng Hi, Hj như phía dưới.
Hi
Hj
Hướng đi

-1
0
1
4

1
4
3

0
1
2


1
0
3

0
-1
4

Nhận xét: Để đảm bảo chuột đi
không rẻ trái thì ta phải xét hướng đi trước
đó của chuột.
Xuất phát từ ô(i,j), trong bảng phía dưới
hướng trước là hướng đi của chuột ở bước
trước đó và hướng tiếp là hướng có thể đi.

24


(i,j)

Hướng
1
2
3
4
trước
Hướng
1,2,3 2,3,4 3,4,1 4,1,2
tiếp
Đoạn chương trình để tránh rẻ trái:


for i:=0 to 2 do
begin
Ví dụ: Trên hình vẽ mũi tên nét liền là

hướng đi trước, nét đứt là hướng không
được đi tiếp.
- Trước đó hướng 4 thì tiếp theo tránh
hướng 3
- Trước đó hướng 1 thì tiếp theo tránh
hướng 4

l:=huongtruoc+i;
if l>4 then l:=l mod 4;
………….

end;

Ngoài ra, do chuột có thể di chuyển vào ô đã đi qua trước đó miễn sao hướng đi
khác nên để đánh dấu ô đã đi qua và hướng đi ta sử dụng 1 mảng 3 chiều B (B[i,j,k] là ô
(i,j) và hướng đi k, 1<=k<=4).
Chương trình:
program chuot_banh;
const
fi='chuot.inp';
fo='chuot.out';
hi:array[1..4] of shortint=(-1,0,1,0);
hj:array[1..4] of shortint=(0,1,0,-1);
var
a:array[0..101,0..101] of boolean;

b:array[1..100,1..100,1..4] of boolean;
qx,qy,tr,h:array[1..100000] of byte;
m,n,i,j,xr,yr,xf,yf,k:word;
f:text;
procedure doc;
var
i,j,z:longword;
begin
assign(f,fi);
reset(f);
read(f,m,n,xr,yr,xf,yf,k);
for i:=0 to m+1 do
for j:=0 to n+1 do
if (i>0) and (i<=m) and (j>0) and (j<=n) then
a[i,j]:=true
else a[i,j]:=false;
for z:=1 to k do
begin
read(f,i,j);
a[i,j]:=false;
end;
fillchar(b,sizeof(b),true);
close(f);
end;
25


×