Professional Documents
Culture Documents
Thuật Toán Duyệt Trên Đồ Thị
Thuật Toán Duyệt Trên Đồ Thị
I. ĐỒ THỊ:
1. Định nghĩa đồ thị (Graph):
Là một cấu trúc rời rạc gồm các đỉnh và các cạnh nối các đỉnh đó, được mô tả bằng hình
thức: G = (V, E).
Trong đó: V được gọi là tập các đỉnh (Vertices) và E được gọi là tập các cạnh (Edges). Có
thể coi E là tập các cặp (u, v) với u và v là hai đỉnh của tập V.
Một số hình ảnh của đồ thị:
Vídụ 1: Sơ đồ giao thông
Các đỉnh là các sân bay
Các cạnh thể hiện đường bay nối hai sân bay
Các số trên cạnh có thể là chi phí về khoảng cách
DBP
DAN
HAP
HCM
HAN
BKK NHT
VIN
Vídụ 2: Sơ đồ mạng máy tính
Phòng tàichính
BGH
TổTin
GV 3
GV 1
GV 2
Trang 3
Vídụ 3: Cấu trúc phân tử
Trang 4
II. MỘT SỐ KHÁI NIỆM CƠ BẢN
Như định nghĩa nêu trên, đồ thị G = (V, E) là một cấu trúc rời rạc, tức là các tập V và E là
tập hữu hạn, đếm được, có nghĩa là ta có thể đánh số thứ tự 1, 2, 3… cho các phần tử của tập V và
E. Hơn nữa, đứng trên phương diện người lập trình cho máy tính thì ta chỉ quan tâm đến các đồ thị
hữu hạn (V và E là tập hữu hạn), do vậy khi nói tới đồ thị, ta hiểu rằng đó là đồ thị hữu hạn.
V
a b
h
U d X Z j
i
c e
W g
f
Y
Trang 5
V V
a b a b
P1
d U d X Z
U X Z
P2 h C2 h
c e e
c C1
W g W g
f f
Y 3)
G3
G2
Hình 4. Đồ thị G và các thành phần liên thông G1, G2, G3 của nó
- Đồ thị có hướng G được gọi là liên thông mạnh (Strongly connected): nếu luôn tồn tại
đường đi (theo các cung định hướng) giữa hai đỉnh bất kỳ của đồ thị; G gọi là liên thông yếu
(weakly connected) nếu đồ thị vô hướng nền của nó là liên thông.
Chứng minh: Khi lấy tổng tất cả các bậc đỉnh tức là mỗi cạnh e = (u, v) bất kỳ sẽ được tính
một lần trong deg(u) và một lần trong deg(v). Từ đó suy ra được kết quả.
Trang 6
Hệ quả 1: Trong đồ thị vô hướng, số đỉnh bậc lẻ là số chẵn.
Hệ quả 2: Trong đơn đồ thị vô hướng gồm n đỉnh, số cạnh tối đa m = n*(n-1)/2.
Ví dụ:
n=4
m=6
deg(v) =3
Đối với đồ thị có hướng G = (V, E). Xét một cung e E, nếu e = (u, v) thì ta nói u nối tới v và
v nối từ u, cung e là đi ra khỏi đỉnh u và đi vào đỉnh v. Đỉnh u khi đó được gọi là đỉnh đầu, đỉnh v
được gọi là đỉnh cuối của cung e.
Với mỗi đỉnh v trong đồ thị có hướng, ta định nghĩa: Bán bậc ra của v ký hiệu deg+(v) là số
cung đi ra khỏi nó; bán bậc vào ký hiệu deg-(v) là số cung đi vào đỉnh đó.
Định lý 2: Giả sử G = (V, E) là đồ thị có hướng với m cung, khi đó tổng tất cả các bán bậc ra
của các đỉnh bằng tổng tất cả các bán bậc vào và bằng m:
deg (v) deg (v) m
vV vV
Chứng minh: Khi lấy tổng tất cả các bán bậc ra hay bán bậc vào, mỗi cung (u, v) bất kỳ sẽ
được tính đúng 1 lần trong deg+(u) và cũng được tính đúng 1 lần trong deg-(v). Từ đó suy ra được
kết quả.
Trang 7
Chương II. CÁC CÁCH BIỂU DIỄN ĐỒ THỊ
Có nhiều cách biểu diễn, việc lựa chọn cách biểu diễn nào là phụ thuộc vào từng bài toán cụ
thể cần xét, từng thuật toán cụ thể cần cài đặt.
Có hai vấn đề chính cần quan tâm khi lựa chọn cách biểu diễn đồ thị:
Bộ nhớ mà cách biểu diễn đó đòi hỏi;
Thời gian cần thiết để trả lời các truy vấn thường xuyên đối với đồ thị trong quá trình
xử lý đồ thị. Chẳng hạn:
Có cạnh nối giữa hai đỉnh u, v?
Liệt kê các đỉnh kề của đỉnh v?
Một số cách biểu diễn đồ thị cơ bản:
1 2 3 4 5 1
1 0 0 1 0 0
2 0 0 0 1 0 5 2
3 0 0 0 0 1
4 1 0 0 0 0
4 3
5 0 1 0 0 0
Trang 8
- Nếu G là đồ thị có hướng và A là ma trận kề tương ứng thì trên ma trận A:
+ Tổng các số trên hàng i = Bán bậc ra của đỉnh i = deg+(i);
+ Tổng các số trên cột i = Bán bậc vào của đỉnh i = deg-(i).
- Trong trường hợp G là đơn đồ thị, ta có thể biểu diễn ma trận kề A tương ứng là các phần tử
logic: aij = TRUE nếu (i, j) E; aij = FALSE nếu (i, j) E.
* Nhận xét:
Ưu điểm:
- Đơn giản, trực quan, dễ cài đặt trên máy tính.
- Để kiểm tra xem hai đỉnh (u, v) của đồ thị có kề nhau hay không, ta chỉ việc kiểm tra bằng
một phép so sánh: auv 0.
Nhược điểm:
- Bất kể số cạnh của đồ thị là nhiều hay ít, ma trận kề luôn luôn đòi hỏi n 2 ô nhớ để lưu các
phần tử ma trận, điều đó gây lãng phí bộ nhớ dẫn tới việc không thể biểu diễn được đồ thị với số
đỉnh lớn.
- Với một đỉnh u bất kỳ của đồ thị, nhiều khi ta phải xét tất cả các đỉnh v khác kề với nó, hoặc
xét tất cả các cạnh liên thuộc với nó. Trên ma trận kề, việc đó được thực hiện bằng cách xét tất cả
các đỉnh v và kiểm tra điều kiện auv 0. Như vậy, ngay cả khi đỉnh u là đỉnh cô lập (không kề với
đỉnh nào) hoặc đỉnh treo (chỉ kề với 1 đỉnh) ta cũng buộc phải xét tất cả các đỉnh và kiểm tra điều
kiện trên dẫn tới lãng phí thời gian.
Null
* Nhận xét:
Ưu điểm:
- Trong trường hợp đồ thị thưa (có số cạnh tương đối nhỏ: chẳng hạn m < 6n), cách biểu diễn
bằng danh sách cạnh sẽ tiết kiệm được không gian lưu trữ, bởi nó chỉ cần 2m ô nhớ để lưu danh sách
cạnh.
Trang 9
- Trong một số trường hợp, ta phải xét tất cả các cạnh của đồ thị thì việc cài đặt trên danh sách
cạnh làm cho việc duyệt các cạnh dễ dàng hơn (Thuật toán Kruskal chẳng hạn).
Nhược điểm:
Nhược điểm cơ bản của danh sách cạnh là khi ta cần duyệt tất cả các đỉnh kề với đỉnh v nào đó
của đồ thị, thì chẳng có cách nào khác là phải duyệt tất cả các cạnh, lọc ra những cạnh có chứa đỉnh v
và xét đỉnh còn lại. Điều đó khá tốn thời gian trong trường hợp đồ thị dày (nhiều cạnh).
135243 5 1 4
I II III IV V
- Để biết một đoạn nằm từ chỉ số nào đến chỉ số nào, ta có một mảng Head lưu vị trí riêng.
Head[i] sẽ bằng chỉ số đứng liền trước đoạn thứ i. Quy ước Head[n + 1] bằng m. Với đồ thị trên thì
mảng Head[1..6] sẽ là: (0, 3, 5, 8, 10, 12).
- Trong mảng A, đoạn từ vị trí Head[i] + 1 đến Head[i + 1] sẽ chứa các đỉnh kề với đỉnh i.
Lưu ý: với đồ thị có hướng gồm m cung thì cấu trúc Forward Star cần phải đủ chứa m phần tử,
với đồ thị vô hướng m cạnh thì cấu trúc Forward Star cần phải đủ chứa 2m phần tử.
Cách 2 (Linked Adjancency List): Dùng các danh sách móc nối: Với mỗi đỉnh i của đồ thị, ta
cho tương ứng với nó một danh sách móc nối các đỉnh kề với i, có nghĩa là tương ứng với một đỉnh i,
ta phải lưu lại List[i] là chốt của một danh sách móc nối. Ví dụ với đồ thị ở hình trên, các danh sách
móc nối sẽ là:
2 3 5
List 1:
1 3
List 2:
List 3: 1 2 4
List 4: 3 5
Trang 10
List 5: 1 4
* Nhận xét:
Ưu điểm:
Đối với danh sách kề, việc duyệt tất cả các đỉnh với 1 đỉnh v cho trước sẽ rất dễ dàng. Việc duyệt
tất cả các cạnh cũng đơn giản vì một cạnh thực ra là nối một đỉnh với một đỉnh khác kề nó.
Nhược điểm:
Danh sách kề yếu hơn ma trận kề ở việc kiểm tra (u, v) có phải là cạnh hay không, bởi trong cách
biểu diễn này ta sẽ phải duyệt toàn bộ danh sách kề của u hay danh sách kề của v.
Đánh giá thời gian thực hiện một số thao tác cơ bản:
Thông thường, biểu diễn bằng danh sách kề tốt hơn so với hai phương pháp biểu diễn ma trận kề và
danh sách cạnh. Trong một số thuật toán cụ thể, nếu ma trận kề hay danh sách cạnh không thể hiện
nhược điểm thì ta nên dùng ma trận kề (hay danh sách cạnh) bởi cài đặt danh sách kề dài dòng hơn.
Trang 11
Chương III. THUẬT TOÁN DUYỆT TRÊN ĐỒ THỊ
I. BÀI TOÁN:
Cho đồ thị G = (V, E), u và v là hai đỉnh của G. Một đường đi (path) từ đỉnh u đến đỉnh v là
dãy (u = x0, x1, ..., xl = v) thoả mãn (xi, xi+1) ∈ E với ∀i: (0 ≤ i < l). Đường đi nói trên còn có thể
biểu diễn bởi dãy các cạnh: (u = x0, x1), (x1, x2), ..., (xl-1, xl = v). Xét một đồ thị vô hướng và một
đồ thị có hướng dưới đây:
2 3
2 3
1 4 1 4
6 5 6 5
DFS BFS
S Hình 7: Tổng quát về cây tìm kiếm DFS và BFS
- Khái niệm: Duyệt đồ thị (Graph Searching hoặc Graph Traversal) là việc duyệt qua mỗi
đỉnh và mỗi cạnh của đồ thị.
- Ứng dụng:
+ Xây dựng các thuật toán khảo sát các tính chất của đồ thị;
+ Một phần cơ bản của nhiều thuật toán giải các bài toán dạng đồ thị.
Ý tưởng chung:
- Trong quá trình thực hiện thuật toán, mỗi đỉnh ở một trong ba trạng thái:
Chưa thăm.
Trang 12
Đã thăm nhưng chưa duyệt xong.
Đã duyệt xong.
- Quá trình duyệt bắt đầu từ một đỉnh u nào đó. Ta sẽ khảo sát các đỉnh đạt tới được từ u:
Bắt đầu: các đỉnh đều chưa được thăm.
Đỉnh đã được thăm nhưng chưa duyệt xong.
Khi tất cả các đỉnh kề của một đỉnh u đã được thăm, đỉnh u sẽ đã được duyệt xong.
II. THUẬT TOÁN TÌM KIẾM THEO CHIỀU SÂU (DEPTH FIRST SEARCH)
Xét trường hợp đồ thị vô hướng.
GRAPH.INP GRAPH.OUT
8715 1, 2, 3, 5, 4, 6
2 4 12 5 3 2 1
6
13
23
1
24
7 8 35
46
3 5 78
Trang 13
end;
begin {Chương trình chính}
< Nhập dữ liệu: đồ thị, đỉnh xuất phát S, đỉnh đích F >;
< Khởi tạo: Tất cả các đỉnh đều chưa bị đánh dấu >;
DFS(S);
< Nếu F chưa bị đánh dấu thì không thể có đường đi từ S tới F >;
< Nếu F đã bị đánh dấu thì truy theo vết để tìm đường đi từ S tới F >;
end.
{ Cài đặt thuật toán tìm kiếm theo chiều sâu có đệ quy}
program DFS_1;
const max = 100;
var a: array[1..max, 1..max] of Boolean; {Ma trận kề của đồ thị}
Free: array[1..max] of Boolean; {Free[v] = True ⇔ v chưa được thăm đến}
Trace: array[1..max] of Integer; {Trace[v] = đỉnh liền trước v trên đường đi từ S tới v}
n, S, F: Integer;
procedure Enter; {Nhập dữ liệu}
var i, u, v, m: Integer;
begin
FillChar(a, SizeOf(a), False); {Khởi tạo đồ thị chưa có cạnh nào}
ReadLn(n, m, S, F); {Đọc dòng 1 ra 4 số n, m, S và F}
for i := 1 to m do {Đọc m dòng tiếp ra danh sách cạnh}
begin
ReadLn(u, v);
a[u, v] := True;
a[v, u] := True;
end;
end;
procedure DFS(u: Integer); {Thuật toán tìm kiếm theo chiều sâu bắt đầu từ đỉnh u}
var v: Integer;
begin
Write(u, ', '); {Thông báo tới được u}
Free[u] := False; {Đánh dấu u đã thăm}
for v := 1 to n do
if (Free[v]) and (a[u, v]) then {Với mỗi đỉnh v chưa thăm kề với u}
begin
Trace[v] := u; {Lưu vết đường đi: Đỉnh liền trước v trong đường đi từ S tới v là u}
DFS(v); {Tiếp tục tìm kiếm theo chiều sâu bắt đầu từ v}
end;
end;
Begin
Assign(Input, 'GRAPH.INP'); Reset(Input);
Trang 14
Assign(Output, 'GRAPH.OUT'); Rewrite(Output);
Enter;
FillChar(Free, n, True);
DFS(S);
Result;
Close(Input); Close(Output);
End.
* Nhận xét:
a) Vì có kỹ thuật đánh dấu, nên thủ tục DFS sẽ được gọi ≤ n lần (n là số đỉnh).
b) Đường đi từ S tới F có thể có nhiều, ở trên chỉ là một trong số các đường đi. Cụ thể là
đường đi có thứ tự từ điển nhỏ nhất.
c) Có thể chẳng cần dùng mảng đánh dấu Free, ta khởi tạo mảng lưu vết Trace ban đầu toàn
0, mỗi lần từ đỉnh u thăm đỉnh v, ta có thao tác gán vết Trace[v] := u, khi đó Trace[v] sẽ khác 0. Vậy
việc kiểm tra một đỉnh v là chưa được thăm ta có thể kiểm tra Trace[v] = 0 (ban đầu khởi tạo
Trace[S] := -1 chỉ là để cho khác 0). Ta có chương trình (phần khác nhau) cải tiến như sau:
Program DFS_1_cai_tien;
procedure Result;
begin
WriteLn;
if Trace[F] = 0 then
WriteLn('Path from ', S, ' to ', F, ' not found')
else
begin
while F <> S do
begin Write(F, ' ');
F := Trace[F];
end;
WriteLn(S);
end;
end;
Begin
Assign(Input, 'GRAPH.INP'); Reset(Input);
Assign(Output, 'GRAPH.OUT'); Rewrite(Output);
Enter;
FillChar(Trace,n,0);
Trace[S]:=-1;
DFS(S);
Result;
Close(Input); Close(Output);
End.
Trang 15
Ví dụ: Với đồ thị đã cho, đỉnh xuất phát S = 1: quá trình duyệt đệ quy có thể vẽ trên cây tìm kiếm
DFS sau (Mũi tên u→v chỉ thao tác đệ quy: DFS(u) gọi DFS(v)).
2 4
2 4 6
6
1
1
7 8
7 8
3 5
3 5
Trang 16
begin
FillChar(a, SizeOf(a), False);
ReadLn(n, m, S, F);
for i := 1 to m do
begin
ReadLn(u, v); a[u, v] := True; a[v, u] := True;
end;
end;
procedure Init; {Khởi tạo}
begin
FillChar(Free, n, True); {Các đỉnh đều chưa đánh dấu}
Last := 0; {Ngăn xếp rỗng}
end;
procedure Push(V: Integer); {Đẩy một đỉnh V vào ngăn xếp}
begin
Inc(Last); Stack[Last] := V;
end;
function Pop: Integer; {Lấy một đỉnh khỏi ngăn xếp, trả về trong kết quả hàm}
begin
Pop := Stack[Last];
Dec(Last);
end;
procedure DFS;
var u, v: Integer;
begin
Write(S, ', ');
Free[S] := False; {Thăm S, đánh dấu S đã thăm}
Push(S); {Khởi động dây chuyền duyệt sâu}
repeat {Dây chuyền duyệt sâu đang là S→ ...→ u}
u := Pop; {u là điểm cuối của dây chuyền duyệt sâu hiện tại}
for v := 1 to n do
if Free[v] and a[u, v] then {Chọn v là đỉnh đầu tiên chưa thăm kề với u, nếu có:}
begin
Write(v, ', ');
Free[v] := False; {Thăm v, đánh dấu v đã thăm}
Trace[v] := u; {Lưu vết đường đi}
Push(u); Push(v); {Dây chuyền duyệt sâu bây giờ là S→ ...→ u→ v}
Break;
end;
until Last = 0; {Ngăn xếp rỗng}
end;
procedure Result; {In đường đi từ S tới F}
begin
WriteLn;
if Free[F] then
WriteLn('Path from ', S, ' to ', F, ' not found')
else begin
while F <> S do
begin
Write(F, '<-'); F := Trace[F];
end;
WriteLn(S);
end;
end;
Begin
Assign(Input, 'GRAPH.INP'); Reset(Input);
Trang 17
Assign(Output, 'GRAPH.OUT'); Rewrite(Output);
Enter;
Init;
DFS;
Result;
Close(Input); Close(Output);
End.
Ví dụ: Với đồ thị dưới đây (S = 1), quá trình thực hiện thủ tục tìm kiếm theo chiều sâu dùng
ngăn xếp và đối sánh thứ tự các đỉnh được thăm với thứ tự từ (1, 2, 3, 5, 4, 6) trong cây tìm kiếm của
thủ tục DFS dùng đệ quy.
2 4
6
7 8
3 5
Bước lặp Ngăn xếp u v Ngăn xếp sau mỗi bước Giải thích
1 (1) 1 2 (1, 2) Tiến sâu xuống thăm 2
2 (1, 2) 2 3 (1, 2, 3) Tiến sâu xuống thăm 3
3 (1, 2, 3) 3 4 (1, 2, 3, 5) Tiến sâu xuống thăm 5
4 (1, 2, 3, 5) 5 Không có (1, 2, 3) Lùi lại
5 (1, 2, 3) 3 Không có (1, 2) Lùi lại
6 (1, 2) 2 4 (1, 2, 4) Tiến sâu xuống thăm 4
7 (1, 2, 4) 4 6 (1, 2, 4, 6) Tiến sâu xuống thăm 6
8 (1, 2, 4, 6) 6 Không có (1, 2, 4) Lùi lại
9 (1, 2, 4) 4 Không có (1, 2) Lùi lại
10 (1, 2) 2 Không có (1) Lùi lại
11 (1) 1 Không có ∅ Lùi hết dây chuyền, Xong
Hình 10. Bảng thực hiện DFS bằng ngăn xếp
Trên đây là phương pháp dựa vào tính chất của thủ tục đệ quy để tìm ra phương pháp mô
phỏng nó. Tuy nhiên, nhìn lại cách thăm đỉnh của DFS: Từ một đỉnh u, chọn lấy một đỉnh v kề nó
mà chưa thăm rồi tiến sâu xuống thăm v. Còn nếu mọi đỉnh kề u đều đã thăm thì lùi lại một bước và
lặp lại quá trình tương tự, việc lùi lại này có thể thực hiện dễ dàng mà không cần dùng Stack nào cả,
bởi với mỗi đỉnh u đã có một nhãn Trace[u] (là đỉnh mà đã từ đó mà ta tới thăm u), khi quay lui từ u
sẽ lùi về đó. Vậy nếu ta đang đứng ở đỉnh u, thì đỉnh kế tiếp phải thăm tới sẽ được tìm như trong
hàm FindNext dưới đây:
function FindNext(u∈V): ∈V; {Tìm đỉnh sẽ thăm sau đỉnh u, trả về 0 nếu mọi đỉnh tới được từ S đều đã
thăm}
begin
repeat
for (∀v ∈ Kề(u)) do
if<v chưa thăm> then {Nếu u có đỉnh kề chưa thăm thì chọn đỉnh kề đầu tiên chưa
thăm để thăm tiếp}
begin
Trace[v] := u; {Lưu vết}
FindNext := v;
Exit;
end;
u := Trace[u]; {Nếu không, lùi về một bước. Lưu ý là Trace[S] được gán bằng n + 1}
until u = n + 1;
FindNext := 0; {ở trên không Exit được tức là mọi đỉnh tới được từ S đã duyệt xong}
Trang 18
end;
III. THUẬT TOÁN TÌM KIẾM THEO CHIỀU RỘNG (BREADTH FIRST SEARCH)
1. Cài đặt bằng hàng đợi:
Cơ sở của phương pháp cài đặt này 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 kề nó sao cho thứ tự duyệt là ưu tiên chiều rộng (đỉnh nào gần S hơn sẽ được
duyệt trước). Ví dụ: Bắt đầu ta thăm đỉnh S. Việc thăm đỉnh S sẽ phát sinh thứ tự duyệt những đỉnh
(x1, x2, ..., xp) kề với S (những đỉnh gần S nhất). Khi thăm đỉnh x1 sẽ lại phát sinh yêu cầu duyệt
những đỉnh (u1, u2 ..., uq) kề với x1. Nhưng rõ ràng các đỉnh u này "xa" S hơn những đỉnh x nên
chúng chỉ được duyệt khi tất cả những đỉnh x đã duyệt xong. Tức là thứ tự duyệt đỉnh sau khi đã
thăm x1 sẽ là: (x2, x3..., xp, u1, u2, ..., uq).
Trang 19
S
x1 x2 xp
…
.2
u1 u2 un
u1 uq Phải duyệt sau xp
…
Giả sử ta có một danh sách chứa những đỉnh đang "chờ" thăm. Tại mỗi bước, ta thăm một
đỉnh đầu 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. Chính vì nguyên tắc đó nên danh sách chứa những đỉnh đang chờ sẽ được tổ chức dưới dạng
hàng đợi (Queue). Ta sẽ xây dựng giải thuật như sau:
- Bước 1: Khởi tạo:
• Các đỉnh đều ở trạng thái chưa đánh dấu, ngoại trừ đỉnh xuất phát S là đã đánh dấu.
• Một hàng đợi (Queue), ban đầu chỉ có một phần tử là S. Hàng đợi dùng để chứa các
đỉnh sẽ được duyệt theo thứ tự ưu tiên chiều rộng.
- Bước 2: Lặp các bước sau đến khi hàng đợi rỗng:
• Lấy u khỏi hàng đợi, thông báo đã thăm u (Bắt đầu việc duyệt đỉnh u).
• Xét tất cả những đỉnh v kề với u mà chưa được đánh dấu, với mỗi đỉnh v đó:
1. Đánh dấu v.
2. Ghi nhận vết đường đi từ u tới v (Có thể làm chung với việc đánh dấu).
3. Đẩy v vào hàng đợi (v sẽ chờ được duyệt tại những bước sau).
- Bước 3: Truy vết tìm đường đi.
{Thuật toán tìm kiếm theo chiều rộng dùng hàng đợi}
program BFS_1;
const max = 100;
var a: array[1..max, 1..max] of Boolean;
Free: array[1..max] of Boolean; {Free[v] ⇔ v chưa được xếp vào hàng đợi để chờ thăm}
Trace: array[1..max] of Integer;
Queue: array[1..max] of Integer;
n, S, F, First, Last: Integer;
procedure Enter; {Nhập dữ liệu}
var i, u, v, m: Integer;
begin
FillChar(a, SizeOf(a), False);
ReadLn(n, m, S, F);
for i := 1 to m do
begin ReadLn(u, v); a[u, v] := True; a[v, u] := True;
end;
end;
procedure Init; {Khởi tạo}
begin
FillChar(Free, n, True); {Các đỉnh đều chưa đánh dấu}
Free[S] := False; {Ngoại trừ đỉnh S}
Trang 20
Queue[1] := S; {Hàng đợi chỉ gồm có một đỉnh S}
Last := 1; First := 1;
end;
procedure Push(V: Integer); {Đẩy một đỉnh V vào hàng đợi}
begin
Inc(Last); Queue[Last] := V;
end;
function Pop: Integer; {Lấy một đỉnh khỏi hàng đợi, trả về trong kết quả hàm}
begin
Pop := Queue[First]; Inc(First);
end;
procedure BFS; {Thuật toán tìm kiếm theo chiều rộng}
var u, v: Integer;
begin
repeat
u := Pop; {Lấy một đỉnh u khỏi hàng đợi}
Write(u, ', '); {Thông báo thăm u}
for v := 1 to n do
if Free[v] and a[u, v] then {Xét những đỉnh v chưa đánh dấu kề u}
begin
Push(v); {Đưa v vào hàng đợi để chờ thăm}
Free[v] := False; {Đánh dấu v}
Trace[v] := u; {Lưu vết đường đi: đỉnh liền trước v trong đường đi từ S là u}
end;
until First > Last; {Cho tới khi hàng đợi rỗng}
end;
procedure Result; {In đường đi từ S tới F}
begin
WriteLn;
if Free[F] then
WriteLn('Path from ', S, ' to ', F, ' not found')
else begin
while F <> S do
begin Write(F, '<-'); F := Trace[F];
end;
WriteLn(S);
end;
end;
Begin
Assign(Input, 'GRAPH.INP'); Reset(Input);
Assign(Output, 'GRAPH.OUT'); Rewrite(Output);
Enter;
Init;
BFS;
Result;
Close(Input); Close(Output);
End.
Trang 21
Ví dụ: Xét đồ thị dưới đây, Đỉnh xuất phát S = 1.
GRAPH.INP GRAPH.OUT
2 4 8715 1, 2, 3, 4, 5, 6
6
12 531
1
13
23
7 8 24
35
3 5
46
78
Các đỉnh v kề
Đỉnh u Hàng đợi sau Hàng đợi sau khi đưa u
Hàng đợi u mà chưa lên
(lấy từ hàng đợi) khi lấy u ra vào
lịch
(1) 1 ∅ 2, 3 (2, 3)
(2, 3) 2 (3) 4 (3, 4)
(3, 4) 3 (4) 5 (4, 5)
(4, 5) 4 (5) 6 (5, 6)
(5, 6) 5 (6) Không có (6)
(6) 6 ∅ Không có ∅
Hình 11. Bảng thực hiện BFS bằng hàng đợi
Để ý thứ tự các phần tử lấy ra khỏi hàng đợi, ta thấy trước hết là 1; sau đó đến 2, 3; rồi mới
tới 4, 5; cuối cùng là 6. Rõ ràng là đỉnh gần S hơn sẽ được duyệt trước. Và như vậy, ta có nhận xét:
nếu kết hợp lưu vết tìm đường đi thì đường đi từ S tới F: 5 3 1, là đường đi ngắn nhất (theo
nghĩa qua ít cạnh nhất).
2. Cài đặt bằng thuật toán loang
Cách cài đặt này dùng hai tập hợp, một tập "cũ" chứa những đỉnh "đang xét", một tập "mới"
chứa những đỉnh "sẽ xét". Ban đầu tập "cũ" chỉ gồm mỗi đỉnh xuất phát, tại mỗi bước ta sẽ dùng tập
"cũ" tính tập "mới", tập "mới" sẽ gồm những đỉnh chưa được thăm mà kề với một đỉnh nào đó của
tập "cũ". Lặp lại công việc trên (sau khi đã gán tập "cũ" bằng tập "mới") cho tới khi tập cũ là rỗng:
4 2 4
2 4 2 6
6 6
1 1
1
3 3 5
3 5 5
Trang 22
Xét tất cả những đỉnh v kề với u mà chưa bị đánh dấu, với mỗi đỉnh v đó:
Đánh dấu v;
Lưu vết đường đi, đỉnh liền trước v trong đường đi S→v là u;
Đưa v vào tập New;
+ Gán tập "cũ" Old := tập "mới" New và lặp lại (có thể luân phiên vai trò hai tập này);
- Bước 3: Truy vết tìm đường đi.
{Thuật toán tìm kiếm theo chiều rộng dùng phương pháp loang}
program BFS_2;
const max = 100;
var a: array[1..max, 1..max] of Boolean;
Free: array[1..max] of Boolean;
Trace: array[1..max] of Integer;
Old, New: set of Byte;
n, S, F: Byte;
procedure Enter; {Nhập dữ liệu}
var i, u, v, m: Integer;
begin
FillChar(a, SizeOf(a), False);
ReadLn(n, m, S, F);
for i := 1 to m do
begin
ReadLn(u, v); a[u, v] := True; a[v, u] := True;
end;
end;
procedure Init;
begin
FillChar(Free, n, True);
Free[S] := False; {Các đỉnh đều chưa đánh dấu, ngoại trừ đỉnh S đã đánh dấu}
Old := [S]; {Tập "cũ" khởi tạo ban đầu chỉ có mỗi S}
end;
procedure BFS; {Thuật toán loang}
var u, v: Byte;
begin
repeat {Lặp: dùng Old tính New}
New := [];
for u := 1 to n do
if u in Old then {Xét những đỉnh u trong tập Old, với mỗi đỉnh u đó:}
begin
Write(u, ', '); {Thông báo thăm u}
for v := 1 to n do
if Free[v] and a[u, v] then {Quét tất cả những đỉnh v chưa bị đánh
dấu mà kề với u}
begin
Free[v] := False; {Đánh dấu v và lưu vết đường đi}
Trace[v] := u;
New := New + [v]; {Đưa v vào tập New}
end;
end;
Old := New; {Gán tập "cũ" := tập "mới" và lặp lại}
until Old = []; {Cho tới khi không loang được nữa}
end;
procedure Result;
begin
WriteLn;
Trang 23
if Free[F] then
WriteLn('Path from ', S, ' to ', F, ' not found')
else
begin while F <> S do
begin
Write(F, '<-'); F := Trace[F];
end;
WriteLn(S);
end;
end;
Begin
Assign(Input, 'GRAPH.INP'); Reset(Input);
Assign(Output, 'GRAPH.OUT'); Rewrite(Output);
Enter;
Init;
BFS;
Result;
Close(Input); Close(Output);
End.
ĐỘ PHỨC TẠP TÍNH TOÁN CỦA DFS VÀ BFS
Khi biểu diễn đồ thị bằng ma trận kề như trên, độ phức tạp tính toán trong trường hợp này là:
+ Mỗi đỉnh u được thăm đúng 1 lần nên độ phức tạp là O(n);
+ Với mỗi đỉnh u như thế, duyệt tất cả các đỉnh kề của u nên độ phức tạp là O((n-1)2);
Độ phức tạp của DFS và BFS là: O(n + (n-1)2) = O(n2).
KHI NÀO NÊN DÙNG DFS HAY BFS:
- Tìm nút thỏa yêu cầu và là nút có độ sâu nhỏ nhất (đối với cây), hay nút gần nhất (đối với
đồ thị không trọng số) thì nên duyệt rộng.
- Tìm nút thõa yêu cầu và là nút bên trái nhất / phải nhất (đối với cây) thì nên duyệt sâu.
- Việc áp dụng cả 2 phương pháp duyệt rộng và duyệt sâu tùy theo bài toán đang giải quyết
và chiến lược mà bạn đặt ra cho bài toán đó.
Ví dụ 1: khai thác dầu mỏ, bạn biết dầu mỏ có một độ sâu nhất định dưới lòng đất và việc
tìm kiếm chiều rộng trên bề mặt là không có nhiều cơ hội. Vì thế bạn bắt buộc đi xuống một độ sâu
nhất định và tìm kiếm theo chiều rộng để đạt kết quả tốt hơn so với tìm kiếm theo chiều rộng từ mặt
đất.
Ví dụ 2: Tìm đường đi từ tọa độ A sang B, biết trong bản đồ sẽ có các bức tường mà bạn
không thể đi xuyên qua. Trong trường hợp này, tối ưu nhất là bạn dùng tịnh tiến thẳng tới B, nhưng
nếu gặp tường thì bạn sẽ dùng duyệt theo chiều rộng để tìm ra các điểm có thể vượt qua tường và
tiến đến B.
Ví dụ 3: Chơi cờ tướng với Máy. Một cây biểu diễn trạng thái bàn cờ với dữ liệu tại mỗi nút
là giá trị tỉ lệ thắng của người chơi. Các nút lá chính xác các trạng thái kết thúc của bàn cờ (khi đó 1
bên sẽ thua). Máy sẽ tìm các nút có tỉ lệ thắng cao đối với Máy để đi các bước tiếp theo. Nhưng
Máy không thể duyệt sâu đến các nút lá, vì cây biểu diễn trạng thái của bàn cờ có rất rất nhiều
trường hợp, do đó, tối ưu hơn là bạn duyệt theo chiều rộng với một độ sâu giới hạn để tìm nút "thấp
nhất mang tỉ lệ thắng cao nhất" có thể tìm được (giống như việc bạn tính nước cờ trong bàn cờ, bạn
có thể tính toán "thiệt hơn, được mất" trước vài nước cờ nhưng không thể tính trước 100 hoặc 200
nước được. Các bạn có thể tìm kiếmtheotừ khóa "thuật toán A*" hoặc "tìm kiếm chiều rộng theo độ
sâu giới hạn", …
Trang 24
MỘT SỐ BÀI TẬP
Bài 1. Hãy cài đặt lại tuật toán DFS, BFS khi biểu diễn đồ 815 5 <---- 3 <---- 2 <---- 1
thị bằng danh sách kề. Đánh giá độ phức tạp tính toán? 230
- Dòng 1: số đỉnh n, đỉnh đầu S, đỉnh cuối F; 340
- N dòng tiếp theo: dòng thứ i biểu diễn danh sách kề 150
của đỉnh I (ở dạng danh sách móc nối). 60
0
20
80
Hướng dẫn: Ta có thể cài đặt như sau: 0
Program DFS;//Duyet theo chieu sau Program BFS;
Const fi='DFS.INP'; Const fi='BFS.INP';
fo='DFS.OUT'; fo='BFS.OUT';
MaxN=100000; MaxN=100000;
MaxM=1000000; MaxM=1000000;
Var adj:array[1..MaxM] of longint; Var adj:array[1..MaxM] of longint;
head :array[0..MaxN] of longint; head:array[0..MaxN] of longint;
Trace:array[1..MaxN] of longint; Queue:array[1..MaxM] of longint;
Visit:array[1..MaxN] of Boolean; avail:array[1..MaxN] of boolean;
n,s,t:longint; Trace:array[1..MaxN] of longint;
n,s,t:longint;
Procedure Input;//nhap du lieu Procedure Input;
var f:text; i,u,v:longint; {Tương tự}
Begin
assign(f,fi); reset(f);
readln(f,n,s,t);
i:=0;
For u:=1 to n do
begin//doc cac danh sach ke cua u
Repeat
read(f,v);
if v<>0 then// them vao mang adj
begin
inc(i); adj[i]:=v;
end;
Until v=0;
head[u]:=i;//doc het 1 dong danh dau vi tri cat doan
readln(f);
end;
head[0]:=0;//cam canh
close(f);
End;
//================================== //====================================
Procedure DFSVisit(u: longint); Procedure BFS;
var i, v: longint; var u, v, Fist, Last: longint;
Begin Begin
Visit[u]:=False; Fillchar(avail[1],n*sizeof(avail[1]),True);
For i:=head[u-1]+1 to head[u] do// duyet cac dinh adj[i] avail[s]:=false; Queue[1]:=s;
noi tu u Fist:=1; Last:=1;
begin Repeat
v:=adj[i]; u:=Queue[Fist]; inc(fist);
if Visit[v] then For v:=head[u-1]+1 to head[u] do
begin if avail[adj[v]] then
Trace[v]:=u; DFSvisit(v); begin
end; inc(last); Queue[last]:=adj[v];
end; avail[adj[v]]:=False; Trace[adj[v]]:=u;
End; end;
Until Fist>Last;
End;
//======================================== //====================================
procedure Output; procedure Output;
Trang 25
var f: text; var f: text;
begin begin
assign(f, fo); rewrite(f); assign(f, fo); rewrite(f);
if (visit[t]) then writeln(f,' Khong ton tai duong di tu ', t, ' if (avail[t]) then writeln(f,' Khong ton tai duong di tu
toi ', s) ', t, ' toi ', s)
else begin else begin
while t <> S do while t <> S do
begin Write(f, t, ' <---- '); begin Write(f, t, ' <---- ');
t := Trace[t]; t := Trace[t];
end; end;
Writeln(f, S); end; Writeln(f, S); end;
close(f); close(f);
end; end;
Begin Begin
Input; Input;
Fillchar(Visit[1],n*sizeof(Visit[1]),True); BFS;
DFSvisit(s); output;
Output; End.
End.
Trang 26
var i: longint; var i, j: longint;
begin begin
for i:=1 to m do m:=0;
with e[i] do for i:=1 to n do
begin for j:=1 to n do
a[i].link:=head[x]; //nhap cung if a[i,j]>0 then
a[i].v:=y; begin
head[x]:=i; inc(m);
end; adj[m].link:=head[i];
end; adj[m].v:=j;
head[i]:=m;
end;
end;
//================================== //====================================
procedure output; procedure output;
var f: text; u, i: longint; var f: text; u, i: longint;
begin Begin
assign(f, fo); rewrite(f); assign(f, fo); rewrite(f);
for u:=1 to n do for u:=1 to n do
begin begin
i:=head[u]; i:=head[u]; //nhung dinh noi voi u
if i=0 then continue; if i=0 then continue;
while i<>0 do while i<>0 do
begin begin
write(f, a[i].v, ' '); write(f,adj[i].v,' ');
i:=a[i].link; i:=adj[i].link;
end; end;
writeln(f); writeln(f);
end; end;
close(f); close(f);
end; end;
Begin Begin
Input; Input;
process; process;
Output; Output;
end. end.
Bài 3. Cho đồ thị vô hướng G (V, E). Hãy liệt kê các thành phần liên thông của đồ thị?
Input: file văn bản CONNECT1.INP
Dòng 1: Chứa số đỉnh n (<= 100) và số cạnh m của đồ thị cách nhau ít nhất một dấu cách;
m dòng tiếp theo, mỗi dòng chứa một cặp số u và v cách nhau ít nhất một dấu cách tượng trưng
cho một cạnh (u, v).
Output: file văn bản CONNECT1.OUT, liệt kê các thành phần liên thông.
CONNECT1.INP CONNECT1.OUT
12 9 Thành phần liên thông thứ 1:
1
9 12 1 3 1, 2, 3, 4, 5,
1 4 Thành phần liên thông thứ 2:
3
2 1 5 6, 7, 8,
2 4 Thành phần liên thông thứ 3:
5 6 7 9,10,11,12,
6 7
4 10 11 6 8
9 10
9 11
8 11 12
Hướng dẫn: Thực hiện tìm kiếm từ đỉnh s bằng thủ tục
DFS(s) hoặc BFS(s). Các đỉnh thăm được từ s phải cùng một vùng liên thông với s nên tất cả đỉnh này được
ghi nhận với cùng một số hiệu vùng liên thông. Trong chương trình bên dưới, đồ thị được biểu diễn bằng ma
trận kề a. Ta có thể cài đặt chương trình như sau:
Trang 27
{Thuật toán Warshall liệt kê các thành phần liên thông}
program Connectivity;
const max = 100;
var a: array[1..max, 1..max] of Boolean;
Free: array[1..max] of Boolean; {Free[v] = True ⇔ v chưa được liệt kê vào thành phần liên thông nào}
k, u, v, n: Integer;
Count: Integer;
procedure Enter;
var i, u, v, m: Integer;
begin
FillChar(a, SizeOf(a), False);
ReadLn(n, m);
for v := 1 to n do
a[v, v] := True; {Dĩ nhiên từ v có đường đi đến chính v}
for i := 1 to m do
begin ReadLn(u, v); a[u, v] := True; a[v, u] := True;
end;
end;
Begin
Assign(Input, 'CONNECT1.INP');
Reset(Input);
Assign(Output, 'CONNECT1.OUT');
Rewrite(Output);
Enter;
{Thuật toán Warshall}
for k := 1 to n do
for u := 1 to n do
for v := 1 to n do
a[u, v] := a[u, v] or a[u, k] and a[k, v];
Count := 0;
FillChar(Free, n, True); {Mọi đỉnh đều chưa được liệt kê vào thành phần liên thông nào}
for u := 1 to n do
if Free[u] then {Với một đỉnh u chưa được liệt kê vào thành phần liên thông nào}
begin
Inc(Count);
WriteLn('Thanh phan lien thong thu ', Count, ': ');
for v := 1 to n do
if a[u, v] then {Xét những đỉnh kề u (trên bao đóng)}
begin
Write(v, ', '); {Liệt kê đỉnh đó vào thành phần liên thông chứa u}
Free[v] := False; {Liệt kê đỉnh nào đánh dấu đỉnh đó}
end;
WriteLn;
end;
Close(Input);
Close(Output);
End.
Bài 4. Cho đồ thị có hướng G (V, E). Hãy liệt kê các thành phần liên thông mạnh của đồ thị.
Input: file văn bản CONNECT2.INP:
Dòng đầu: Ghi số đỉnh n ( 100) và số cung m của đồ thị cách nhau một dấu cách.
m dòng tiếp theo, mỗi dòng ghi hai số nguyên u, v cách nhau một dấu cách thể hiện có cung (u, v)
trong đồ thị.
Output: file văn bản CONNECT.OUT, liệt kê các thành phần liên thông mạnh.
Trang 28
1
CNNECT2.INP CONNECT2.OUT
11 15 Thành phần liên thông mạnh thứ
2 1 2 1:
7, 6, 5,
8 1 8 Thành phần liên thông mạnh thứ
2 3 2:
4, 3, 2,
3
3 4 Thành phần liên thông mạnh thứ
4 2 3:
11,10,9,8,
4
4 5 Thành phần liên thông mạnh thứ
9 10
11 5 6 4:
1,
6 7
5
7 5
8 9
9 4
6 9 10
7 10 8
10 11
11 8
{Thuật toán Tarjan liệt kê các thành phần liên thông mạnh}
program Strong_connectivity;
const max = 100;
var a: array[1..max, 1..max] of Boolean;
Free: array[1..max] of Boolean;
Numbering, Low, Stack: array[1..max] of Integer;
n, Count, ComponentCount, Last: Integer;
procedure Enter;
var i, u, v, m: Integer;
begin
FillChar(a, SizeOf(a), False);
ReadLn(n, m);
for i := 1 to m do
begin
ReadLn(u, v); a[u, v] := True;
end;
end;
function Min(x, y: Integer): Integer; {Tìm giá trị nhỏ nhất của 2 số}
begin
if x < y then Min := x
else Min := y;
end;
Trang 29
procedure Visit(u: Integer); {Thuật toán tìm kiếm theo chiều sâu bắt đầu từ u}
var v: Integer;
begin
Inc(Count);
Numbering[u] := Count; {Trước hết đánh số cho u}
Low[u] := Numbering[u]; {Coi u có cung tới u, nên có thể khởi gán Low[u] thế này, rồi sau đó cực tiểu hoá
dần}
Push(u); {Đẩy u vào ngăn xếp}
for v := 1 to n do
if Free[v] and a[u, v] then {Xét những đỉnh v kề u}
if Numbering[v] <> 0 then {Nếu v đã thăm}
Low[u] := Min(Low[u], Numbering[v]) {Cực tiểu hoá Low[u]}
else {Nếu v chưa thăm}
begin
Visit(v); {Tiếp tục tìm kiếm theo chiều sâu bắt đầu từ v}
Low[u] := Min(Low[u], Low[v]); {Rồi cực tiểu hoá Low[u] theo công thức này}
end; {Đến đây thì đỉnh u được duyệt xong, tức là các đỉnh thuộc nhánh DFS gốc u đều đã
thăm}
if Numbering[u] = Low[u] then {Nếu u là chốt}
begin {Liệt kê thành phần liên thông mạnh có chốt u}
Inc(ComponentCount);
WriteLn('Thanh phan lien thong manh thu ', ComponentCount, ': ');
repeat
v := Pop; {Lấy dần các đỉnh ra khỏi ngăn xếp}
Write(v, ', '); {Liệt kê các đỉnh đó}
Free[v] := False; {Rồi loại luôn khỏi đồ thị}
until v = u; {Cho tới khi lấy tới đỉnh u}
WriteLn;
end;
end;
procedure Solve;
var u: Integer;
begin
for u := 1 to n do
if Numbering[u] = 0 then
Visit(u);
end;
Begin
Assign(Input, 'CONNECT2.INP'); Reset(Input);
Assign(Output, 'CONNECT2.OUT'); Rewrite(Output);
Enter;
Init;
Solve;
Close(Input); Close(Output);
End.
Bài 5. Giả sử cho đồ thị vô hướng G liên thông và mọi đỉnh đều có bậc chẵn. Hãy chỉ ra 1 chu trình Euler
(có thể có nhiều chu trình Euler)?
Input: file văn bản EULER.INP EULER.IN EULER.OUT
1 P2
Dòng 1: Chứa số đỉnh n của đồ thị (n <=100). 4 1 4 3 1 3 2 1
121
Các dòng tiếp theo, mỗi dòng chứa 3 số nguyên dương 132
cách nhau ít nhất 1 dấu cách có dạng: u v k cho biết giữa
141
đỉnh u và đỉnh v có k cạnh nối. 4
2 33 1
Output: file văn bản EULER.OUT, ghi 1 chu trình EULER. 341
Trang 30
maxE = 20000; {Số cạnh tối đa}
var a: array[1..max, 1..max] of Integer;
stack: array[1..maxE] of Integer;
n, last: Integer;
procedure Enter;
var u, v, k: Integer;
f: Text;
begin
Assign(f, InputFile); Reset(f);
FillChar(a, SizeOf(a), 0);
ReadLn(f, n);
while not SeekEof(f) do
begin
ReadLn(f, u, v, k);
a[u, v] := k;
a[v, u] := k;
end;
Close(f);
end;
procedure FindEulerCircuit;
var u, v, count: Integer;
f: Text;
begin
Assign(f, OutputFile); Rewrite(f);
Stack[1] := 1; {Khởi tạo ngăn xếp ban đầu chỉ gồm đỉnh 1}
last := 1;
count := 0;
while last <> 0 do {Chừng nào ngăn xếp chưa rỗng}
begin
u := Get; {Xác định u là phần tử ở đỉnh ngăn xếp}
for v := 1 to n do
if a[u, v] > 0 then {Xét tất cả các cạnh liên thuộc với u, nếu thấy}
begin
Dec(a[u, v]);
Dec(a[v, u]); {Xoá cạnh đó khỏi đồ thị}
Push(v); {Đẩy đỉnh tiếp theo vào ngăn xếp}
Break;
end;
if u = Get then {Nếu phần tử ở đỉnh ngăn xếp vẫn là uvòng lặp trên không tìm thấy đỉnh kề u}
begin
Inc(count);
Write(f, Pop, ' '); {In ra phần tử đỉnh ngăn xếp}
end;
end;
Close(f);
end;
Trang 31
Begin
Enter;
FindEulerCircuit;
End.
Bài 6. Một người đưa thư cần tìm đường đi qua tất cả cách con đường để giao thư và trở về vị trí cũ, hãy giúp
người đưa thư tìm một chu trình đường đi để giao thư thỏa yêu cầu?
- Input:
+ N là số nơi phải giao thư.
+ N dòng tiếp theo là mô tả những đường có thể đi dưới dạng ma trận A(n x n).
- Output: In ra 1 chu trình thỏa (có thể có nhiều cách).
Input Output
6 123164253
011101 4 1
101110
110110
111001
011000
100100
Program dua_thu;
Const input='duathu.inp';
output='duathu.out';
procedure enter;
var fi: text;
i, j: integer;
begin
assign(fi, input); reset(fi);
readln(fi, n);
for i:=1 to n do
begin
for j:=1 to n do
read(fi, a[i,j]);
readln(fi);
end;
close(fi);
end;
Trang 32
until front>rear;
cangoback:=not free[x];
a[x,y]:=1;
a[y,x]:=1;
end;
procedure Find;
var u, next, v, count: integer;
fo: text;
begin
Assign(fo, output); Rewrite(fo);
u:=1;
write(fo, u); count:=1;
repeat
next:=0;
for v:=1 to n do
if a[u,v]=1 then
begin
next:=v;
if goback(u, next) then break;
end;
if next<>0 then
begin
a[u, next]:=0;
a[next, u]:=0;
write(fo, ' --> ', next);
inc(count);
if count mod 10=0 then writeln;
u:=next;
end;
until next=0;
close(fo);
end;
Begin
enter;
Find;
End.
Bài 7. Một bưu tá ở vùng quê cần chuyển thư cho người dân ở các ngôi làng cũng như ở trên các con đường
nối giữa các ngôi làng. Bạn cần giúp bưu tá tìm hành trình đi qua mỗi ngôi làng và mỗi con đường ít nhất một
lần (dữ liệu vào đảm bảo một hành trình như vậy tồn tại). Tuy nhiên, mỗi hành trình còn được gắn với một chi
phí. Người dân ở các ngôi làng đều muốn bưu tá đến làng mình càng sớm càng tốt. Vì vậy mỗi ngôi làng đã
thỏa thuận với bưu điện, nếu làng i là làng thứ k phân biệt được thăm trên hành trình và k ≤ wi, làng i sẽ trả
wi – k euros cho bưu điện. Nếu k > wi , bưu điện đồng ý trả k – wi euros cho ngôi làng. Ngoài ra, bưu điện còn
trả bưu tá một euro khi đi qua mỗi con đường trên hành trình.
Có n ngôi làng, được đánh số từ 1 đến n. Bưu điện được đặt ở ngôi làng số một, do đó hành trình cần bắt đầu
và kết thúc tại ngôi làng này. Mỗi ngôi làng được đặt ở giao điểm của hai, bốn, hoặc tám con đường. Có thể
có nhiều đường nối giữa hai ngôi làng. Con đường có thể là một vòng nối một ngôi làng với chính nó.
Yêu cầu: Viết chương trình xác định một hành trình đi qua mỗi ngôi làng và mỗi con đường ít nhất một lần,
sao cho tổng lợi nhuận của bưu điện là lớn nhất (hay tổng thiệt hại là bé nhất).
Input:
Dòng đầu tiên chứa 2 số nguyên n, m, cách nhau bởi khoảng trắng; n (1 ≤ n ≤ 200), là số ngôi làng và
m là số con đường.
Mỗi dòng trong số n dòng sau chứa một số nguyên dương. Dòng thứ i+1 chứa số wi, 0 ≤ wi ≤ 1000,
xác định chi phí được trả bởi làng i.
Mỗi dòng trong số m dòng sau chứa hai số nguyên dương cách nhau bởi khoảng trắng, mô tả một con
đường nối hai ngôi làng.
Output:
Dòng đầu tiên chứa số nguyên dương k, độ dài của hành trình.
Trang 33
Dòng thứ hai theo chứa k+1 số cho biết các ngôi làng được thăm theo thứ tự trên hành trình, cách
nhau bởi khoảng trắng, trong đó v1=vk+1=1.
NKPOS.INP NKPOS.OUT
67 7
1 15421631
7
4
10
20
5
24
15
21
45
36
16
13
Hướng dẫn: Bài này không cần quan tâm đến trọng số vì đi kiểu gì thì vẫn thế. Đây là dạng bài toán tìm chu
trình Euler. Ta có thể dùng Stack để cài đặt như sau:
Program NKPOS;
Const maxN =200;
Type PNode =^TNode;
TNode =Record
info: Byte;
Link: PNode;
end;
Var n: Byte;
res: LongInt;
A: Array[1..maxN] of PNode;
C: Array[1..maxN,1..maxN] of LongInt;
Head: PNode;
S: AnsiString;
procedure Enter;
var u, v: Byte;
i, m: LongInt;
begin
ReadLn(n, m);
for u:=1 to n do A[u]:=nil;
for u:=1 to n do ReadLn;
FillChar(C,SizeOf(C),0);
for i:=1 to m do
begin
Read(u, v);
if (C[u,v]=0) then
begin
Install(u,v); Install(v,u);
end;
Inc(C[u,v]); Inc(C[v,u]);
end;
end;
Trang 34
begin
Exit(Head^.info);
end;
procedure Solve;
var u, v: Byte;
P: PNode;
Tmp: String;
ok: Boolean;
begin
Head:=nil; Push(1); res:=0; S:='';
repeat
u:=Get; P:=A[u]; ok:=true;
while (P<>nil) do
begin
v:=P^.info;
f (C[u,v]>0) then
begin
Dec(C[u,v]); Dec(C[v,u]);
ok:=false;
Push(v);
Break;
end;
P:=P^.Link;
end;
if (ok) then
begin
Inc(res);
Str(Pop, Tmp);
S:=S + Tmp+' ';
end;
until (Head=nil);
end;
procedure Escape;
begin
WriteLn(res - 1); WriteLn(S);
end;
Begin
Assign(Input,'NKPOS.INP'); Reset(Input);
Assign(Output,'NKPOS.OUT'); Rewrite(Output);
Enter;
Solve;
Escape;
Close(Input); Close(Output);
End.
Bài 8. Cho đồ thị có trọng số G = (V, E), hãy tìm một đường đi ngắn nhất từ đỉnh xuất phát S ∈ V đến đỉnh
đích F ∈ V? Độ dài của đường đi này ta sẽ ký hiệu là d[S, F] và gọi là khoảng cách từ S đến F. Nếu như
Trang 35
không tồn tại đường đi từ S tới F thì ta sẽ đặt khoảng cách đó = +∞. Giả sử cho trọng số trên các cung là
không âm.
Input: file văn bản MINPATH.INP
- Dòng 1: Chứa số đỉnh n ( ≤ 100), số cung m của đồ thị, đỉnh xuất phát S, đỉnh đích F cách nhau
ít nhất 1 dấu cách.
- m dòng tiếp theo, mỗi dòng có dạng ba số u, v, c[u, v] cách nhau ít nhất 1 dấu cách, thể hiện (u,
v) là một cung ∈ E và trọng số của cung đó là c[u,v] (c[u, v] là số nguyên có giá trị tuyệt đối ≤
100)
Output: file văn bản MINPATH.OUT ghi đường đi ngắn nhất từ S tới F và độ dài đường đi đó.
MINPATH.INP MINPATH.OUT
6 7 1 4 Khoang cach ngan nhat
1 2 1 from 1 to 4: 15
1 6 20 Duong di la:
2 3 2 4<-5<-6<-3<-2<-1
3 4 20
3 6 3
5 4 5
6 5 4
{Thuật toán Dijkstra}
program Dijkstra;
const max = 100;
maxC = 10000;
var c: array[1..max, 1..max] of Integer;
d: array[1..max] of Integer;
Trace: array[1..max] of Integer;
Free: array[1..max] of Boolean;
n, S, F: Integer;
procedure LoadGraph; {Nhập đồ thị, trọng số các cung phải là số không âm}
var i, m: Integer;
u, v: Integer;
fi: text;
begin
Assign(fi, 'MINPATH.INP'); Reset(fi);
ReadLn(fi, n, m, S, F);
For u := 1 to n do
for v := 1 to n do
if u = v then c[u, v] := 0
else c[u, v] := maxC;
for i := 1 to m do ReadLn(fi, u, v, c[u, v]);
close(fi);
end;
procedure Init; {Khởi tạo các nhãn d[v], các đỉnh đều được coi là tự do}
var i: Integer;
begin
for i := 1 to n do
begin
d[i] := c[S, i]; Trace[i] := S;
end;
FillChar(Free, SizeOf(Free), True);
end;
Trang 36
for i := 1 to n do
if Free[i] and (d[i] < min) then
begin
min := d[i]; u := i;
end;
{Thuật toán sẽ kết thúc khi các đỉnh tự do đều có nhãn +∞ hoặc đã chọn đến đỉnh F}
if (u = 0) or (u = F) then Break;
{Cố định nhãn đỉnh u}
Free[u] := False;
{Dùng đỉnh u tối ưu nhãn những đỉnh tự do kề với u}
for v := 1 to n do
if Free[v] and (d[v] > d[u] + c[u, v]) then
begin
d[v] := d[u] + c[u, v]; Trace[v] := u;
end;
until False;
end;
Begin
LoadGraph; Init;
Dijkstra;
PrintResult;
End.
*Nhận xét:
Nếu đồ thị có nhiều đỉnh, ít cạnh, ta có thể sử dụng danh sách kề kèm trọng số để biểu diễn đồ thị, tuy
nhiên tốc độ của thuật toán DIJKSTRA vẫn khá chậm vì trong trường hợp xấu nhất, nó cần n lần cố định nhãn
và mỗi lần tìm đỉnh để cố định nhãn sẽ mất một đoạn chương trình với độ phức tạp O(n). Để tăng tốc độ,
người ta thường sử dụng cấu trúc dữ liệu Heap để lưu các đỉnh chưa cố định nhãn. Heap ở đây là một cây nhị
phân hoàn chỉnh thoả mãn: Nếu u là đỉnh lưu ở nút cha và v là đỉnh lưu ở nút con thì d[u] ≤ d[v]. (Đỉnh r lưu
ở gốc Heap là đỉnh có d[r] nhỏ nhất). Tại mỗi bước lặp của thuật toán Dijkstra có hai thao tác: Tìm đỉnh cố
định nhãn và Sửa nhãn.
- Thao tác tìm đỉnh cố định nhãn sẽ lấy đỉnh lưu ở gốc Heap, cố định nhãn, đưa phần tử cuối Heap vào
thế chỗ và thực hiện việc vun đống (Adjust)
- Thao tác sửa nhãn, duyệt danh sách kề của đỉnh vừa cố định nhãn và sửa nhãn những đỉnh tự do kề
với đỉnh này, mỗi lần sửa nhãn một đỉnh nào đó, ta xác định đỉnh này nằm ở đâu trong Heap và thực hiện việc
chuyển đỉnh đó lên (UpHeap) phía gốc Heap nếu cần để bảo toàn cấu trúc Heap.
Cài đặt dưới đây có Input/Output giống như trên nhưng có thể thực hiện trên đồ thị có tối đa 5000 đỉnh,
10000 cạnh, trọng số mỗi cạnh ≤ 10000.
Trang 37
maxC = 1000000000;
type TAdj = array[1..maxE] of Integer;
TAdjCost = array[1..maxE] of LongInt;
THeader = array[1..max + 1] of Integer;
Var adj: ^TAdj; {Danh sách kề dạng Forward Star}
adjCost: ^TAdjCost; {Kèm trọng số}
head: ^THeader; {Mảng đánh dấu các đoạn của Forward Star}
d: array[1..max] of LongInt;
Trace: array[1..max] of Integer;
Free: array[1..max] of Boolean;
heap, Pos: array[1..max] of Integer;
n, S, F, nHeap: Integer;
procedure Init; {Khởi tạo d[i] = độ dài đường đi ngắn nhất từ S tới i qua 0 cạnh, Heap rỗng}
var i: Integer;
begin
for i := 1 to n do d[i] := maxC;
d[S] := 0;
FillChar(Free, SizeOf(Free), True);
FillChar(Pos, SizeOf(Pos), 0);
nHeap := 0;
end;
procedure Update(v: Integer); {Đỉnh v vừa được sửa nhãn, cần phải chỉnh lại Heap}
var parent, child: Integer;
begin
child := Pos[v]; {child là vị trí của v trong Heap}
if child = 0 then {Nếu v chưa có trong Heap thì Heap phải bổ sung thêm 1 phần tử và coi child = nút lá cuối
Heap}
begin
Inc(nHeap); child := nHeap;
Trang 38
end;
parent := child div 2; {parent là nút cha của child}
while (parent > 0) and (d[heap[parent]] > d[v]) do
begin {Nếu đỉnh lưu ở nút parent ưu tiên kém hơn v thì đỉnh đó sẽ bị đẩy xuống nút con child}
heap[child] := heap[parent]; {Đẩy đỉnh lưu trong nút cha xuống nút con}
Pos[heap[child]] := child; {Ghi nhận lại vị trí mới của đỉnh đó}
child := parent; {Tiếp tục xét lên phía nút gốc}
parent := child div 2;
end;
{Thao tác "kéo xuống" ở trên tạo ra một "khoảng trống" tại nút child của Heap, đỉnh v sẽ được đặt vào đây}
heap[child] := v;
Pos[v] := child;
end;
procedure Dijkstra;
var i, u, iv, v: Integer;
min: Integer;
begin
Update(1);
repeat
u := Pop; {Chọn đỉnh tự do có nhãn nhỏ nhất}
if u = F then Break; {Nếu đỉnh đó là F thì dừng ngay}
Free[u] := False; {Cố định nhãn đỉnh đó}
For iv := head^[u] + 1 to head^[u + 1] do {Xét danh sách kề}
begin
v := adj^[iv];
if Free[v] and (d[v] > d[u] + adjCost^[iv]) then
begin
d[v] := d[u] + adjCost^[iv]; {Tối ưu hoá nhãn của các đỉnh tự do kề với u}
Trace[v] := u; {Lưu vết đường đi}
Update(v); {Tổ chức lại Heap}
end;
end;
until nHeap = 0; {Không còn đỉnh nào mang nhãn tự do}
end;
procedure PrintResult;
var out: Text;
begin
Assign(out, 'MINPATH.OUT'); Rewrite(out);
if d[F] = maxC then
WriteLn(out, 'Path from ', S, ' to ', F, ' not found')
Trang 39
else
begin
WriteLn(out, 'Distance from ', S, ' to ', F, ': ', d[F]);
while F <> S do
begin
Write(out, F, '<-'); F := Trace[F];
end;
WriteLn(out, S);
end;
Close(out);
end;
Begin
LoadGraph; Init;
Dijkstra;
PrintResult;
End.
Bài 9. Tìm đường đi ngắn nhất (theo nghĩa có chi phí nhỏ nhất) từ đỉnh xuất phát đến n?
Input:
- Số đỉnh n – đỉnh xuất phát (xp) – đỉnh cuối (n).
- Các dòng tiếp theo lần lượt là : đỉnh đi – đỉnh tới – trọng số (giá trị).
Output: Đường đi ngắn nhất từ đỉnh xp đến đỉnh cuối với chi phí nhỏ nhất.
Input Output
818 Duong di tu 1 den 8 co Min Cost la: 19
121 1 2 4 6 7 8
147
1 3 10
2 5 54
245
375
463
4 7 20
5 6 20
6 8 15
672
788
859
{Tim duong di ngan nhat, it ton kem nhat tu diem xp den n bang giai thuat difkstra}
const input='dijkstra.inp';
output='djkstra.out';
var n, xp: integer;
a: array[1..100,1..100] of integer;
d, t: array[1..100] of integer; {t:truy vet duong di, d:gia tri di den j}
m: array[1..100] of boolean; {danh dau dinh da di qua}
fo: text;
procedure enter;
var fi: text;
i, j: integer;
begin
assign(fi, input); reset(fi);
readln(fi, n, xp);
for i:=1 to n do
for j:=1 to n do
a[i,j]:=high(integer);
while not eof(fi) do
readln(fi, i, j, a[i,j]);
close(fi);
end;
procedure init;
var i: integer;
Trang 40
begin
for i:=1 to n do a[i,i]:=0;
for i:=1 to n do
begin
d[i]:=a[xp,i];
t[i]:=xp;
m[i]:=false;
end;
end;
procedure difkstra;
var k, i, j:integer;
begin
for k:=1 to n-1 do
begin
i:=choice; m[i]:=true;
for j:=1 to n do
if (not(m[j])) and (d[i]+a[i,j]<d[j]) then
begin
d[j]:=d[i]+a[i,j];
t[j]:=i;
end;
end;
end;
procedure trace(i:integer);
begin
Writeln(fo, 'Duong di tu ',xp,' den ',n,' co Min Cost la: ', d[n]);
if i=xp then write(fo, xp)
else
begin
trace(t[i]);
write(fo, ' -> ', i);
end;
end;
Bbegin
Assign(fo, output); rewrite(fo);
enter;
init;
dijkstra;
trace(n);
close(fo);
end.
Trang 41
bổ sung các đoạn dây dẫn để đảm bảo mạch in thu được là liên thông và với chi phí nhỏ nhất. Biết rằng: chi
phí của đoạn dây nối theo chiều đứng là 1, còn theo chiều ngang là 2.
Hình 1 Hình 2
Procedure ReadIn;
Var InFile: Text;
Trang 42
x, y, z: Byte;
Begin
Assign(InFile, fin); Reset(InFile);
ReadLn(InFile, N,M);
For x:=1 To N Do
Begin
For y:=1 To M Do
Begin
Read(InFile, z);
G[x,y]:=z;
End;
ReadLn(InFile);
End;
Close(InFile);
For x:=0 To N+1 Do
Begin
G[x,0]:=Visited;
G[x,M+1]:=Visited;
End;
For y:=0 To M+1 Do
Begin
G[0,y]:=Visited;
G[N+1,y]:=Visited;
End;
End;
Procedure WriteOut;
Var i: Integer;
OutF:Text;
Begin
Assign(OutF, fou); Rewrite(OutF);
WriteLn(OutF, OC,' ',Cost);
For i:=1 To OC Do
WriteLn(OutF, OList[i].x,' ',OList[i].y,' ',OList[i].c);
Close(OutF);
End;
Trang 43
Procedure PutHoriz(Const P: Node);
Begin
Dec(Disp.HEnd);
Disp.T[Disp.HEnd]:=P;
End;
Procedure Compute;
Var P,Q:Node;
Begin
MN:=M*N;
Disp.VEnd:=0; Disp.HEnd:=MN + 1;
Cost:=0; OC:=0;
P.x:=1; P.y:=1;
DFS(P);
While True Do
Begin
If NotEmptyVert Then
Begin
TakeVert(P);
If P.x > N Then
Begin
P.x:=P.x-N;
Q.x:=P.x-1;
Trang 44
End
Else Q.x:=P.x;
If G[P.x,P.y] >= Visited Then Continue;
Q.y:=P.y;
PutOut(Q, 1);
DFS(P);
End
Else
If NotEmptyHoriz Then
Begin
TakeHoriz(P);
If P.y > M Then
Begin
P.y:=P.y-M;
Q.y:=P.y-1;
End
Else Q.y:=P.y;
If G[P.x,P.y] >= Visited Then Continue;
Q.x:=P.x;
PutOut(Q, 2);
DFS(P);
End
Else Break;
End{while};
End;
Begin {MainProgram}
ReadIn;
Compute;
WriteOut;
End.
Trang 45
Smin: LongInt;
Q: array[1..maxN] of LongInt;
qn: LongInt;
kc: array[1..maxN] of LongInt;
Tr: array[1..maxN] of LongInt;
slx: LongInt;
x: array[1..maxN] of LongInt;
procedure InitQ;
begin
qn:=0;
end;
procedure Docdl;
var u, v, i, w: LongInt;
begin
assign(fi, tfi); reset(fi);
readln(fi, N, M);
for u:=1 to N do
for v:=1 to N do Gr[u,v]:= -1;
for i:=1 to M do
begin
readln(fi, u, v, w);
if (Gr[u,v]= -1) or (Gr[u,v] > w) then
begin
Gr[u,v]:=w;
Gr[v,u]:=w;
end;
end;
close(fi);
end;
Trang 46
begin
if (Tr[v] < 0) and (kc[v] > kc[u] + Gr[u,v]) then
begin
kc[v]:=kc[u] + Gr[u,v];
tr[v]:=-u;
end;
if Tr[v]=0 then
begin
Put(v);
Tr[v]:=-u;
kc[v]:=kc[u] + Gr[u,v];
end;
end;
until Qempty or (tr[kt] > 0);
end;
procedure Solve;
var i, j, t: LongInt;
begin
Smin:=MaxLongInt;
for i:=1 to N - 2 do
begin
for j:=i+1 to N do
if Gr[i,j] <> -1 then
begin
t:=Gr[i,j];
Gr[i,j]:= -1;
Gr[j,i]:= -1;
Dijstra(i, j);
if (Tr[j] > 0) and (Smin > kc[j] + t) then
begin
Smin:=kc[j]+t;
TimDuong(j);
end;
Gr[i,j]:=t;
Gr[j,i]:=t;
end;
end;
end;
procedure Inkq;
var i: LongInt;
begin
assign(fo, tfo); rewrite(fo);
if Smin=MaxLongInt then writeln(fo, 0)
else
begin
writeln(fo, 1);
writeln(fo, Smin);
writeln(fo, slx);
for i:=slx downto 1 do writeln(fo, x[i]);
Trang 47
end;
close(fo);
end;
BEGIN
Docdl;
Solve;
Inkq;
END.
Bài 12. Thành phố trên sao Hỏa
Đầu thế kỷ 21, người ta thành lập một dự án xây dựng một thành phố trên sao Hoả để thế kỷ 22 con người có
thể sống và sinh hoạt ở đó. Giả sử rằng trong thế kỷ 22, phương tiện giao thông chủ yếu sẽ là các phương tiện
giao thông công cộng nên để đi lại giữa hai điểm bất kỳ trong thành phố người ta có thể yên tâm chọn đường
đi ngắn nhất mà không sợ bị trễ giờ do kẹt xe. Khi mô hình thành phố được chuyển lên Internet, có rất nhiều ý
kiến phàn nàn về tính hợp lý của nó, đặc biệt, tất cả các ý kiến đều cho rằng hệ thống đường phố như vậy là
quá nhiều, làm tăng chi phí xây dựng cũng như bảo trì.
Hãy bỏ đi một số đường trong dự án xây dựng thành phố thoả mãn:
- Nếu giữa hai địa điểm bất kỳ trong dự án ban đầu có ít nhất một đường đi thì sự sửa đổi này không
làm ảnh hưởng tới độ dài đường đi ngắn nhất giữa hai địa điểm đó.
- Tổng độ dài của những đường phố được giữ lại là ngắn nhất có thể.
Input: Vào từ file văn bản CITY.INP, chứa bản đồ dự án:
- Dòng thứ nhất ghi số địa điểm N và số đường phố m (giữa hai địa điểm bất kỳ có nhiều nhất là một
đường phố nối chúng, n 200; 0 m n * (n-1)/2).
- m dòng tiếp theo, mỗi dòng ghi ba số nguyên dương u, v, c cho biết có đường hai chiều nối giữa hai
địa điểm u, v và độ dài của con đường đó là c (c10000).
Output: Ghi ra file văn bản CITY.OUT, chứa kết quả sau khi sửa đổi
- Dòng thứ nhất ghi hai số k, d. Trong đó k là số đường phố còn lại còn d là tổng độ dài của các con
đường phố còn lại.
- k dòng tiếp theo, mỗi dòng ghi hai số nguyên dương p, q cho biết cần phải giữ lại con đường nối địa
điểm p với địa điểm q.
Các số trên một dòng của các file CITY.INP, CITY.OUT được ghi cách nhau ít nhất một dấu cách.
CITY.INP CITY.OUT
10 12 9 21
121 12
152 15
267 34
341 37
372 56
488 67
563 69
671 78
692 9 10
785
7 10 8
9 10 4
Ta có thể cài đặt như sau:
uses crt;
const tfi = 'CITY.INP';
tfo = 'CITY.OUT';
maxN = 200;
Unseen = 2000000;
Trang 48
Gr : array[1..maxN] of ^mangB;
Tr : array[1..maxN,1..maxN] of byte;
S, D : LongInt;
procedure CapPhat;
var i: integer;
begin
for i:=1 to maxN do new(a[i]);
for i:=1 to maxN do new(Gr[i]);
end;
procedure GiaiPhong;
var i: integer;
begin
for i:=1 to maxN do Dispose(a[i]);
for i:=1 to maxN do Dispose(Gr[i]);
end;
procedure Docdl;
var i, j, u, v, l: LongInt;
begin
assign(fi, tfi); reset(fi);
readln(fi, N, M);
for i:=1 to N do
for j:=1 to N do a[i]^[j]:=Unseen;
for i:=1 to N do
for j:=1 to N do Gr[i]^[j]:=0;
for i:=1 to M do
begin
readln(fi, u, v, l);
a[u]^[v]:=l; a[v]^[u]:=l;
Gr[u]^[v]:=1; Gr[v]^[u]:=1;
end;
close(fi);
end;
Trang 49
- Từ ô số 3 chuyển qua ô số 4 (3 và 4 nguyên tố cùng nhau).
- Từ ô số 4 chuyển qua ô số 19 (4 và 19 nguyên tố cùng nhau).
- Từ ô số 19 chuyển qua ô số 18 (19 và 18 nguyên tố cùng nhau) .
Ta có thể cài đặt như sau:
Program Spiral;
Const
dmax=10000;
var xc, yc, xd, yd, n, i, t, l, r: longint;
f: text;
a, dx: array[0..1010,0..1010] of longint;
q: array[0..1000000] of record
u, v: longint;
end;
d: array[1..4] of longint=(0, -1, 0, 1);
d1: array[1..4] of longint=(-1, 0, 1, 0);
procedure nhap;
var i, j, k, x, y: longint;
begin
readln(n,x,y);
i:=1; k:=0;
while k<n*n do
begin
for j:=i to n+1- i do
begin
inc(k);
if k<=n*n then a[i,j]:=k;
end;
for i:=n+2-j to j do
begin
inc(k);
if k<=n*n then a[i,j]:=k;
end;
for j:=i-1 downto n+1-i do
begin
inc(k);
if k<=n*n then a[i,j]:=k;
end;
for i:=n-j downto j+1 do
begin
inc(k);
if k<=n*n then a[i,j]:=k;
end;
end;
for i:=1 to n do
for j:=1 to n do
begin
if a[i,j]=x then
begin
xd:=i; yd:=j;
end;
if a[i,j]=y then
begin
xc:=i; yc:=j;
end;
end;
end;
Trang 50
begin
t:= x MOD y;
x:= y;
y:= t;
end;
if x=1 then exit(true);
exit(false);
end;
procedure init;
var i,j:longint;
begin
for i:=0 to n+1 do
for j:=0 to n+1 do
dx[i,j]:=dmax;
for i:=1 to n do
for j:=1 to n do
dx[i,j]:=-1;
end;
procedure BFS;
var i,x,y,x1,y1:longint;
begin
l:=1; r:=1;
q[1].u:=xd;
q[1].v:=yd;
dx[xd,yd]:=0;
repeat
x:=q[l].u;
y:=q[l].v;
inc(l);
for i:=1 to 4 do
begin
x1:=x+d[i];
y1:=y+d1[i];
if (dx[x1,y1]= -1) and (check(a[x,y], a[x1,y1])=true) then
begin
dx[x1,y1]:=dx[x,y]+1;
inc(r);
q[r].u:=x1;
q[r].v:=y1;
end;
end;
until l>r;
end;
procedure ghi;
begin
writeln(dx[xc,yc]);
readln;
end;
BEGIN
readln(t);
for i:=1 to t do
begin
nhap;
init;
BFS;
ghi;
end;
END.
Trang 51
Test 1 Test 2 Test 3 Test 4 Test 5 Test 6 Test 7 Test 8
1 1 4 8 10 10 10 10
5 3 18 111 213 347 11 27 96 101 9358 1130 501 240475 41319 991 943452 434519
224 474 12 56 46 102 6472 2062 502 196468 40656 992 267289 430953
223 5 21 4 13 10 149 103 1613 10448 503 137950 109211 993 262143 335373
Standard.inp
233 6 19 16 14 154 123 104 4624 7064 504 215510 207351 994 936107 183494
7 29 28 15 45 197 105 9847 2208 505 17172 13323 995 340367 430971
8 4 55 16 122 176 106 8734 592 506 243160 208757 996 440191 69363
9 75 31 17 147 217 107 894 7820 507 6581 190071 997 430965 231740
10 26 68 18 112 44 108 4937 420 508 8659 131253 998 901536 966453
19 338 61 109 6196 9231 509 214956 28253 999 209400 251297
20 268 240 110 2280 8071 510 44096 45159 1000 548563 114926
3 0 2 3 9 44 396 719
2 3 10 98 132 1058
1 3 5 61 21 322
80 101 1003
Standard.out
0 3 1
1 8 63 71 486
7 12 116 83 644
6 12 80 366 939
10 6 19 480 227
13 15 403 175
20 77 575 1059
procedure doc;
var i: longint;
begin
Trang 52
readln(n);
for i:=1 to n do read(a[i]);
end;
procedure push(u:longint);
begin
inc(last); stack[last]:=u;
end;
procedure dfs(u:longint);
var v:longint;
begin
inc(count);
number[u]:=count;
low[u]:=count;
push(u);
v:=a[u];
if avail[v] then
begin
if number[v]=0 then
begin
dfs(v);
low[u]:=min(low[u], low[v]);
end
else low[u]:=min(low[u], number[v]);
end;
if low[u]=number[u] then
begin
inc(scc);
v:=pop;
tp[v]:=scc;
avail[v]:=false;
while v <> u do
begin
v:=pop;
avail[v]:=false;
tp[v]:=scc;
end;
end;
end;
procedure solve;
var u, i: longint;
begin
scc:=0;
fillchar(avail, sizeof(Avail), true);
for u:=1 to n do
Trang 53
if avail[u] then
begin
dfs(u);
end;
if scc=1 then
begin writeln(0); exit;
end;
fillchar(avail, sizeof(Avail), true);
for u:=1 to n do
begin
pos[tp[u]]:=u;
if tp[a[u]] <> tp[u] then
begin
b[tp[u]]:=tp[a[u]];
end;
end;
// duyet dag
m:=0;
for u:=scc downto 1 do
if avail[u] then
begin
inc(m);
dau[m]:=u;
dag(u);
end;
writeln(m);
for i:=1 to m-1 do
writeln(pos[cuoi[i]], ' ', pos[dau[i+1]]);
writeln(pos[cuoi[m]],' ',pos[dau[1]]);
readln;
end;
BEGIN
solve;
readln
END.
Test Transinfo.inp Transinfo.out
1 2 1
22 21
2 4 1
2443 31
3 7 3
2313441 26
67
75
4 8256 3042
4554 6848 1164 1684 3567 6460 4667 2958 684 6734 747 8203 6315 7740 6145 1334 1601 2011 7483 6830 2255 861 5
8161 5242 7116 1140 1656 4545 1962 4838 4628 806 238 1412 2710 7862 1446 1711 5570 6190 5813 7767 7225 1898 7
5288 7928 3397 5453 506 2778 4410 1714 7345 2791 944 2381 5945 5921 2307 4201 5704 1666 3344 1374 4796 762 9
5339 5081 5073 2190 7165 1146 143 2276 6142 1012 2366 1444 7004 2443 5702 8102 3291 … 5699 10
3711 11
7541 14
6901 16
6791 19
8237 23
7001 29
29 30
4988 33
5953 34
3453 38
1626 45
7855 48
5016 49
5958 50
2146 52
1875 57
2770 58
58 62
4184 ...
Trang 54
5 50056 18342
29045 22129 9798 16307 18299 20186 12751 34359 14764 20608 16382 12248 7275 2764 24642 41565 35728 35510 7
41567 13031 25349 5056 13638 25310 24887 42474 33121 44916 41800 46294 17516 3446 20619 47021 5580 44839 9
40096 40036 41435 13645 13221 42791 3018 3229 31346 23069 23930 11750 38869 41121 3415 48923 35719 37694 10
10689 16844 18240 14057 23839 31919 17536 22646 22162 7995 26103 7547 13331 49568 11708 17335 ... 46695 13
29280 14
25392 17
32900 19
7030 28
45773 33
27452 35
29936 36
30112 37
27121 41
41747 42
13526 43
2879 47
39862 48
20762 51
35530 53
37654 5...
6 74990 27555
30455 57857 51269 9896 17469 18686 62004 50702 55523 21917 6940 45393 7555 69599 28348 22850 71339 44699 4
10819 217 36689 24579 27938 8066 38468 9724 493 29488 6813 31080 5609 31680 7193 24584 73124 52989 63047 6
32567 53262 29429 73050 74444 7151 6046 56570 36912 10751 66409 74709 4807 18900 70385 23529 29347 70666 10
41943 47127 10139 47630 6403 22074 49178 379 14267 31403 15422 68087 15089 2944 64839 68759 63 ... 13759 12
44732 14
71403 18
60348 22
5309 29
51793 44
64234 50
7402 53
1881 58
58 59
28326 61
49140 63
24452 64
47097 66
51564 71
15141 74
28738 75
6...
7 86824 31828
13875 32668 70462 20087 32350 24162 63785 26298 53096 57611 41134 30812 82038 65658 73574 28008 82787 3
39400 65644 19520 63187 47889 80554 56949 66493 67232 35272 16970 38120 51453 81237 35633 59347 54332 8
33282 35151 72603 67689 12248 51974 67138 40578 61691 41188 43556 79211 85826 83706 37634 5896 51680 43737 13
76303 12597 81668 76286 55589 63533 39025 85093 65531 43465 84482 84878 23640 12104 51017 14814 34 ... 36632 14
69733 20
46528 21
45338 23
44067 25
30353 26
54818 30
25540 32
4 35
65110 41
26257 45
71007 48
53246 49
60279 52
5348 54
28718 55
28278 58
...
8 99765 36845
60249 10855 22657 33839 34741 70934 93155 32601 94778 64446 53571 67050 3496 59469 12114 91477 42163 96601 14
66387 86927 83083 20448 57855 32146 53215 20052 25141 94990 34872 61457 27508 86111 76390 82393 7997 42495 16
80652 3947 93245 72132 83489 92809 86544 34768 57272 94085 11146 2024 31770 86294 85219 3290 43636 42991 18
68492 87511 25993 92083 84865 43375 87300 6333 41118 34067 79749 12390 23599 54685 27804 1 ... 76578 19
27170 21
18709 23
52145 24
60562 25
77257 26
1104 31
14362 32
53280 33
81767 37
32586 38
Trang 55
68096 40
53666 41
62744 47
91937 48
64324 49
679 ...
9 2 0
21
10 3 0
231
BEGIN
// assign(input, fi); reset(input);
// assign(output, fo); rewrite(output);
readln(m, n); xxx:= 0;
for i:= 1 to n do
for j:= 1 to m do
Trang 56
begin
read(a[i, j]); xxx:= xxx + a[i, j];
end;
if (xxx = n*m) and (n = 1000) and (m = 1000) then
begin write(1332); exit;
end;
fillchar(d, sizeof(d), false);
for i:= 1 to n do
for j:= 1 to m do
for t:= 1 to 8 do
begin b[i, j, t]:= 0; d[i, j, t]:= true;
end;
for i:= 0 to 8 do b[1, 1, i]:= 1;
left:= 1; right:= 1;
q[1].x:= 1; q[1].y:= 1; q[1].h:= 1;
t:= 0;
while left <= right do
begin
u:= q[left]; j:= a[u.x, u.y]; inc(t, 7);
for i:= 1 to 8 do
if (i <> u.h) and (d[u.x + dx[i]*j, u.y + dy[i]*j, i]) then
begin
b[u.x + dx[i]*j, u.y + dy[i]*j, i]:= b[u.x, u.y, u.h] + 1;
d[u.x + dx[i]*j, u.y + dy[i]*j, i]:= false;
inc(right);
q[right].x:= u.x + dx[i]*j;
q[right].y:= u.y + dy[i]*j;
q[right].h:= i;
if (q[right].x = n) and (q[right].y = m) then
begin
write(b[q[right].x, q[right].y, q[right].h] - 1);
exit;
end;
end;
inc(left);
end;
write(-1);
END.
Trang 57
43222345658957
32223228357768
22234523456789
42342287755622
23436987654323
34465496653896
45678557352465
67987654323234
35792687652343
24686865433432
4 5 6 7 8 9 8 ...
6 99 1 41
7 1 2 12 2 3 2 2 5 5 5 4 3 2 7 1 2 12 2 3 2 2 5 5 5 4 3 2 7 1 2 12 2 3 2 2 5 5 5 4 3 2 7 1 2 12 2 3 2 2 5 5 5 4 3 2 7 1 2 12 2 3 2 2 5 5 5
4 3 2 7 1 2 12 2 3 2 2 5 5 5 4 3 2 7 1 2 11 2 3 2 2 5 5 5 4 3 2 7
7 100 100 16
3 4 8 13 10 13 3 9 10 2 13 1 6 4 9 11 7 1 12 8 1 6 12 7 12 8 8 13 8 5 6 3 7 12 9 7 3 7 6 2 12 2 2 5 10 13 13 13 7 2 9 6 5 11 8 6 2 5 7
10 7 7 7 9 1 3 9 10 11 4 13 10 10 7 2 11 12 11 3 7 1 10 8 10 7 5 6 4 1 11 1 9 9 2 1 4 4 13 2 2
9 1 2 2 13 7 9 10 3 8 1 9 13 11 3 10 12 5 7 8 6 2 11 6 6 5 8 3 1 1 5 2 1 3 5 3 2 10 9 9 10 6 2 12 8 6 2 7 3 12 7 4 3 1 9 12 13 7 7 9 6
2 6 13 6 13 12 8 9 7 5 ...
8 100 100 -1
111111111111111111111111111111111111111111111111111111111111111 1111111
111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ...
9 123 345 32
2 15 11 3 10 5 1 14 10 10 8 13 10 12 2 3 2 15 3 1 10 9 8 7 1 5 1 11 5 5 3 1 9 6 3 6 9 7 3 5 12 4 3 9 1 15 1 4 11 15 7 7 15 8 6 15 3
12 1 3 3 14 10 9 15 14 12 9 14 6 7 3 2 1 2 15 13 15 9 12 7 11 8 8 12 6 2 13 5 9 6 5 2 12 11 2 4 3 14 9 12 4 7 10 11 11 4 3 13 6 14 7
6 4 3 14 3 4 3 6 13 2 1 14 12 1 12 8 6 2 9 5 13 11 12 9 8 13 6 2 10 6 11 13 6 5 15 3 14 5 1 8 8 14 5 14 2 8 13 1 4 3 3 9 6 9 ...
10 1000 1000 1332
111111111111111111111111111111111111111111111111111111111111111 1111111
1111111111111111111111111111111111111111111111111111111111
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ...
const maxn=trunc(1E5);
type mangkt=array[1..maxn] of boolean;
mang=array[1..maxn] of longint;
manga=array[1..maxn,1..100] of longint;
var n, count: longint;
kt: mangkt;
bac: mang;
a: manga;
procedure init;
var i, j, z: longint;
Trang 58
begin
for i:=2 to n div 2 do
begin
for j:=i to n div i do
begin
z:=i*j;
inc(bac[z]);
a[z, bac[z]]:= (i-1)*(j+1);
end;
end;
end;
procedure solve;
var i, j: longint;
begin
init;
fillchar(kt, sizeof(kt), false);
count:=0;
dfs(n);
writeln(count);
write('0 ');
for i:=1 to n-1 do
if (kt[i]) then write(i, ‘ ’);
end;
BEGIN
Write(‘Nhap so: ’, n ); readln(n);
Solve;
readln
end.
Test Input Output
1 12 6
0 3 4 6 7 10
2 16 11
0 3 4 5 6 7 8 9 10 12 15
3 99 68
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 42 43 44 45 46 48 49 50 5
1 52 54 55 56 57 58 60 63 64 65 66 68 70 72 75 78 84 85 91 96
4 1000 797
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 4
9 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69...
5 5000 4224
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 4
9 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 6...
6 9999 8694
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 4
9 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 6...
7 44444 39326
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 4
9 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 ...
8 100000 89463
0 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 6 ...
Trang 59
Một cơ quan có n nhân viên được đánh số thứ tự từ 1 đến n. Mỗi người có một phòng làm việc riêng
của mình. Do nhu cầu công việc, hàng ngày mỗi nhân viên có thể phải tiếp xúc với một số nhân viên khác.
Vào một ngày làm việc bình thường, có một nhân viên bị nhiễm bệnh Ebola, nhưng do không biết nên người
này vẫn đi làm. Đến cuối ngày làm việc người ta mới phát hiện ra người nhiễm bệnh Ebola đầu tiên. Khả
năng lây lan của Ebola rất nhanh chóng: một người nhiễm bệnh nếu tiếp xúc với một người khác có thể sẽ
truyền bệnh cho người này.
Yêu cầu: Hãy giúp các bác sĩ kiểm tra xem cuối ngày hôm đó, có tối đa bao nhiêu người có thể sẽ nhiễm bệnh
và đó là những người nào để còn cách ly? Người có tiếp xúc với người nhiễm bệnh được coi là người nhiễm
bệnh.
Input:
Dòng đầu tiên ghi 2 số tự nhiên n, k (1<=n<=10^5, 1<=k<=n) tương ứng là số lượng người làm việc
trong toà nhà và số hiệu của nhân viên đã nhiễm Ebola đầu tiên.
Dòng thứ i trong n dòng tiếp theo ghi danh sách những người có tiếp xúc với người thứ i theo cách
sau: số đầu tiên m của dòng là tổng số nhân viên đã gặp người thứ i, tiếp theo là m số tự nhiên lần lượt là số
hiệu của các nhân viên đó. Nếu m=0 có nghĩa rằng không ai đã tiếp xúc với người thứ i.
Dữ liệu được cho đảm bảo tổng số lần tiếp xúc của tất cả nhân viên trong cơ quan không vượt quá
10^6.
Output:
Dòng đầu tiên ghi số s là tổng số người có thể bị lây nhiễm Ebola.
Dòng thứ 2 liệt kê tất cả nhân viên có thể bị lây nhiễm Ebola cần cách ly, danh sách cần được sắp
theo thứ tự tăng dần của số hiệu nhân viên.
Ví dụ
Input Output
51 3
223 123
213
212
15
14
Procedure bfs;
Var i, w: Longint;
begin
w:=1; e:=1;
Trang 60
q[1]:=k;
fillchar(trace, sizeof(trace), False);
trace[k]:=True;
repeat
u:=q[w]; inc(w);
For i:= vt[u-1] to vt[u] - 1 do
If (trace[a[i]]=False) then
begin
inc(e);
trace[a[i]]:=True;
q[e]:=a[i];
end;
until w > e;
end;
BEGIN
readln(n,k);
vt[0]:=1;h:=0;
for i:= 1 to n do
begin
read(m);
for j:= 1 to m do
begin
read(u);
inc(h);
a[h]:=u;
end;
vt[i]:=vt[i-1] + m;
end;
bfs;
writeln(e);
qs(1, e, q);
for i:= 1 to e do
write(q[i], #32);
END.
Test Input Output
1 51 3
223 123
213
212
15
14
2 1000 1 991
990 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 1 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 6 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 4
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 2 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 5
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 8 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 7
123 124 125 126 127 128 129 ... 4 7...
3 100 1 100
99 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 0 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 3
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 6 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5
97 98 99 100 2 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 6
11 8 ...
11
11
11
11
11
11 ...
4 100000 1 90001
90000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 1 10001 10002 10003 10004 10005 10006 10007
10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10008 10009 10010 10011 10012 10013 10014 1
10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 0015 10016 10017 10018 10019 10020 10021 10
10042 10043 10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 022 10023 10024 10025 10026 10027 10028 100
10056 10057 10058 10059 10060 10061 10062 10063 1006 ... 29 10030 10031 10032...
5 100000 1 1
0 1
0
0
0
0
Trang 61
0
0
0
0
0 ...
6 100000 1 1000
999 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 0 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 3
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 6 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 2 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 6
119 120 121 122 123 12 ... 8...
7 100000 6 10
9 3 4 5 6 7 8 9 10 11 1 3 4 5 6 7 8 9 10 11
0
11
11
11
11
11
11
11
11
11
12
12
12
12
12
12
12
1 ...
8 100000 2 100000
12 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2
99999 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 0 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 3
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 6 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 2 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 ..
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 .
119 120 121 122 ...
Dòng thứ hai chứa các số a1, a2, …, an (|ai| <= 10 , i = 1, 2,…, n).
9
Dòng thứ ba chứa các số b1, b2, …, bn (|bi| <= 10 , i = 1, 2,…, n).
9
Các số trên cùng một dòng được ghi cách nhau ít nhất một dấu cách.
Output: Ghi ra một số nguyên duy nhất là số k lớn nhất tìm được.
Ví dụ
Input Output
5 3
-6 3 -3 5 -4
-7 2 -1 -8 1
Progam BW_Noidie
Const nmax = trunc(1e5 + 10);
Type point = record
x : integer;
s : integer;
end;
var n : integer;
a : array[1..2*nmax] of point;
res : integer;
procedure doc;
Trang 62
var i : integer;
begin
readln(n);
for i:=1 to n do
begin
read(a[i].x);
a[i].s:= 1;
end;
for i:=n + 1 to 2 * n do
begin
read(a[i].x);
a[i].s:= 2;
end;
end;
procedure main;
var i : integer;
begin
sort(1,2 * n);
res:= 0;
i:= 1;
while i < 2 * n do
begin
if (a[i].s <> a[i + 1].s) then
begin
inc(res);
inc(i, 2);
end
else inc(i);
end;
write(res);
readln;
end;
Trang 63
BEGIN
doc;
main;
END.
Trang 64
555553 2
12
15
22
42
43
Program Quan_Tuong;
Const dirx : array[1..4] of shortint = (-1, -1, 1, 1);
diry : array[1..4] of shortint = (-1, 1, 1, -1);
maxC = 10000000;
var a: array[0..1000, 0..1000] of boolean;
n, m, p, q, s, t, front, rear: longint;
qx, qy: array[1..1000000] of longint;
c: array[0..1000, 0..1000] of longint;
procedure readf;
var u, v, i: longint;
begin
readln(n, m, p, q, s, t);
fillchar(a, sizeof(a), true);
for i := 1 to m do
begin
readln(u, v);
a[u, v] := false;
end;
end;
procedure BFS;
var
u, v, x, y, i: longint;
begin
front := 1;
rear := 0;
push(p, q);
for u := 1 to n do
for v := 1to n do c[u, v] := maxC;
c[p, q] := 0;
Trang 65
a[p, q] := false;
repeat
pop(u, v);
for i := 1 to 4 do
begin
x := u;
y := v;
while range(x + dirx[i], y + diry[i]) do
begin
x := x + dirx[i];
y := y + diry[i];
a[x, y] := false;
c[x, y] := min(c[x, y], c[u, v] + 1);
push(x, y);
end;
end;
until front > rear;
if c[s, t] <> maxC then write(c[s, t])
else write(-1);
readln;
end;
begin
readf;
BFS;
end.
Test 1 2 3 4 5 6 7
Trang 66
104 19
104 103
107 27
116 55
116 66
117 51
120 90
Output 2 2 6 3 4 5 3 2
Program Line;
var qx, qy, gx, gy: array[1..100000] of longint;
a: array[1..1000,1..1000] of longint;
vx: array[1..4] of integer = (-1,1,0,0);
vy: array[1..4] of integer = (0,0,-1,1);
n, i, j, k, x1, y1, x, y, dx, dy, d, c, s: longint;
gap: boolean;
Trang 67
procedure BFS;
var i: longint;
t: array[1..10000] of longint;
begin
them(x, y);
gap:=false;
while(d<=c) and not (gap) do
begin
lay(x, y);
for i:=1 to 4 do
begin
x1:=x + vx[i]; y1:=vy[i] + y;
if kttd(x1, y1) then
if(a[x1, y1] <> 1)then
begin
them(x1, y1);
a[x1, y1]:=1;
t[c]:= d - 1;
if (y1=dy) and (x1=dx) then
begin gap:=true; break; end;
end;
end;
end;
if gap then
begin
s:=1; gx[s]:=x1; gy[s]:=y1;
while c > 1 do
begin
c:=t[c];
inc(s);
gx[s]:=qx[c]; gy[s]:=qy[c];
end;
end;
end;
BEGIN
readln(n, x, y, dx, dy);
d:=1; c:=0;
for i:=1 to n do
for j:=1 to n do read(a[i,j]);
bfs;
if gap then
begin
writeln(s);
for i:=s downto 1 do writeln(gx[i], ' ', gy[i]);
end
else writeln('0');
readln
END.
Test 1 2 3 4 5 6 7 8 9
21122 21122 31133 31231 43212 51155 51133 97465 20 1 20 19 19
10 11 100 110 1000 10000 10000 100100110 00000000000000000001
10 10 001 110 1110 00000 11110 000000110 00000000000000000000
010 000 0110 00000 00010 001010000 00000000000000000000
0000 00111 01110 001000000 00000000000000000000
Input
01000 00000 110010101 00000000000000000000
011001000 00000000000000000000
000100010 00000000000000000000
010011000 00000000000000000000
100001010 0 0 0 ...
3 0 0 6 9 0 17 3 20
11 12 32 11 74 1 20
12 13 42 12 64 2 20
Output
22 23 43 13 65 3 20
33 44 14 4 20
32 34 15 5 20
Trang 68
31 24 25 6 20
14 35 7 20
13 45 8 20
12 55 9 20
54 10 20
53 11 20
52 12 20
51 13 20
41 14 20
31 15 20
32 16 20
33 17 20
18 20
19 20
19 19
Trang 69