Download as pdf or txt
Download as pdf or txt
You are on page 1of 67

Chương I:GIỚI THIỆU ĐỒ 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

TrườngTHPT chuyên LQĐ Tổ bộ môn


Đoàn thanh niên

TổTin

GV 3
GV 1
GV 2

Trang 3
Vídụ 3: Cấu trúc phân tử

Hình 1: Ví dụ về mô hình đồ thị


2. Phân loại đồ thị:
Có thể phân loại đồ thị theo đặc tính và số lượng của tập các cạnh E:
Đơn đồ thị Đa đồ thị
Có hướng Vô hướng Có hướng Vô hướng
Cho đồ thị G = (V, E). Ta định nghĩa một cách hình thức như sau:
- G được gọi là đơn đồ thị nếu giữa hai đỉnh u, v của V có nhiều nhất là 1 cạnh trong E nối
từ u tới v.
- G được gọi là đa đồ thị nếu giữa hai đỉnh u, v của V có thể có nhiều hơn 1 cạnh trong E
nối từ u tới v (Đơn đồ thị cũng được xem là một đa đồ thị đặc biệt).
- G được gọi là đồ thị vô hướng nếu các cạnh trong E là không có định hướng, tức là cạnh
nối hai đỉnh u, v bất kỳ cũng là cạnh nối hai đỉnh v, u. Hay nói cách khác, tập E gồm các cặp (u, v)
không tính thứ tự (u, v)≡(v, u).
- G được gọi là đồ thị có hướng nếu các cạnh trong E là có định hướng, tức là có thể có
cạnh nối từ đỉnh u tới đỉnh v nhưng chưa chắc đã có cạnh nối từ đỉnh v tới đỉnh u. Hay nói cách
khác, tập E gồm các cặp (u, v) có tính thứ tự: (u, v) ≠ (v, u). Trong đồ thị có hướng, các cạnh được
gọi là các cung. Đồ thị vô hướng cũng có thể coi là đồ thị có hướng nếu như ta coi cạnh nối hai đỉnh
u, v bất kỳ tương đương với hai cung (u, v) và (v, u). Ví dụ:

Vô hướng Có hướng Vô hướng Có hướng

Đơn đồthị Đa đồthị

Hình 2. Phân loại đồ thị

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

1. Một số khái niệm:


- Đối với đồ thị vô hướng G = (V, E). Xét một cạnh e∈E, nếu e = (u, v) thì ta nói hai đỉnh u
và v là kề nhau (adjacent) và cạnh e này liên thuộc (incident) với đỉnh u và đỉnh v. Ví dụ: đỉnh kề
của đỉnh V là: U, X, W; a, b, d là cạnh liên thuộc của đỉnh V.
- Với một đỉnh v trong đồ thị, ta định nghĩa bậc (degree) của v, ký hiệu deg(v) là số cạnh liên
thuộc với v. Dễ thấy rằng trên đơn đồ thị thì số cạnh liên thuộc với v cũng là số đỉnh kề với v. Ví dụ:
bậc của đỉnh X là: 5.
- Cạnh lặp: giữa 2 đỉnh u, v tồn tại ít nhất 2 cạnh thì ta gọi các cạnh đó là cạnh lặp. Ví dụ: h, i
là cạnh lặp.
- Khuyên: Xét một cạnh e∈E, nếu e = (u, u) thì ta nói e là khuyên. Ví dụ: j là khuyên.
 Nhận xét: Đơn đồ thị không chứa cạnh lặp và khuyên.
- Đường đi: là dãy các đỉnh (hoặc các cạnh), trong đó 2 đỉnh liên tiếp có cạnh nối với nhau.
Ví dụ: đường đi P = u(1), u(2), u(3), u(4), …, u(k-1), u(k); trong đó u(i-1) và u(i) là 2 đỉnh kề tức là
(u(i-1), u(i)) E; i = 2, 3, …, k.
+ Độ dài đường đi là số cạnh của đường đi (k)/ (giá trị).
+ u(1) là đỉnh đầu, u(k) là đỉnh cuối của đường đi.
- Đường đi đơn: các đỉnh trên đường đi phân biệt nhau. Ví dụ: P1 là đường đi đơn; P2 là
đường đi không đơn (Hình 3a).
- Chu trình: đường đi gồm các cạnh phân biệt có đỉnh đầu trùng đỉnh cuối.
+ Chu trình đơn: ngoại trừ đỉnh đầu trùng đỉnh cuối, không còn 2 cặp đỉnh nào trùng nhau. Ví
dụ: C1 là chu trình đơn; C2 là chu trình không đơn (Hình 3b).

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)

a) Đường đi đơn và không đơn b) Chu trình đơn và không đơn


Y
Hình 3. Ví dụ về đường đi và chu trình
- Đồ thị vô hướng G được gọi là liên thông (connected): nếu luôn tồn tại đường đi giữa mọi
cặp đỉnh phân biệt của đồ thị. Nếu G không liên thông thì chắc chắn nó sẽ là hợp của hai hay nhiều
đồ thị con liên thông, các đồ thị con này đôi một không có đỉnh chung. Các đồ thị con liên thông rời
nhau như vậy được gọi là các thành phần liên thông của đồ thị đang xét.
G1

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.

Hình 5: Liên thông mạnh và liên thông yếu


- Bao đóng đồ thị: Với đồ thị G = (V, E), người ta xây dựng đồ thị G' = (V, E') cũng gồm
những đỉnh của G còn các cạnh được xây dựng như sau: (ở đây quy ước giữa u và u luôn có đường
đi) giữa đỉnh u và v của G' có cạnh nối ⇔ giữa đỉnh u và v của G có đường đi. Đồ thị G' xây dựng
như vậy được gọi là bao đóng của đồ thị G.

2. Một số tính chất:


Định lý1: Giả sử G = (V, E) là đồ thị vô hướng với m cạnh, khi đó tổng tất cả các bậc đỉnh
trong V sẽ bằng 2m: deg(v)  2m
vV

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
vV vV

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:

I. MA TRẬN KỀ (Adjacency Matrix):


1. Cách biểu diễn:
Giả sử G = (V, E) là một đơn đồ thị có số đỉnh (ký hiệu V) là n, được đánh số 1, 2, …, n.
Khi đó, ta có thể biểu diễn đồ thị bằng một ma trận vuông A = [aij] cấp n.
Trongđó: aij = 1 nếu (i, j)  E
aij= 0 nếu (i, j)  E
Quy ước aii = 0 với i;
(Đối với đa đồ thị thì việc biểu diễn cũng tương tự, chỉ có điều nếu như (i, j) là cạnh thì không
phải ta ghi số 1 vào vị trí aij mà là ghi số cạnh nối giữa đỉnh i và đỉnh j).
Vídụ:
1 2 3 4 5
1
1 0 0 1 1 0
2 0 0 0 1 1 5 2
3 1 0 0 0 1
4 1 1 0 0 0
5 0 1 1 0 0 4 3

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

2. Tính chất của ma trận kề:


- Đối với đồ thị vô hướng G, thì ma trận kề tương ứng là ma trận đối xứng (aij = aji) (điều này
không đúng với đồ thị có hướng).
- Nếu G là đồ thị vô 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 = Tổng các số trên cột i = Bậc của đỉnh i = deg(i).

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.

II. DANH SÁCH CẠNH (Edge List)


Trong trường hợp đồ thị có n đỉnh, m cạnh, ta có thể biểu diễn đồ thị dưới dạng danh sách
cạnh bằng cách liệt kê tất cả các cạnh của đồ thị trong một danh sách, mỗi phần tử của danh sách là
một cặp (u, v) tương ứng với một cạnh của đồ thị. (Trong trường hợp đồ thị có hướng thì mỗi cặp (u,
v) tương ứng với một cung, u là đỉnh đầu và v là đỉnh cuối của cung). Danh sách được lưu trong bộ
nhớ dưới dạng mảng hoặc danh sách móc nối (liên kết đơn).
Cài đặt bằng mảng:
1 2 3 4 5
1

(1, 4) (2, 4) (2, 5) (3, 5) 5 2


(1,

Cài đặt trên danh sách móc nối:


4 3
(1, 3) (1, 4) (2, 4) (2, 5) (3, 5)

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).

III. DANH SÁCH KỀ (Adjacency List)


Để khắc phục nhược điểm của các phương pháp ma trận kề và danh sách cạnh, người ta đề xuất
phương pháp biểu diễn đồ thị bằng danh sách kề. Trong cách biểu diễn này, với mỗi đỉnh v của đồ thị,
ta cho tương ứng với nó một danh sách các đỉnh kề với v.
Với đồ thị G = (V, E). V gồm n đỉnh và E gồm m cạnh. Có hai cách cài đặt danh sách kề phổ
biến:
Cách 1 (Forward Star): Dùng một mảng các đỉnh, mảng đó chia
làm n đoạn, đoạn thứ i trong mảng lưu danh sách các đỉnh kề với đỉnh i:
Với đồ thị ở hình trên, danh sách kề sẽ là một mảng A gồm 12 phần tử:
1 2 3 4 5 6 7 8 9 10 11 12

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:

Xét đơn đồ thị vôhướng có Danh sách Danh sách kề Ma trận kề


* n đỉnh, mcạnh cạnh
Bộ nhớ n+m n+m
n2
Cạnh liên thuộc từ đỉnh v m deg(v) n
incidentEdges(v)
Kiểm tra v, w có kề nhau m min(deg(v), deg(w)) 1
areAdjacent (v, w)
Chèn thêm đỉnh 1 1 n2
insertVertex(o)
Xó bớt đỉnh m deg(v) n2
removeVertex(v)
Xóa bớt cạnh 1 1 1
removeEdge(e)

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

a) Đồ thị vô hướng b) Đồ thị có hướng


Hình 6: Ví dụ đồ thị vô hướng và đồ thị có hướng
Trên cả hai đồ thị: xét tập (1, 2, 3, 4) là đường đi đơn độ dài 3 từ đỉnh 1 tới đỉnh 4 có (1, 2),
(2, 3) và (3, 4) đều là các cạnh (cung); (1, 6, 5, 4) không phải đường đi bởi (6, 5) không phải là cạnh
(cung).
Một bài toán quan trọng trong lý thuyết đồ thị là bài toán duyệt tất cả các đỉnh có thể đến
được từ một đỉnh xuất phát nào đó. Vấn đề này đưa về một bài toán liệt kê mà yêu cầu của nó là
không được bỏ sót hay lặp lại bất kỳ đỉnh nào. Chính vì vậy mà ta phải xây dựng những thuật toán
cho phép duyệt một cách hệ thống các đỉnh, những thuật toán như vậy gọi là những thuật toán tìm
kiếm trên đồ thị (duyệt đồ thị). Hai thuật toán cơ bản nhất là: thuật toán tìm kiếm theo chiều sâu và
thuật toán tìm kiếm theo chiều rộng cùng với các ứng dụng của chúng.

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

Hình 8. Đồ thị vô hướng.


- Giả sử cho đồ thị như hình 8. Dữ liệu về đồ thị sẽ được nhập từ file văn bản GRAPH.INP.
+ Dòng 1 chứa số đỉnh n (≤ 100), số cạnh m của đồ thị, đỉnh xuất phát S, đỉnh kết thúc F.
+ m dòng tiếp theo: mỗi dòng có dạng hai số nguyên dương u, v thể hiện có cạnh nối từ
đỉnh u đến đỉnh v trong đồ thị.
- Kết quả ghi ra file văn bản GRAPH.OUT.
+ Dòng 1: Ghi danh sách các đỉnh có thể đến được từ S.
+ Dòng 2: Đường đi từ S tới F được in ngược theo chiều từ F về S.
1. Cài đặt đệ quy:
Tư tưởng: mọi đỉnh x kề với S tất nhiên sẽ đến được từ S. Với mỗi đỉnh x kề với S, những
đỉnh y kề với x cũng đến được từ S... Ta sẽ viết một thủ tục đệ quy DFS(u) mô tả việc duyệt từ đỉnh
u bằng cách thông báo thăm đỉnh u và tiếp tục quá trình duyệt DFS(v) với v là một đỉnh chưa thăm
kề với u.
- Để không một đỉnh nào bị liệt kê tới hai lần, ta sử dụng kỹ thuật đánh dấu, mỗi lần thăm
một đỉnh, ta đánh dấu đỉnh đó lại để các bước duyệt đệ quy kế tiếp không duyệt lại đỉnh đó nữa.
- Để lưu lại đường đi từ đỉnh xuất phát S, trong thủ tục DFS(u), trước khi gọi đệ quy DFS(v),
với v là một đỉnh kề u mà chưa đánh dấu, ta lưu lại vết đường đi từ u tới v bằng cách đặt TRACE[v]
:= u, tức là TRACE[v] lưu lại đỉnh liền trước v trong đường đi từ S tới v. Khi quá trình tìm kiếm
theo chiều sâu kết thúc, đường đi từ S tới F sẽ là: F ← p1 = Trace[F] ← p2 = Trace[p1] ←... ← S.
procedure DFS(u∈V);
begin
< 1. Thông báo tới được u >;
< 2. Đánh dấu u là đã thăm (có thể tới được từ S)>;
< 3. Xét mọi đỉnh v kề với u mà chưa thăm, với mỗi đỉnh v đó >;
begin
Trace[v] := u; {Lưu vết đường đi, đỉnh mà từ đó tới v là u}
DFS(v); {Gọi đệ quy duyệt tương tự đối với v}
end;

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;

procedure Result; {In đường đi từ S tới F}


begin
Writeln; {Vào dòng thứ hai của Output file}
if Free[F] then {Nếu F chưa đánh dấu thăm tức là không có đường}
WriteLn('Path from ', S, ' to ', F, ' not found')
else {Truy vết đường đi, bắt đầu từ F}
begin
while F <> S do
beginWrite(F, '  ');
F := Trace[F];
end;
Writeln(S);
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 DFS(u: Integer);


var v: Integer;
begin
Write(u, ', ');
for v := 1 to n do
if (Trace[v] = 0) and (a[u, v]) then
begin
Trace[v] := u; {Lưu vết đường đi cũng là đánh dấu}
DFS(v);
end;
end;

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

Hình 9. Cây DFS


* Nhận xét:
- Đỉnh 2 và 3 đều kề với đỉnh 1, nhưng DFS(1) chỉ gọi đệ quy tới DFS(2) mà không gọi
DFS(3). Là vì DFS(1) sẽ tìm thấy đỉnh 2 trước và gọi DFS(2). Trong DFS(2) sẽ xét tất cả các đỉnh
kề với 2 mà chưa đánh dấu thì trước hết nó tìm thấy đỉnh 3 và gọi DFS(3), khi đó 3 đã bị đánh dấu
nên khi kết thúc quá trình đệ quy gọi DFS(2), lùi về DFS(1) thì đỉnh 3 đã được thăm (đã bị đánh
dấu) nên DFS(1) sẽ không gọi lại DFS(3) nữa. Do đó: DFS(5) do DFS(3) gọi nên Trace[5] = 3;
DFS(3) do DFS(2) gọi nên Trace[3] = 2; DFS(2) do DFS(1) gọi nên Trace[2] = 1. Vậy đường đi là: 5
← 3 ← 2 ←1.
- Với cây thể hiện quá trình đệ quy DFS ở trên, ta thấy nếu dây chuyền đệ quy là: DFS(S) →
DFS (u1) → DFS(u2) ... Thì thủ tục DFS nào gọi cuối dây chuyền sẽ được thoát ra đầu tiên, thủ tục
DFS(S) gọi đầu dây chuyền sẽ được thoát cuối cùng. Vậy, ta có thể mô tả dây chuyền đệ quy bằng
một ngăn xếp (Stack).
2. Cài đặt không đệ quy:
Khi mô tả quá trình đệ quy bằng một ngăn xếp, ta luôn luôn để cho ngăn xếp lưu lại dây
chuyền duyệt sâu từ nút gốc (đỉnh xuất phát S).
<Thăm S, đánh dấu S đã thăm>;
<Đẩy S vào ngăn xếp>; {Dây chuyền đệ quy ban đầu chỉ có một đỉnh S}
repeat
<Lấy u khỏi ngăn xếp>; {Đang đứng ở đỉnh u}
if<u có đỉnh kề chưa thăm> then
begin<Chỉ chọn lấy 1 đỉnh v, là đỉnh đầu tiên kề u mà chưa được thăm>;
<Thông báo thăm v>;
<Đẩy u trở lại ngăn xếp>; {Giữ lại địa chỉ quay lui}
<Đẩy tiếp v vào ngăn xếp>; {Dây chuyền duyệt sâu được "nối" thêm v nữa}
end;
{Còn nếu u không có đỉnh kề chưa thăm thì ngăn xếp sẽ ngắn lại, tương ứng với quá trình lùi
về của dây chuyền DFS}
until<Ngăn xếp rỗng>;
{ Thuật toán tìm kiếm theo chiều sâu không cài đặt đệ quy }
program DFS_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;
Stack: array[1..max] of Integer;
n, S, F, Last: Integer;
procedure Enter; {Nhập dữ liệu}
var i, u, v, m: Integer;

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;

{Thuật toán duyệt theo chiều sâu}


begin
Trace[S] := n + 1;
u := S;
repeat
<Thông báo thăm u, đánh dấu u đã thăm>;
u := FindNext(u);
until u = 0;
end;
Ta có chương trình cải tiến (phần khác nhau) như sau:
program DFS_2_cai_tien;
function FindNext(u:integer): integer;
var v:integer;
begin
repeat
for v := 1 to n do
if Free[v] and a[u, v] then
begin
Trace[v] := u;
FindNext := v;
Exit;
end;
u := Trace[u];
until u = n + 1;
FindNext := 0;
end;
procedure DFS;
var u, v: Integer;
begin
Trace[S] := n + 1;
u := S;
repeat
write(u,', ');
free[u]:=false;
u := FindNext(u);
until u = 0;
end;
(Trong trường hợp đồ thị có hướng: chúng ta sẽ thay các cạnh vô hướng bằng các cung của
đồ thị có hướng).

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 531
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

Hình 12: Thuật toán loang


Giải thuật loang có thể được xây dựng như sau:
- Bước 1: Khởi tạo các đỉnh khác S đều chưa bị đánh dấu, khi đỉnh S bị đánh dấu, tập "cũ"
Old := {S}.
- Bước 2: Lặp các bước sau đến khi Old = ∅.
+ Đặt tập "mới" New = ∅, sau đó dùng tập "cũ" tính tập "mới" như sau:
 Xét các đỉnh u ∈ Old, với mỗi đỉnh u đó:
 Thông báo thăm u;

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.

- Độ phức tạp tính toán: O(n+m) = O(max(n,m)).


- Nhận xét: Khi biểu diễn đồ thị bằng danh sách kề, thuật toán có độ phức tạp tính toán tốt hơn nhiều so
với ma trận trận kề (danh sách cạnh). Tuy nhiên chương trình sẽ dài hơn.
Bài 2. Giả sử cho đồ thị được biểu diễn bằng ma trận kề (danh sách cạnh). Yêu cầu cài đặt thuật toán BFS,
DFS sao cho độ phức tạp tính toán là tốt nhất?
Hướng dẫn: Độ phức tạp tốt nhất là khi biểu diễn đồ thị bằng danh sách kề. Do đó, cần đoạn chương trình
chuyển ma trận kề thành danh sách kề. Ta có thể cài đặt như sau:
test.inp test1.inp test.out
33 3 2
12 010 31
21 101
23 000
Ta có thể cài đặt như sau:
program dsc_dslt; //chuyen danh sach canh --> danh sach Program mtrk_dslt;//chuyen ma tran ke --> danh sach lien
lien thuoc thuoc
Const fi='test.inp'; const fi='test1.inp';
fo='test.out'; fo='test1.out';
type Tadj=record type Tadj=record
link, v: longint; link, v: longint;
end; end;
Tedge=record
x, y: longint;
end;
var e: array[1..10000] of Tedge; var a: array[1..1000, 1..1000] of longint;
head: array[1..1000] of longint; head:array[1..1000] of longint;
a: array[1..10000] of Tadj; adj:array[1..10000] of Tadj;
n,m: longint; n,m: longint;
Procedure Input; procedure Input;
var f: text; i: longint; var f: text; i, j: longint;
begin begin
assign(f, fi); reset(f); assign(f, fi); reset(f);
readln(f, n, m); //nhap danh sach canh hoac cung readln(f, n); //nhap ma tran
for i:=1 to m do for i:=1 to n do
with e[i] do readln(f, x, y); for j:=1 to n do read(f, a[i,j]);
close(f); close(f);
end; end;
//====================================== //==================================
procedure process; procedure process;

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;

procedure Init; {Khởi tạo}


begin
FillChar(Numbering, SizeOf(Numbering), 0); {Mọi đỉnh đều chưa thăm}
FillChar(Free, SizeOf(Free), True); {Chưa đỉnh nào bị loại}
Last := 0; {Ngăn xếp rỗng}
Count := 0; {Biến đánh số thứ tự thăm}
ComponentCount := 0; {Biến đánh số các thành phần liên thông}
end;

procedure Push(v: Integer); {Đẩy đỉnh v vào ngăn xếp}


begin
Inc(Last); Stack[Last] := v;
end;

function Pop: Integer; {Lấy đỉnh ra khỏi ngăn xếp}


begin
Pop := Stack[Last]; Dec(Last);
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

Hướng dẫn: Ta có thể cài đặt như sau :


program Euler_Circuit;
const InputFile = 'EULER.INP';
OutputFile = 'EULER.OUT';
max = 100;

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 Push(v: Integer);


begin
Inc(last); Stack[last] := v;
end;

function Pop: Integer;


begin
Pop := Stack[last]; Dec(last);
end;

function Get: Integer; {Trả về phần tử ở đỉnh (Top) ngăn xếp}


begin
Get := Stack[last];
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à uvò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 123164253
011101 4 1
101110
110110
111001
011000
100100

Program dua_thu;
Const input='duathu.inp';
output='duathu.out';

var a: array[1..100, 1..100] of integer;


n:integer;

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;

function goback(x, y: integer): boolean;


var queue: array[1..100] of integer;
front, rear: integer;
u, v: integer;
free: array[1..100] of boolean;
begin
a[x,y]:=0; a[y,x]:=0;
fillchar(free, n, true);
free[y]:=false;
queue[1]:=y;
front:=1; rear:=1;
repeat
u:=queue[front]; inc(front);
for v:=1 to n do
if (free[v]) and (a[u,v]=1) then
begin
inc(rear);
queue[rear]:=v;
free[v]:=false;
if free[x] then break;
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 Install(u,v: Byte);


var P: PNode;
begin
New(P); P^.info:=v; P^.Link:=A[u]; A[u]:=P;
end;

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;

function Get: Byte;

Trang 34
begin
Exit(Head^.info);
end;

procedure Push(u: Byte);


var P: PNode;
begin
New(P); P^.info:=u; P^.Link:=Head; Head:=P;
end;

function Pop :Byte;


var P: PNode;
begin
Pop:=Head^.info; P:=Head^.Link; Dispose(Head); Head:=P;
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;

procedure Dijkstra; {Thuật toán Dijkstra}


var i, u, v: Integer;
min: Integer;
begin
repeat {Tìm trong các đỉnh có nhãn tự do ra đỉnh u có d[u] nhỏ nhất}
u := 0; min := maxC;

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;

procedure PrintResult; {In đường đi từ S tới F}


var fo: text;
begin
Assign(fo, 'MINPATH.OUT'); Rewrite(fo);
if d[F] = maxC then
WriteLn(fo, 'Path from ', S, ' to ', F, ' not found')
else
begin
WriteLn(fo, 'Khoang cach ngan nhat from ', S, ' to ', F, ': ', d[F]);
WriteLn(fo, ‘Duong di la: ’);
while F <> S do
begin
Write(fo, F, '<-'); F := Trace[F];
end;
Write(fo, S);
end;
close(fo);
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.

{Thuật toán Dijkstra và cấu trúc Heap}


program Dijkstra_Heap;
const max = 5000;
maxE = 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 LoadGraph; {Nhập dữ liệu}


var i, m: Integer;
u, v, c: Integer;
inp: Text;
begin
{Đọc file lần 1, để xác định các đoạn}
Assign(inp, 'MINPATH.INP'); Reset(inp);
ReadLn(inp, n, m, S, F);
New(head);
New(adj); New(adjCost);
{Phép đếm phân phối (Distribution Counting)}
FillChar(head^, SizeOf(head^), 0);
for i := 1 to m do
begin
ReadLn(inp, u);
Inc(head^[u]);
end;
for i := 2 to n do head^[i] := head^[i - 1] + head^[i];
Close(inp);
{Đến đây, ta xác định được head[u] là vị trí cuối của danh sách kề đỉnh u trong adj^}
Reset(inp); {Đọc file lần 2, vào cấu trúc Forward Start}
ReadLn(inp); {Bỏ qua dòng đầu tiên Input file}
for i := 1 to m do
begin
ReadLn(inp, u, v, c);
adj^[head^[u]] := v; {Điền v và c vào vị trí đúng trong danh sách kề của u}
adjCost^[head^[u]] := c;
Dec(head^[u]);
end;
head^[n + 1] := m;
Close(inp);
end;

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;

function Pop: Integer;


var r, c, v: Integer;
begin
Pop := heap[1]; {Nút gốc Heap chứa đỉnh có nhãn tự do nhỏ nhất}
v := heap[nHeap]; {v là đỉnh ở nút lá cuồi Heap, sẽ được đảo lên đầu và vun đống}
Dec(nHeap);
r := 1; {Bắt đầu từ nút gốc}
while r * 2 <= nHeap do {Chừng nào r chưa phải là lá}
begin
{Chọn c là nút chứa đỉnh ưu tiên hơn trong hai nút con}
c := r * 2;
if (c < nHeap) and (d[heap[c + 1]] < d[heap[c]]) then Inc(c);
{Nếu v ưu tiên hơn cả đỉnh chứa trong C, thì thoát ngay}
if d[v] <= d[heap[c]] then Break;
heap[r] := heap[c]; {Chuyển đỉnh lưu ở nút con c lên nút cha r}
Pos[heap[r]] := r; {Ghi nhận lại vị trí mới trong Heap của đỉnh đó}
r := c; {Gán nút cha := nút con và lặp lại}
end;
heap[r] := v; {Đỉnh v sẽ được đặt vào nút r để bảo toàn cấu trúc Heap}
Pos[v] := r;
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;

function choice: integer;


var min, i, j: integer;
begin
min:=high(integer);
for i:=1 to n do
if (d[i]<min) and (not(m[i])) then
begin
min:=d[i];
j:=i;
end;
exit(j);
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.

Bài 10. Mạch in


Mạch in là một bảng gồm các nút của một lưới hình chữ nhật và các đoạn dây dẫn nối các nút kề nhau (theo
chiều đứng hoặc ngang). Mạch được gọi là liên thông nếu hai nút bất kỳ được nối với nhau bởi dãy các đoạn
dây dẫn. Cho một mạch in trong đó đã có một số đoạn dây dẫn nối một loạt các cặp nút kề nhau. Cần tìm cách

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

Input: Vào từ file văn bản CIRCUIT.INP:


- Dòng đầu tiên chứa hai số nguyên N và M, với N (1N100) là số dòng và M (1M100) là số cột
của lưới. Mỗi nút trên của lưới được xác định bởi hai toạ độ: Nút ở góc trái trên có toạ độ (1,1), nút ở góc phải
dưới có toạ độ (N, M).
- Mỗi một trong số N dòng tiếp theo chứa M số nguyên. Số nguyên ở tên dòng i và cột j của dòng này
mô tả đoạn dây nối giữa cặp nối (i,j) và (i+1,j) cũng như giữa cặp nút (i,j) và (i,j+1) theo qui tắc sau:
+ 0 có nghĩa là không có đoạn dây nối giữa (i,j) và (i+1,j) cũng như giữa cặp nút (i,j) và (i,j+1);
+ 1 có nghĩa là có đoạn dây nối giữa cặp nút (i,j) và (i+1,j);
+ 2 có nghĩa là có đoạn dây nối giữa cặp nút (i,j) và (i,j+1);
+ 3 có nghĩa là cả cặp nút (i,j) và (i+1,j) và (i,j) và (i,j+1) đều được nối với nhau.

Output: ghi ra file văn bản CIRCUIT.OUT:


- Dòng đầu tiên chứa hai số nguyên K và V, trong đó K là số đoạn nối cần bổ sung trong cách bổ sung
với chi phí nhỏ nhất tìm được còn V là chi phí tương ứng.
- Mỗi một trong số K dòng tiếp theo chứa ba số nguyên i, j và d, trong đó (i,j) là toạ độ của nút, còn d
chỉ nhận một trong hai giá trị 1 và 2. Với d=1 cho biết phải nối nút (i,j) với (i+1,j) và d=2 cho biết phải nối
nút (i,j) với (i,j+1).
Ví dụ: Xem minh hoạ trong hình 1 và 2.
CIRCUIT.INP CIRCUIT.OUT
4 5 5 6
2 1 1 2 1 1 1 1
0 3 0 1 0 3 2 1
3 0 0 3 1 3 3 1
0 2 0 2 0 3 3 2
2 5 1
Program Circuit;
Const MaxN=101;
Visited=16;
fin: string = 'CIRCUITN.INP';
fou: string = 'CIRCUITN.OUT';

Type Node = Record


x, y: Byte
End;
Table = Record
HEnd,VEnd:Word;
T:Array[1..MaxN*MaxN] of Node
End;
Var N, M, MN: Word;
G: Array[0..MaxN, 0..MaxN] Of Byte;
Disp: Table;
Cost, OC: Word;
OList: Array[1..MaxN*MaxN] of Record
x,y,c: Byte
End;

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;

Function NotEmptyVert: Boolean;


Begin
NotEmptyVert:=Disp.VEnd>0
End;

Function NotEmptyHoriz: Boolean;


Begin
NotEmptyHoriz:=Disp.HEnd<=MN
End;

Procedure TakeVert(Var P: Node);


Begin
P:=Disp.T[Disp.VEnd];
Dec(Disp.VEnd);
End;

Procedure TakeHoriz(Var P: Node);


Begin
P:=Disp.T[Disp.HEnd];
Inc(Disp.HEnd);
End;

Procedure PutVert(Const P: Node);


Begin
Inc(Disp.VEnd);
Disp.T[Disp.VEnd]:=P;
End;

Trang 43
Procedure PutHoriz(Const P: Node);
Begin
Dec(Disp.HEnd);
Disp.T[Disp.HEnd]:=P;
End;

Procedure DFS(P: Node);


Var Q1,Q2,Q3,Q4: Node; {must be global in DOS!}
Begin {DFS}
G[P.x, P.y]:=G[P.x, P.y] + Visited;
Q1.x:=P.x; Q1.y:=P.y - 1;
If (G[Q1.x,Q1.y]<Visited) And (G[Q1.x,Q1.y]>1) Then
DFS(Q1);
Q2.x:=P.x + 1; Q2.y:=P.y;
If (G[Q2.x,Q2.y]<Visited) And Odd(G[P.x,P.y]-Visited) Then
DFS(Q2);
Q3.x:=P.x; Q3.y:=P.y + 1;
If (G[Q3.x,Q3.y]<Visited) And (G[P.x,P.y]-Visited>1) Then
DFS(Q3);
Q4.x:=P.x - 1; Q4.y:=P.y;
If (G[Q4.x,Q4.y]<Visited) And Odd(G[Q4.x,Q4.y]) Then
DFS(Q4);

If G[Q1.x, Q1.y] < Visited Then


PutHoriz(Q1);
If G[Q2.x, Q2.y] < Visited Then
Begin
Q2.x:=Q2.x + N;
PutVert(Q2);
End;
If G[Q3.x,Q3.y]<Visited Then
Begin
Q3.y:=Q3.y + M;
PutHoriz(Q3);
End;
If G[Q4.x, Q4.y] < Visited Then
PutVert(Q4);
End;

Procedure PutOut(P: Node; c: Byte);


Begin
Inc(Cost,c);
Inc(OC);
OList[OC].x:=P.x; OList[OC].y:=P.y; OList[OC].c:=c;
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.

Bài 11. Du lịch


Có N thành phố đánh số từ 1 đến N, N100. Giữa một số cặp thành phố có đường đi hai chiều nối trực tiếp.
Cần chọn một tour du lịch đi qua ít nhất 3 thành phố khác nhau, mỗi thành phố đúng một lần trừ thành phố
đầu tiên đi qua đúng hai lần (lần đầu tiên và lần cuối cùng) sao cho tổng chi phí của tour du lịch là ít nhất.
Input: Vào từ file DULICH.INP trong đó dòng đầu tiên ghi hai số nguyên N, M với M là số đoạn đường nối
trực tiếp hai thành phố. Tiếp theo là M dòng, mỗi dòng ghi ba số nguyên dương u, v, w với ý nghĩa là có
đường đi hai chiều trực tiếp từ thành phố u đến thành phố v với chi phí w, w10000. Chú ý rằng giữa hai
thành phố có thể có nhiều đường nối trực tiếp.
Output: ghi ra file văn bản DULICH.OUT như sau: dòng thứ nhất ghi 1/0 tuỳ theo có hay không có tour du
lịch theo yêu cầu trên. Nếu dòng thứ nhất ghi số 1, dòng thứ hai ghi số C là tổng chi phí của tour được chọn,
dòng thứ 3 ghi số k là số thành phố khác nhau trong tour, cuối cùng là k dòng, mỗi dòng ghi tên một thành
phố theo trình tự lần lượt đi trong tour.
DULICH.INP DULICH.OUT DULICH.INP DULICH.OUT
57 1 43 0
141 61 1 2 10
1 3 300 4 1 3 20
3 1 10 1 1 4 30
1 2 16 3
2 3 100 5
2 5 15 2
5 3 20

Const tfi = 'DULICH.INP';


tfo = 'DULICH.OUT';
maxN = 400;

var fi, fo: text;


N, M: LongInt;
Gr: array[1..maxN,1..maxN] of LongInt;

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 Put(u: LongInt);


begin
inc(qn); q[qn]:=u;
end;

function Get: LongInt;


var u, i: LongInt;
begin
u:=1;
for i:=2 to qn do
if kc[q[i]] < kc[q[u]] then u:=i;
Get:=q[u];
q[u]:=q[qn];
dec(qn);
end;

function Qempty: boolean;


begin
Qempty:=(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;

procedure Dijstra(xp, kt: LongInt);


var u, v: LongInt;
begin
InitQ;
Fillchar(Tr, sizeof(Tr), 0);
Put(xp); Tr[xp]:= -(N+1);
kc[xp]:=0;
repeat
u:=Get; Tr[u]:= -Tr[u];
for v:=xp + 1 to N do
if (Gr[u,v] <> -1) then

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 TimDuong(kt: LongInt);


var u: LongInt;
begin
slx:=0;
u:=kt;
repeat
inc(slx);
x[slx]:=u;
u:=Tr[u];
until u=N + 1;
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 (c10000).
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;

Type mangB = array[1..maxN] of byte;


mangL = array[1..maxN] of LongInt;

var fi, fo : text;


N, M : LongInt;
a : array[1..maxN] of ^mangL;

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;

Bài 13. SPIRAL - Bảng xoắn ốc


Tùng có một bảng gồm N dòng N cột. Các dòng được đánh số từ 1 đến N, từ
trên xuống dưới, các cột được đánh số từ 1 đến N từ trái sang phải. Tùng điền
bảng này với các con số từ 1 đến NxN theo hình xoắn ốc, bắt đầu từ ô (1, 1).
Hình xoắn ốc đi từ ngoài vào trong theo chiều kim đồng hồ. Hình bên phải là ví
dụ với bảng N = 5. Hiện tại, Tùng đang ở vị trí của ô số X và Tùng muốn
chuyển đến ô của số Y bằng cách thực hiện một số bước di chuyển. Trong mỗi
bước, Tùng chỉ có thể di chuyển từ ô số A đến ô số B nếu:
- A và B nguyên tố cùng nhau.
- Ô chứa số A và ô chứa số B có chung một cạnh.
Cho N, X, Y, Tùng muốn biết số bước di chuyển ít nhất để chuyển từ ô số X
đến ô số Y.
Input (standard input):
- Dòng đầu tiên chứa số nguyên T là số lượng test. (1 ≤ T ≤ 10).
- Trong T dòng tiếp theo, mỗi test trên một dòng là 03 số nguyên N, X và Y (1 ≤ N ≤ 1000, 1 ≤ X, Y ≤ N2).
Output (standard output):
- Với mỗi test, in ra số bước ít nhất để di chuyển từ ô số X đến ô số Y. Nếu không di chuyển được in ra -1
Ví dụ
Standard.inp Standard.out
1 3
5 3 18

Thực hiện 3 bước:

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;

function check(x, y: longint): boolean;


var t: longint;
begin
t:= y mod x;
While t <> 0 do

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

Bài 14. TRANSINFO - Truyền thông tin


Mạng máy tính nơi C làm việc gồm có N máy tính, khi nhận được dữ liệu từ máy chủ hay máy
khác, máy thứ i sẽ truyền dữ liệu nó nhận được tới một máy khác có giá trị là Ai. Nhận nhiệm vụ truyền dữ
liệu từ máy chủ tới N máy tính kia, C nhận ra rằng, nếu cậu ta truyền dữ liệu từ máy chủ tới một máy tính bất
kì, thì có thể một số máy tính sẽ không nhận được dữ liệu. Bởi vậy C muốn tăng thêm một số ít nhất các liên
kết truyền dữ liệu giữa các máy, để dữ liệu từ máy chủ tới một máy tính bất kì, thì tất cả các máy tính còn lại
đều sẽ nhận được dữ liệu.
Hãy giúp C xác định một lượng ít nhất các liên kết cần phải thêm đó.
Chú ý: Máy u có thể truyền dữ liệu tới máy v nhưng chưa chắc máy v có thể truyền dữ liệu tới máy
u.
INPUT:
- Một số nguyên N, số máy tính client (1<=N<=105).
- Dòng 2: Gồm N số nguyên A1...AN (1<=Ai<=N).
OUTPUT:
- Dòng 1: Số nguyên M, số liên kết cần phải thêm.
- M dòng tiếp theo, mỗi dòng gồm hai số nguyên u và v thể hiện liên kết mới từ u tới v
Nếu có nhiều đáp án, in ra một cách bất kì.
Ví dụ
Input output
2 1
22 21
program Transinfo;
uses math;
var
a, stack, tp, b, pos: array[1..100000] of longint;
number, low, dau, cuoi: array[1..100000] of longint;
avail: array[1..100000] of boolean;
n, count, last, scc, m: longint;

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;

function pop: longint;


begin
pop:=stack[last];
dec(last);
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 dag(u: longint);


var v:longint;
begin
avail[u]:=false;
cuoi[m]:=u;
v:=b[u];
if v=0 then exit;
if (avail[v]) then dag(v);
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

Bài 15. Đường đi ngắn nhất


Một lâu đài có dạng một hình chữ nhật được chia ra làm N*M phòng. Một 3 3 6 7 11
nhà thám hiểm bắt đầu từ phòng (1,1) muốn di chuyển đến phòng (N, M) tuân thủ
qui tắc sau: 3 2 1 1 3
 Trên sàn của mỗi phòng có viết một số huyền bí là số nguyên trong khoảng
3 2 2 1 1
từ 1 đến 13. Khi nhà thám hiểm có thể lựa chọn một trong tám hướng di chuyển
(song song với các bức tường hoặc theo các đường chéo) lập tức anh ta được di 2 1 2 2 1
chuyển theo hướng đó đi một số lượng phòng đúng bằng số viết trên sàn nhà theo
hướng đã chọn. Nếu như điều đó không thực hiện được (khi số lượng phòng theo hướng lựa chọn là nhỏ hơn
số viết trên sàn) thì nhà thám hiểm vẫn ở nguyên vị trí và buộc phải lựa chọn hướng dịch chuyển khác.
 Không được di chuyển hai lần trong cùng một phương theo cùng một hướng.
Ví dụ: Xét lâu đài trong hình trên, các số trên mặt sàn của mỗi phòng được cho trong ô tương ứng. Nếu
nhà thám hiểm đang đứng ở phòng (3,3) trong lâu đài (có 5*4 phòng) thì anh ta có thể di chuyển đến các
phòng (1;1), (3;1), (1;3), (5;1), và (5;3). Để di chuyển từ phòng (3, 2) đến phòng (5,4) sử dụng hai lần dịch
chuyển, nhà thám hiểm không được di chuyển đến ô (4,3) rồi từ (4,3) đến (5,4), bởi vì như vậy đã di chuyển
trong cùng một phương theo cùng một hướng, mà cần di chuyển đến ô (2,1) và từ đó đến (5,4).
Yêu cầu: Tìm cách di chuyển từ ô (1,1) đến ô (N,M) sau ít lần dịch chuyển nhất?
Input:
 Dòng đầu tiên chứa hai số nguyên N, M, tương ứng là số phòng theo chiều ngang và theo chiều đứng
của sơ đồ toà lâu đài (1 ≤ N, M ≤ 1000).
 Dòng thứ i trong số M dòng tiếp theo chứa dãy N số nguyên là các số viết trên dãy phòng ở dòng thứ
i của toà lâu đài.
Các số trên cùng dòng được viết cách nhau bởi dấu cách.
Output: Ghi ra số bước dịch chuyển ít nhất. Ghi -1 nếu không có cách di chuyển đến ô (N, M).
Ví dụ
Input Output
54 4
3 3 6 7 11
32113
32211
21221

Const dx : array[1..8] of -1..1 = (-1, -1, 0, 1, 1, 1, 0, -1);


dy : array[1..8] of -1..1 = (0, 1, 1, 1, 0, -1, -1, -1);
type hi = record
x, y, h: longint;
end;
var a: array[0..1001, 0..1001] of longint;
b: array[0..1001, 0..1001, 0..8] of longint;
d: array[-15..1015, -15..1015, 0..8] of boolean;
q: array[0..8100000] of hi;
left, right, n, m, i, j, t, xxx: longint;
u: hi;

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.

Test Input Output


1 54 4
3 3 6 7 11
32113
32211
21221
2 99 7
492929292
929272229
292939392
929212929
293929393
929292929
293939293
929292929
292929392
3 54 -1
1 6 6 7 11
96113
32211
21221
4 66 -1
121212
111312
111321
111321
111321
111321
5 14 14 9
3 6 5 4 4 11 8 2 3 4 6 5 4 3
65433527258645
54322439659546

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 ...

Bài 16. ZEROPATH - Phân tích số


Mỗi một số nguyên dương đều có thể biểu diễn dưới dạng tích của 2 số nguyên dương X,Y sao cho
X<=Y . Nếu như trong phân tích này ta thay X bởi X-1 còn Y bởi Y+1 thì sau khi tính tích của chúng ta thu
được hoặc là một số nguyên dương mới hoặc là số 0.
Ví Dụ: Số 12 có 3 cách phân tích 1*12, 3*4, 2*6. Cách phân tích thứ nhất cho ta tích mới là 0: (1-1)
*(12+1) = 0, cách phân tích thứ hai cho ta tích mới 10: (3-1)*(4+1) = 10, còn cách phân tích thứ ba cho ta 7:
(2-1)*(6+1)=7. Nếu kết quả là khác 0 ta lại lặp lại thủ tục này đối với số thu được. Rõ ràng áp dụng liên tiếp
thủ tục trên, cuối cùng ta sẽ đến được số 0, không phụ thuộc vào việc ta chọn cách phân tích nào để tiếp tục.
Yêu cầu: Cho trước số nguyên dương N ( 1<=N<=100000), hãy đưa ra tất cả các số nguyên dương khác nhau
có thể gặp trong việc áp dụng thủ tục đã mô tả đối với N .
Input: 1 dòng chứa số nguyên dương N.
Outputt: Gồm 2 dòng:
- Dòng đầu tiên ghi K là số lượng số tìm được.
- Dòng tiếp theo chứa K số tìm được theo thứ tự tăng dần bắt đầu từ số 0.
Lưu ý: Có thể có số xuất hiện trên nhiều đường biến đổi khác nhau, nhưng nó chỉ được tính một lần trong kết
quả.
Ví dụ
Input Output
12 6
0 3 4 6 7 10

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 dfs(u: longint);


var i: longint;
begin
kt[u]:=true;
inc(count);
for i:=1 to bac[u] do
if (kt[a[u,i]]=false) then dfs(a[u,i]);
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 ...

Bài 17. DS - ebola

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

Type ma=ARRAY [0..2000000] of Longint;


Var n, i, k, u, j, m, h, e : Longint;
a, vt: ARRAY [0..2000000] of Longint;
q: ARRAY [0..2000000] of Longint;
trace: ARRAY [0..2000000] of Boolean;

Procedure qs(l, r : Longint; var kt: ma );


Var i, j, t, tep: Longint;
begin
i:=l;
j:=r;
t:=kt[(l+r) DIV 2];
repeat
While kt[i] < t do inc(i);
While kt[j] > t do dec(j);
If i <= j then
begin
tep:=kt[i];
kt[i]:=kt[j];
kt[j]:=tep;
inc(i); dec(j);
end;
unil i > j;
If i < r then qs(i, r, q);
If l < j then qs(l, j, q);
end;

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 ...

Bài 18. BW - Nối điểm


Trên trục số thực cho n điểm đen và n điểm trắng hoàn toàn phân biệt. Các điểm đen có tọa độ
nguyên a1, a2, …, an còn các điểm trắng có tọa độ nguyên b1, b2, …, bn. Người ta muốn chọn ra k điểm đen
và k điểm trắng để nối mỗi một điểm đen với một điểm trắng sao cho k đoạn thẳng tạo được đôi một không có
điểm chung.
Yêu cầu: Cho tọa độ của n điểm đen a1, a2, …, an và tọa độ của điểm trắng b1, b2, …, bn. Hãy tìm giá trị k
lớn nhất thỏa mãn yêu cầu trên?
Input:
 Dòng thứ nhất chứa số nguyên dương n (n <= 10 ).
5

 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 swap (var x, y: point);


var tmp : point;
begin
tmp:= x;
x:= y;
y:= tmp;
end;

procedure sort (l, r : integer);


var i, j : integer;
tmp : integer;
begin
if l >= r then exit;
i:= l;
j:= r;
tmp:= a[(l + r) shr 1].x;
repeat
while (a[i].x < tmp) do inc(i);
while (a[j].x > tmp) do dec(j);
if i <= j then
begin
swap(a[i],a[j]);
inc(i);
dec(j);
end;
until i > j;
sort(l, j);
sort(i, r);
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.

Test Input Output


1 5 3
-6 3 -3 5 -4
-7 2 -1 -8 1
2 32 24
-52 -63 -48 -60 7 -88 44 -45 -39 -33 -78 -11 67 -28 11 -74 -41 3 -8 -20 -32 60 10 18 15 13 81 31 33 -17 35 -2
-86 55 -68 -34 -69 8 2 -5 84 14 26 30 27 23 21 -94 -16 -49 -30 22 -85 39 -64 -31 37 -7 9 -12 -46 -18 -4 41
3 66 47
40 51 91 61 -10 -11 -77 20 -76 25 -51 -20 -12 16 -35 1 54 -45 62 -54 -73 -22 2 -39 -43 11 -79 43 -61 -4 -13 -74 10 23 36 38 -62 32 -69
65 -27 13 5 -18 78 -38 -33 -19 42 -57 -29 -90 34 -24 74 -34 19 -7 18 0 -83 60 -58 44 57 -28
-5 -46 -44 -49 -1 -78 -36 -6 47 -94 28 -41 -65 -26 -30 -31 -2 -71 -81 46 -23 -64 27 -52 -80 3 29 67 -37 -55 21 -50 30 79 9 7 45 4 -9 33
55 6 12 -63 -3 37 15 31 49 14 ...
4 100 69
-503 -146 -625 70 -704 -248 806 -103 -369 114 563 -170 218 -571 -261 540 60 44 111 -619 -715 502 316 -409 -22 522 156 -177 148 -
39 69 470 211 460 516 165 145 -199 -935 696 -260 11 91 -11 -322 270 488 -291 192 651 190 -319 28 -221 -216 51 -589 309 -86 -667
719 -301 -365 -105 -209 -108 -634 380 195 -274 366 727 -311 -49 351 137 33 173 -175 -19 818 846 125 -101 -218 319 -477 -74 -2
533 -335 -4 ...
5 3637 2409
-783970 -126964 -503727 -419705 -329358 -247728 -458910 -53119 50991 -293621 -311304 -387320 375835 532264 -5261 22893 -
645537 -28957 -381401 241387 -7854 -58486 -201414 -282389 -631194 -691644 97463 -358189 224235 65688 -282293 -281815
596629 -422351 587776 263018 -357306 514709 135151 548528 475802 -184386 468936 325258 -91593 -38150 -503640 289091
40974 937566 -179028 -453207 -232423 -3 ...
6 69495 46412
266788 65247 421540 -377627 104191 -311338 -187586 212730 108730 80113 73030 -451684 872233 -720303 7008 -96718 58540 -
350349 513217 542607 -8331 635832 183113 119286 112522 -13098 649003 -308266 742594 -114847 -561373 -271082 520427
124445 45594 266275 -136234 -211390 66061 -555290 -150452 -331433 -462437 569882 112636 -237672 -779057 131888 569473
8980 -339468 -545976 -10433 459366 1101 ...
7 99999 66590
600012 -46631 -677883 89154 -908618 -140903 155425 932933 536391 208041 -841340 93661 522766 -472866 37187 544877
631453 -85753 360910 -327592 -24362 -115367 129191 -114756 768826 -232281 -254841 470290 -207141 524149 -440706 -862230
-114851 -406774 -305272 -7947 -133343 39196 -62034 -418214 -889897 -538230 -353920 544815 -452900 -151202 23009 551033
239060 -157527 -600465 -416258 -430296 ...
8 100000 66726
-4010 33452 -742533 -801925 287391 61311 -614130 -527810 -471600 451238 132128 -393987 104456 -507468 -539788 497678 -
691139 402242 239576 -359084 95488 -106096 -70420 12968 -601114 30655 352372 57210 -28817 -945643 963242 -382758 -
422330 435940 -729650 530295 -560564 -184386 -659346 663389 -628412 171395 89992 -435668 -598631 740764 -71310 400454 -
231915 42112 -628336 -221916 -165145 -8 ...

Bài 19. QT - 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. 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:
- 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 r i , 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: Gồm 1 dòng duy nhất là số nước đi tìm được.
Ví dụ
Inputt Output

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 push(x, y: longint);


begin
inc(rear);
qx[rear] := x;
qy[rear] := y;
end;

procedure pop(var x, y: longint);


begin
x := qx[front];
y := qy[front];
inc(front);
end;

function min(x, y: longint): longint;


begin
if x < y then exit(x)
else exit(y);
end;

function range(x, y: longint): boolean;


begin
if (x >= 1) and (x <= n) and (y >= 1) and (y <= n) and (a[x, y]) then exit(true);
exit(false);
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

Input 555553 12 0 3 4 10 3 17 9 12 14 15 7 20 4 19 13 15 5 40 10 38 16 36 4 80 20 25 15 19 65 120 50 28 99 68 77 200 0 30 148 173 79


12 67 14 16 20 14 40 18 27 17 2 68 67 185
15 11 13 18 14 37 15 24 14 3 31 136 42
22 13 13 18 12 39 15 26 14 3 58
42 10 16 19 10 36 18 24 16 41
43 11 15 39 17 25 17 6 60
15 10 35 17 27 10 11 63
15 13 27 17 30 19 15 83
16 2 29 9 38 13 16 30
17 9 31 9 41 31 23 4
38 32 47 37 23 90
47 79 28 81
49 20 30 37
50 35 35 25
51 51 36 43
60 80 37 69
68 1 37 108
69 9 39 3
71 50 45 31
72 27 46 109
79 42 46 118
47 91
51 106
52 23
55 58
56 102
59 12
59 68
63 44
64 67
65 8
65 80
68 32
72 98
73 75
78 111
82 95
85 38
85 120
86 117
88 9
88 116
92 58
102 81

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

Bài 20. LINE3 - Trò chơi Line.


Trò chơi Line là trò chơi di chuyển các viên bi trong một hình vuông 9 x 9 ô. Bi được ăn khi tạo thành
các hàng, cột, đường chéo gồm 5 viên bi liên tiếp cùng màu. Một thuật toán được sử dụng trong trò chơi là
tìm đường đi để di chuyển một viên bi. Giả sử trò chơi Line tổng quát có n dòng, n cột. Đánh số các dòng từ 1
đến n theo thứ tự từ trên xuống dưới, đánh số các cột từ 1 đến n theo thứ tự từ trái sang phải. Giả sử có một
viên bi tại ô (y, x) - dòng y cột x, bi có thể di chuyển đến 1 trong 4 ô (y+1, x), (y-1, x), (y, x+1), (y, x-1), nếu
ô đó đang trống.
Yêu cầu: Cho vị trí bắt đầu và vị trí kết thúc của viên bi, hãy viết chương trình cho biết đường
đi ngắn nhất của viên bi (qua ít ô nhất)?
Input: gồm các dòng sau:
- Dòng thứ nhất gồm năm số n, sy, sx, dy, dx, mỗi số cách nhau một khoảng trắng (2 ≤ n ≤ 30; 1 ≤ sy,
sx, dy, dx ≤ n). sy là chỉ số dòng, sx là chỉ số cột của viên bi cần di chuyển. dy là chỉ số dòng, dx là chỉ số cột
của vị trí cần di chuyển viên bi đến.
- Trong n dòng tiếp theo, mỗi dòng gồm n số nguyên 0 hoặc 1, mỗi số cách nhau một khoảng trắng,
biểu thị tình trạng của trò chơi. Số 1 nghĩa là vị trí ô đó có bi, số 0 nghĩa là vị trí ô đó đang trống.
(Dữ liệu cho bảo đảm tại ô (sy, sx) có giá trị là 1, tại ô (dy, dx) có giá trị là 0).
Output: Nếu tìm được đường đi ngắn nhất:
- Dòng đầu tiên in ra số nguyên m là chiều dài (số ô) của đường đi. (bao gồm cả ô đầu tiên của viên bi
và ô đích).
- Trong m dòng tiếp theo, dòng thứ i in ra hai giá trị yi, xi là vị trí ô thứ i trong đường đi, hai số cách
nhau một khoảng trắng.
(Nếu có nhiều đường đi cùng ngắn nhất, chỉ cần in ra một đường đi bất kỳ).
Nếu không tìm được đường đi: in ra 0.
Ví dụ
Input Output
2 1 1 2 2 3
1 0 1 1
1 0 1 2
2 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;

function kttd(x, y:longint): boolean;


begin
kttd:=(x <= n)and(x >= 1)and(y <= n)and(y >= 1);
end;

procedure them(x, y: longint);


begin
inc(c); qx[c]:=x; qy[c]:=y;
end;

procedure lay(var x, y: longint);


begin
x:=qx[d]; y:=qy[d]; inc(d);
end;

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

You might also like