1. Trang chủ
  2. » Trung học cơ sở - phổ thông

Do Thi

266 8 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

Ví dụ, BFS, xây dựng một cây đường đi ngắn nhất trong đồ thị không có hàm chi phí trên các cạnh và DFS là một bước cơ bản để sắp xếp topo hiệu quả, tìm kiếm các thành phần liên thông mạn[r]

(1)PHÂN LOẠI BÀI TOÁN ĐỒ THỊ LỜI MỞ ĐẦU 2 MỘT SỐ KHÁI NIỆM CƠ BẢN .2 2.1 cấu trúc đồ thị 2.2 Phép duyệt đồ thị 2.3 Đồ thị và hàm chi phí 2.4 Đồ thị và quan hệ 2.5 Cây và cây có gốc .8 2.6 Biểu diễn đồ thị và cây 10 TÌM HIỂU MỘT SỐ CÁCH PHÂN LOẠI BÀI TOÁN ĐỒ THỊ 11 3.1 Một số cách phân loại bài toán đồ thị 11 3.1.1 Phân loại theo chương trình khung Bộ GD&ĐT .11 3.1.2 Phân loại theo logic lý thuyết đồ thị 12 3.1.3 Phân loại bài toán theo nhóm các thuật toán trên đồ thị 13 3.2 Nhận xét và lựa chọn chúng tôi .14 (2) LỜI MỞ ĐẦU Bài toán trên đồ thị là chủ đề truyền thống các kỳ thi HSG Quốc gia, Olympic Tin học Quốc tế và khu vực Không mà số lượng các bài toán đồ thị đưa vào các đề thi chiếm tỷ lệ khá lớn MỘT SỐ KHÁI NIỆM CƠ BẢN 2.1 Cấu trúc đồ thị Một cấu trúc đồ thị G(V,E) xác định tập hữu hạn V gồm các đỉnh và tập hữu hạn E các liên kết mà liên kết nối cặp đỉnh với Một liên kết là có hướng cặp đỉnh tương ứng là có thứ tự và liên kết là vô hướng cặp đỉnh không có thứ tự Ta gọi liên kết không có thứ tự là cạnh và liên kết có thứ tự là cung Nếu E là tập cạnh (cung) có lặp lại ta gọi đồ thị đó là đa đồ thị, không có cạnh lặp ta gọi đồ thị đó là đơn đồ thị có thể chia bốn loại đồ thị: đơn đồ thị vô hướng, đơn đồ thị có hướng, đa đồ thị vô hướng, đa đồ thị có hướng Và với loại đồ thị này, thuật toán giải các bài toán tương tự là khác Ví dụ với thủ tục hiển thị cấu trúc đồ thị dạng ma trận liên thuộc g[][] Với cấu trúc cho trước từ tệp tin đầu vào dạng danh sách cạnh (cung) ta có thể gán g[v][w] = ứng với cung (v,w), với cạnh (v,w) thì cần phải gán g[v][w] = và g[w][v] = Với các liên kết (v,v) ta gọi là khuyên, và người ta thường cho phép khuyên có mặt đồ thị có hướng (3) 12 a b c Đa Đ đồ ơn thị đồ thị có hư có vô ớn hư ớn g g 12 12 Hình 1: Minh họa đồ thị 2.2 Phép duyệt đồ thị Ý tưởng "di chuyển" cấu trúc đồ thị, từ đỉnh tới đỉnh khác qua cạnh (cung) nối chúng là thao tác làm việc với đồ thị và ta gọi đó là duyệt đồ thị Ta gọi dãy các đỉnh v0, v1, , vm đồ thị có hướng là đường độ dài m từ đỉnh v0 đến đỉnh vm, cung (vi, vi+1)E với i = 0,1, , m-1 Khi v0 = vm ta gọi đường đó là chu trình Ta gọi dãy các đỉnh v0, v1, , vm đồ thị vô hướng là đường độ dài m từ đỉnh v0 đến đỉnh vm, cạnh(vi, vi+1)E và vi-1 ≠ vi+1 với i = 0,1, , m-1 Khi v0 = vm ta gọi đường đó là chu trình Điều kiện vi-1 ≠ vi+1 là quan trọng không xảy loại chu trình kiểu 1,2,1 1,1, 2, (với cạnh (1,2) đồ thị) Trong đồ thị, hai đỉnh luôn có đường đi, ta nói đồ thị đó liên thông Trong đồ thị có hướng, hai đỉnh có ít đường theo hướng nào đó, thì gọi là liên thông yếu Khi đồ thị không liên thông, nó phân chia thành số đồ thị liên thông ta gọi chúng là thành phần liên thông Ngoài còn có các khái niệm đường không có đỉnh nào (hoặc cạnh nào) lặp lại Đường mà cạnh (cung) xuất đúng lần gọi là (4) đường Euler Đường mà đỉnh xuất đúng lần gọi là đường Hamilton 2.3 Đồ thị và hàm chi phí Mỗi cấu trúc đồ thị có thể xác định hàm chi phí trên các đỉnh cV: V  C, hàm chi phí trên các cạnh(cung) cE: E  C, hai, với C là tập các số nào đó (số nguyên, số hữu tỷ, số thực, v.v tập các tập đó) Giá trị các hàm chi phí, bên cạnh từ chi phí có các tên gọi khác độ dài, trọng số, độ ưu tiên, v.v tùy theo ngữ cảnh Nếu đồ thị không định nghĩa hàm chi phí thì ta coi chi phí đỉnh cạnh (cung) là Hàm chi phí thường mở rộng theo cách tự nhiên trên các đồ thị các cấu trúc đồ thị Ví dụ chi phí đường đồ thị thường là tổng chi phí các đỉnh (hoặc cách cạnh, hai) đồ thị Khái niệm đường có chi phí nhỏ nhất, hay gọi là đường ngắn là khái niệm trên đồ thị với nhiều thuật toán liên quan Ví dụ số bài toán đây có hàm chứa bài toán tìm đường ngắn đã đề cập Bài toán 1: Thành phố có N bến xe buýt gán nhãn 1, 2, , N và R tuyến xe buýt: M1(i1,1, i1,2, , i1,m1), M2(i2,1, i2,2, , i2,m2), , Mr(ir,1, ir,2, , ir,m1), i  ij,k  N, ij,k ≠ ij,l với k ≠ l Mỗi xe buýt xuất phát từ bến đầu mút tuyến, qua tất các bến thuộc tuyến theo trật tự đã cho và đến bến đầu mút còn lại nó quay lại với hành trình đảo ngược Viết chương trình: (i) Kiểm tra người nào đó có thể xe buýt từ bến i đến bến j cho trước hay không? (ii) Với hai bến i và j cho trước, hãy in tất các đường có thể để từ bến i đến bến j? (5) (iii) Với hai bến i và j cho trước, hãy tìm đường nhanh có thể để từ bến i đến bến j, thời gian từ bến này sang bến khác là và nhỏ lần so với thời gian đổi tuyến xe buýt? Bài toán 2: Cho tập V gồm các xâu ký tự độ dài 2N gồm N-1 chữ cái 'A', N-1 chữ cái 'B' và chữ cái 'O' Với hai xâu X và Y thuộc V (hai đỉnh thuộc V), xâu Y gọi là biến đổi từ xâu X (đỉnh X có cạnh nối với đỉnh Y) nếu, ta đổi chỗ hai ký tự 'O' với hai ký tự liền kề nào đó (giữ nguyên thứ tự chúng) X thì Y Xâu có các chữ cái 'A' nằm hoàn toàn phía trái các chữ cái 'B' (không quan tâm vị trí các chữ cái 'O') gọi là xâu cuối cùng Viết chương trình với xâu S cho trước hãy tìm và in chi phí nhỏ để từ S ta biến đổi xâu cuối cùng (tìm và in đường ngắn từ đỉnh S đến đỉnh cuối cùng, chi phí cạnh là 1) Nếu không có cách biến đổi (không có đường đi) thì thông báo 'NOSOLUTION' Bài toán 3: Cho đồ thị G(V = {1, 2, , n}, E) Có r đường độ dài tương ứng là m1, m2, , mr, cạnh G thuộc ít các đường đã cho Chi phí đỉnh là và chi phí cạnh là Viết chương trình: (i) Kiểm tra xem đồ thị có liên thông hay không, (ii) cho cho hai đỉnh v và w, in tất các đường v và w, (iii) cho hai đỉnh v và w, để tìm đường v và w với chi phí tối thiểu Cho đồ thị G (V, E) với hàm chi phí cE: E → C, đó C là tập các số không âm Thì hàm d: V × V → C, đó d(v, w) là chi phí đường ngắn từ v đến w là khoảng cách theo ý nghĩa toán học cổ điển, vì: (i) ∀ v, w ∈ V, d(v, w) ≥ và d (v, w) =  v = w, (ii) ∀ v, w ∈ V, d(v, w) = d (w, v), (iii) ∀ v, w, u ∈ V, d(v, w)  d(v, u) + d(u, w) (6) Hàm khoảng cách cho ta khả xem xét đồ thị G (V, E) là đối tượng hình học và xác định các khái niệm tương ứng Ví dụ, tâm đồ thị G là đỉnh v, đó giá trị nhỏ D(v) = max {d(v,w) | w ∈ V} và đường kính đồ thị G là D(G) = max {d (v, w) | v, w ∈ V} Sự tương tự tính chất "hình học" đồ thị và hình học không gian Euclid biết đến là nguồn gốc bài toán thú vị trên đồ thị 2.4 Đồ thị và quan hệ Cho A và B là tập tùy ý Mỗi tập R tích Đề các A × B gọi là quan hệ Trong toán học, người ta dạy học sinh số lượng lớn các mối quan hệ hữu ích: đại số ta nói ("x nhỏ y", "x nhỏ y", "x y", v.v ), hình học ta nói ("điểm p nằm trên đường thẳng l", "đường thẳng l qua điểm p", "đường thẳng l và m song song", v.v ), lý thuyết tập hợp nói chung, ta nói ("A là tập B","A và B giao nhau", v.v ) Rất nhiều mối quan hệ chúng ta có thể tìm thấy bên ngoài toán học, giới xung quanh chúng ta Ví dụ, quan hệ người - "x là trai y", "x giống y", "x và y cùng lớp", v.v , mối quan hệ phổ biến các làng, "làng x nối với y làng đường" (các quan hệ tương tự có thể thiết lập các ngã tư đường phố nối với đường phố, các nhà ga kết nối đường xe lửa; nguồn điện, trung tâm phân phối điện và khách hàng liên kết đường điện, v.v ) Đó là lý có nhiều bài toán khác có thể xảy Ta cần quan tâm đến các tính chất đặc biệt quan hệ trên tích Descartes A×A phản xạ, đối xứng, phản đối xứng, bắc cầu Một số mối quan hệ cụ thể trên tích Descartes - tương đương (phản xạ, đối xứng và bắc cầu), thứ tự phận (phản xạ, phản đối xứng và bắc cầu) và thứ tự toàn phần (phản xạ, phản đối xứng mạnh và bắc cầu) có ý nghĩa toán học và thuật toán (7) Khái niệm quan hệ hữu hạn trùng với khái niệm đồ thị có hướng Thật vậy, đồ thị có hướng G(V, E) có thể coi quan hệ E ⊆ V × V và ngược lại Một mối quan hệ hữu hạn E ⊆ V × V có tính đối xứng (tùy trường hợp có thêm tính phản xạ) thực là đồ thị Đó là lý sao, bài toán liên quan với quan hệ nào đó có thể coi bài toán trên đồ thị có hướng đồ thị vô hướng Chúng ta hãy xem xét số ví dụ, với giả sử tập đỉnh V là {1, 2, , n} Bài toán 4: Cho E ⊆ V×V là quan hệ tương đương Tìm số lượng các lớp tương đương E Số này có hay không? Nếu số lượng các lớp tương đương lớn 1, hãy tìm các lớp tương đương E Bài toán này (thực là tập các bài toán tương tự) là bài toán cổ điển quan hệ tương đương Vì quan hệ tương đương có tính phản xạ và đối xứng, G (V, E) là đồ thị Từ quan điểm đồ thị, bài toán này có thể phát biểu sau: "Có bao nhiêu thành phần liên thông đồ thị G(V, E)? Đồ thị có liên thông không? Nếu không, hãy liệt kê các đỉnh thành phần liên thông G?" Bài toán 5: Cho E ⊆ V×V là tập quan hệ thứ tự toàn phần (chúng ta biểu thị (x, y) ∈ E x  y) và V'⊆ V, |V'|= M Tìm dãy a1, a2, , aM gồm tất các phần tử V' thỏa mãn a1  a2   aM Tất nhiên, đây là bài toán xếp tập hợp các phần tử dãy thứ tự toàn phần cho trước Có nhánh cụ thể Lý thuyết Thuật toán dành riêng cho nó Bài toán có thể xây dựng bài toán trên đồ thị có hướng Có thể hiểu quan hệ thứ tự đồ thị có hướng không có chu trình Vì vậy, bài toán xếp tập cho trước theo trật tự toàn phần có thể phát biểu sau: "Cho đồ thị có hướng G(V', E') không có chu trình Tìm đường có độ dài cực đại G" Quan hệ E' công thức này là, rõ ràng, hạn chế E trên V' Đồ thị có hướng không có chu trình phổ biến và có tên là dag (viết tắt Directed Acyclic Graphs) (8) Bài toán 6: Cho E ⊆ V×V là tập quan hệ thứ tự phận (chúng ta biểu thị (x, y) ∈ E với x  y) Tìm dãy a1, a2, , aM gồm các phần tử V với chiều dài cực đại, thỏa mãn a1  a2   aM Phát biểu bài toán này dạng bài toán đồ thị có hướng sau: "Cho dag G(V,E) Tìm đường có độ dài cực đại G" Bài toán 7: Cho R1⊆ A × B và R2⊆ B × A, (a, b) ∈ R1 và (b,a) ∈ R2 Tìm tập hợp {(a1, b1), (a2, b2), , (aM, bM)} R1 (hoặc {(b1, a1), (b2, a2), , (bM, aM)} R2) với số lượng phần tử tối đa, thỏa mãn = aj và bi = bj,  i < j  M Quan hệ R1 và R2 với đề cập trên gọi là đảo ngược lẫn Ví dụ quan hệ cặp đôi đảo ngược là "điểm p nằm trên đường thẳng l" và "đường thẳng l qua điểm p" Một ví dụ thực tế sống là "người p có thể làm công việc w" và "công việc w có thể thực người p" Đối với quan hệ cặp đôi đảo ngược, chúng ta có thể xây dựng đồ thị G(V = A ∪ B, R1) (hoặc G (V = A ∪ B, R2)) coi các phần tử R1 (hoặc R2) là không có thứ tự Đồ thị gọi là đồ thị hai phía Việc tìm tập có M cạnh, mà đỉnh là đầu mút nhiều cạnh M gọi là cặp ghép Trong đồ thị bài toán có thể phát biểu sau: "Cho đồ thị hai phía G(V = A ∪ B, R1) Tìm cặp ghép cực đại G" 2.5 Cây và cây có gốc Khi nói các bài toán trên đồ thị, ta không thể không giới thiệu khái niệm cây Theo định nghĩa cổ điển, đồ thị T(V, E) là cây nó liên thông và không có chu trình Hai định nghĩa quy nạp tương đương cây phát biểu sau: Định nghĩa (i) Đồ thị T ({r}, ∅) là cây có gốc r là gốc và r là lá T; (9) (ii) Cho T(V, E) là cây có gốc r và các lá L = {v1, v2, , vk} Cho v ∈ V và w  V; (iii) thì, T''(V '= V∪{w}, E' = E∪{(v, w)}) là cây có gốc là r và lá T' là (L - {v}) ∪ {w}, (xem minh họa hình 2a) Định nghĩa (i) Đồ thị T({r}, ∅) là cây có gốc là r và r là lá T; (ii) Cho T1(V1, E1), T2(V2, E2), , Tk(Vk, Ek), là các cây có gốc tương ứng là r1, r2, , rk, và lá L1, L2, , Lc Cho r  V1 ∪ V2 ∪ ∪ Vk; (iii) thì, T'(V' = V1 ∪ V2 ∪ ∪ Vk ∪ {r}, E' = E1 ∪ E2 ∪ ∪ Ek ∪ {(r, r1), (r, r2), , (r, rk)}) là cây có gốc r và lá T' là L1 ∪ L2 ∪ ∪ Lc Các cây có gốc T1, T2, , Tk gọi là cây T', (xem minh họa hình 2b) Các khái niệm cây dẫn đến các thủ tục đệ quy tự nhiên Bởi cây bắt nguồn từ định nghĩa là đồ thị vô hướng Định nghĩa giới thiệu hướng tiềm ẩn trên các cạnh cây có gốc Chúng ta có thể nói v là cha w và w là v Rõ ràng cây có gốc là cây và cây có thể xây dựng lại cây có gốc ta chọn các đỉnh là gốc nó Nếu G(V,E) là đồ thị và T(V, E') là cây (có gốc) thỏa mãn E'⊆ E thì T gọi là cây khung G Đồ thị G liên thông và nó có cây khung Như vậy, cách tự nhiên để kiểm tra tính liên thông đồ thị G là xây dựng cây khung G Nếu c: E → C là hàm chi phí trên các cạnh G(V, E), chúng ta có thể mở rộng nó thành hàm chi phí cây khung G, định nghĩa (10) c(T (V , E ' ))   c(e) eE ' Mỗi cây khung T G với c(T) tối thiểu tối đa) gọi là cây khung tối thiểu (tối đa) G r T2 Tk T1 12k Trvw Hình Minh họa hai định nghĩa tương đương cây có gốc 2.6 Biểu diễn đồ thị và cây Trong lĩnh vực khác nhau, việc biểu diễn các kiểu liệu trừu tượng đơn đồ thị vô hướng, đơn đồ thị có hướng, đa đồ thị vô hướng, đa đồ thị có hướng và cây cấu trúc liệu là quan trọng việc xây dựng các thuật toán hiệu Theo truyền thống, đồ thị đưa vào qua tập tin đầu vào, đã đề cập trên, hình thức danh sách cạnh (cung) và trước đó là số n - số đỉnh nó và số m - số cạnh (cung) đồ thị và phần hướng dẫn phải rõ là đồ thị vô hướng (cạnh) có hướng (cung) Nếu phần quan trọng thuật toán thực trên cấu trúc đồ thị G(V, E) là lệnh lặp dạng: for e ∈ G do{ } thì danh sách cạnh (cung) biểu diễn với chi phí thời gian O(m) Nếu cùng thuật toán biểu diễn đồ thị G với ma trận kề (ví dụ, mảng hai chiều g thỏa mãn g[v][w] là số cạnh (cung) nối v và w) chi phí thời gian là O(n 2) và việc thờì gian thi hành chậm với các đồ thị thưa Nếu phần quan trọng thuật toán là lệnh lặp dạng: 10 (11) for v ∈ V'⊆ V {for w∈V''⊆ V do{ if (v, w) ∈ E { }}} thì thực với cấu trúc danh sách cạnh (cung) có chi phí thời gian O(|V'|| V'' |.m) và với cấu trúc ma trận kề có chi phí thời gian O(|V'||V''|), tốt so với việc dùng cấu danh sách cạnh (cung) Nếu phần quan trọng thuật toán là lệnh lặp dạng: for v ∈ V {for w|(v, w) ∈ E { }} thì thực với cấu trúc ma trận kề có chi phí thời gian O(n2) Trong trường hợp này, sử dụng cấu trúc danh sách kề cho bạn chi phí thời gian O(m) Đặc biệt là với cây có gốc, chúng ta biểu diễn danh sách cha g[] với g[i] là cha i cho đỉnh không phải gốc r và g[r] = Cách biểu diễn này với cây có gốc là thuận tiện cần xây dựng cây có gốc (cây khung, cây khung tối thiểu, v.v ) để duyệt cây TÌM HIỂU MỘT SỐ CÁCH PHÂN LOẠI BÀI TOÁN ĐỒ THỊ Phân loại các bài toán trên đồ thị là điều quan trọng vì lý khác Phân loại tốt có thể hữu ích cho việc chuẩn bị chương trình đào tạo và tổ chức quá trình đào tạo - cho việc định thứ tự các chuyên đề giảng dạy theo trật tự định Ngoài việc phân loại bài toán đồ thị, hữu ích để chuẩn bị cho thi Trong phần này chúng tôi xin đề nêu số cách phân loại các bài toán trên đồ thị mà chúng tôi sưu tầm qua các tài liệu và quan điểm lựa chọn chúng tôi 3.1 Một số cách phân loại bài toán đồ thị 3.1.1 Phân loại theo chương trình khung Bộ GD&ĐT * Lớp 10: + Chuyên đề 2: PHÂN TÍCH, THIẾT KẾ VÀ CÀI ĐẶT THUẬT TOÁN Nội dung 7: Mô hình đồ thị không có và có trọng số, cây 11 (12) Nội dung 8: Bài toán tìm đường ngắn Nội dung 9: Bài toán tìm cây khung nhỏ * Lớp 11: + Chuyên đề LÝ THUYẾT TRÒ CHƠI + Chuyên đề 4: BÀI TOÁN LUỒNG CỰC ĐẠI TRONG MẠNG VÀ ỨNG DỤNG Nội dung 1: Biểu diễn đồ thị, duyệt đồ thị Nội dung 2: Bài toán luồng cực đại Nội dung 3: Lát cắt Đường tăng luồng Định lý Ford – Fulkerson Nội dung 4: Thuật toán Ford - Fulkerson tìm luồng cực đại mạng Nội dung 5: Một số bài toán luồng tổng quát Nội dung 6: Một số ứng dụng + Chuyên đề BÀI TOÁN LẬP LỊCH: Đề cập đến số bài toán lập lịch có ứng dụng mô hình bài toán đồ thị * Lớp 12: + Chuyên đề CÁC CẤU TRÚC DỮ LIỆU NÂNG CAO Đề cập đến ứng dụng kiểu liệu heap vào bài toán đồ thị 3.1.2 Phân loại theo logic lý thuyết đồ thị Tài liệu Giải thuật và lập trình (TS Lê Minh Hoàng), Tài liệu giáo khoa chuyên Tin và (TS Hồ Sĩ Đàm chủ biên), Toán rời rạc (TS Nguyễn Đức Nghĩa và TS Nguyễn Tô Thành) có bố cục kiến thức lý thuyết đồ thị tương tự sau: Các khái niệm 12 (13) Biểu diễn đồ thị trên máy tính Các thuật toán tìm kiếm trên đồ thị (DFS, BFS) Tính liên thông đồ thị Vài ứng dụng DFS và BFS Chu trình Euler, đường Euler, đồ thị Euler Chu trình Hamilton, đường Hamilton, đồ thị Hamilton Bài toán tìm đường ngắn Bài toán cây khung nhỏ 10 Bài toán luồng cực đại trên mạng 11 Bài toán tìm ghép cực đại trên mạng 12 Bài toán tìm ghép cực đại với trọng số cực tiểu trên đồ thị hai phía 13 Bài toán tìm ghép cực đại trên đồ thị 3.1.3 Phân loại bài toán theo nhóm các thuật toán trên đồ thị + Tài liệu Algorithms (Robert Sedgewick) Phần 6: Các thuật toán đồ thị có bố cục sau: Các thuật toán đồ thị: Thuật ngữ, Biểu diễn đồ thị, Tìm kiếm DFS và BFS, Mê cung Đồ thị liên thông: Các thành phần liên thông, Song liên thông, Các thuật toán tìm phần hợp Đồ thị có trọng số: Cây khung tối thiểu, Đường ngắn nhất, Đồ thị dày, Các bài toán hình học Đồ thị có hướng: Tìm kiếm DFS, Bao đóng bắc cầu, Sắp xếp tô pô, Tìm tất các đường ngắn nhất, các thành phần liên thông mạnh 13 (14) Luồng mạng: Bài toán luồng mạng, Phương pháp Ford-Fulkerson, Tìm kiếm trên mạng Cặp ghép: Đồ thị hai phía, Bài toán hôn nhân bền vững, các thuật toán nâng cao + Tài liệu Introduction to Algorithms (Thomas H Cormen, Charles E Leiserson, Ronald L Rivest, Clifford Stein) Phần 6: Các thuật toán đồ thị tài liệu này phân chia bài toán đồ thị sau: Các thuật toán đồ thị: Biểu diễn đồ thị, Tìm kiếm DFS và BFS, xếp topo, các thành phần liên thông mạnh Cây khung tối thiểu: Xây dựng cây khung tối thiểu, các thuật toán Prim, Kruskal Đường đơn ngắn nhất: Thuật toán Bellman-Ford, đường đơn ngắn đồ thị không có chu trình, thuật toán Dijkstra Đường ngắn tất các cặp đỉnh: Các đường ngắn và phép nhân ma trận, Thuật toán Floyd-Warshall, Thuật toán Johnson với đồ thị thưa Luồng cực đại: Luồng mạng, Phương pháp Ford-Fulkerson, Cặp ghép cực đại 3.2 Nhận xét và lựa chọn chúng tôi Cách phân loại các bài toán đồ thị theo logic lý thuyết đồ thị các tài liệu nêu phần 3.2.1 có thể coi là các phân loại truyền thống hầu hết các giáo trình dạy lý thuyết đồ thị cho học sinh và sinh viên Đơn vị chúng tôi thực phân phối chương trình dạy toán đồ thị cho học sinh theo cách phân loại trên Chúng tôi thường chọn giáo trình Toán rời rạc Nguyễn Đức Nghĩa và Nguyễn Tô Thành làm tài liệu tham khảo chính để dạy phần Lý thuyết đồ thị và giáo trình Giải thuật và Lập trình TS Lê Minh Hoàng làm tài liệu tham khảo cho học sinh thực hành cài đặt các giải thuật trên máy tính Bên cạnh đó, chúng tôi 14 (15) thấy các mô đun phân phối chương trình Tin Bộ GD&ĐT chưa cụ thể, chưa khoa học và logic Phân loại các bài toán trên sở các thuật toán sử dụng là cách tiếp cận điển hình cho các sách thuật toán Những sách này không nhằm mục đích xem xét các bài toán thuộc miền toán học cụ thể nó giới thiệu các nguyên tắc và phương pháp tiếp cận thiết kế và phân tích các thuật toán Bài toán đồ thị tài liệu Introduction to Algorithms nêu phần 3.2 bắt đầu với chương "Các thuật toán đồ thị", đó thảo luận các phép duyệt đỉnh đồ thị là BFS và DFS Cả hai phương pháp áp dụng để giải các bài toán khác liên quan đến tính liên thông đồ thị và đường hai đỉnh khác Nhưng hai thuật toán này sử dụng để giải số bài toán cụ thể Ví dụ, BFS, xây dựng cây đường ngắn đồ thị không có hàm chi phí trên các cạnh và DFS là bước để xếp topo hiệu quả, tìm kiếm các thành phần liên thông mạnh, các điểm khớp và các cầu, v.v Chương thứ hai, dành riêng cho các thuật toán tìm cây khung tối thiểu đồ thị (MSP) Các thuật toán tìm MST còn đề cập đến chương nói thuật toán tham lam phần đặc biệt dành riêng cho việc áp dụng thuật toán tham lam trên đồ thị Hai chương là "Đường đơn ngắn nhất" và "Đường ngắn tất các cặp đỉnh" Thực hai chương này có thể dồn thành các tài liệu thuật toán khác Ta có thêm hai nhận xét Đầu tiên, từ "ngắn nhất" phải xem xét nghĩa rộng - bài toán tìm đường lớn nhất, đường tin cậy hơn, v.v , giải với phương pháp tương tự - phương pháp làm tốt dần Và thứ hai, bài toán tìm kiếm tâm, điểm giữa, bán kính (hoặc đường kính), v.v đồ thị giải phương pháp làm tốt dần 15 (16) Chương cuối cùng là "Luồng cực đại" Bên cạnh số thuật toán để tìm luồng cực đại mạng lưới, chương này xem xét các bài toán liên quan đến việc tìm ghép cực đại đồ thị hai phía Cách phân chia các bài toán đồ thị theo nhóm các thuật toán số tài liệu, đã nêu phần 3.2 lại khá hữu dụng ôn tập kiến thức cho học sinh tham gia đội tuyển thi Học sinh giỏi Chúng tôi đã chọn sử dụng cách phân chia này quá trình ôn luyện, hướng dẫn học sinh phát dạng bài toán đồ thị và qua đó các em có thể nhanh chóng chọn thuật toán phù hợp để áp dụng vào giải bài tập Trong quá trình ôn tập cho học sinh, chúng tôi tham khảo thêm các chủ đề bài tập liệt kê các tài liệu như: Art of programming contest, The Programming Contest Training Manual 16 (17) TÀI LIỆU THAM KHẢO [1] Bộ GD&ĐT, Chương trình chuyên sâu môn Tin học trường THPT chuyên [2] Nguyễn Đức Nghĩa, Nguyễn Tô Thành, Toán rời rạc, NXB Đại học Quốc gia Hà Nội, 2007 [3] Lê Minh Hoàng, Giải thuật và lập trình, Ebook [4] Thomas H.Cormen, Charles E.Leiserson, Ronald L.Rivest, Introduction to Algorithms, The MIT Press, 2001 [5] Robert Sedgetwick, Algorithms, Addison-Wesley Publishing Co, 1984 [6] Krassimir Manev, Tasks on Graphs, 2008 [7] http://en.wikipedia.org 17 (18) MỘT SỐ BÀI TOÁN VỀ CÂY KHUNG NHỎ NHẤT Bài toán cây khung nhỏ là bài toán tối ưu thuộc phần lý thuyết đồ thị Như chúng ta biết, có thuật toán để giải bài toán này, đó là thuật toán Prim và thuật toán Kruskal, Tài liệu Giáo khoa chuyên Tin (Quyển 2) đã trình bày kỹ thuật toán, hướng dẫn cách cài đặt cụ thể và đánh giá độ phức tạp tính toán Trong bài viết này đưa số bài tập áp dụng thuật toán Bài toán 1: Vòng đua F1- Mã bài: NKRACING Singapore tổ chức đua xe Công Thức vào năm 2008 Trước đua diễn ra, đã xuất số đua đêm trái luật Chính quyền muốn thiết kế hệ thống kiểm soát giao thông để bắt giữ các tay đua phạm luật Hệ thống bao gồm số camera đặt trên các tuyến đường khác Để đảm bảo tính hiệu cho hệ thống, cần có ít camera dọc theo vòng đua Hệ thống đường Singapore có thể mô tả dãy các nút giao thông và các đường nối hai chiều (xem hình vẽ) Một vòng đua bao gồm nút giao thông xuất phát, là đường bao gồm ít tuyến đường và cuối cùng quay trở lại điểm xuất phát Trong vòng đua, tuyến đường qua đúng lần, theo đúng hướng Chi phí để đặt camera phụ thuộc vào tuyến đường chọn Các số nhỏ hình vẽ cho biết chi phí để đặt camera lên các tuyến đường Các số lớn xác định các nút giao thông Camera đặt trên các tuyến đường không phải các nút giao thông Bạn cần chọn số tuyến đường cho chi phí lắp đặt là thấp đồng thời đảm bảo có ít camera dọc theo vòng đua Viết chương trính tìm cách đặt các camera theo dõi giao thông cho tổng chi phí lắp đặt là thấp Dữ liệu  Dòng đầu tiên chứa số nguyên n, m ( ≤ n ≤ 10000, ≤ m ≤ 100000) là số nút giao thông và số đường nối Các nút giao thông đánh số từ đến n 18 (19)  m dòng mô tả các đường nối, dòng bao gồm số nguyên dương cho biết hai đầu mút tuyến đường và chi phí lắp đặt camera Chi phí lắp đặt thuộc phạm vi [1, 1000] Kết In số nguyên là tổng chi phí lắp đặt thất tìm Ví dụ Dữ liệu: 67 125 233 145 454 564 633 Kết Thuật toán: Ban đầu ta giả sử đã đặt camera tuyến đường, cần tìm cách bỏ số các camera với tổng chi phí giảm là lớn Tập hợp các tuyến đường bỏ không chứa chu trình vì chứa tạo vòng đua không giám sát, suy có thể bỏ nhiều là n-1 camera n-1 tuyến đường và n-1 tuyến đường đó là cây khung đồ thị Để giảm nhiều chi phí thì cần tìm cây khung lớn đồ thị để bỏ camera trên các cạnh cây khung đó Chương trình: {$mode objfpc} const fi='nkracing.inp'; 19 (20) fo='nkracing.out'; max=10000; maxm=100000; vc=100000000; var f:text; n,m,kq:longint; x,y,c:array[0 maxm+1]of longint; {a,ts:array[0 maxm*2+1]of longint;} goc:array[0 max+1]of longint; chon:array[0 maxm+1]of longint; dd:array[0 max+1]of boolean; procedure doc; var i,j:longint; begin assign(f,fi); reset(f); readln(f,n,m); kq:=0; for i:=1 to m begin read(f,x[i],y[i],c[i]); kq:=kq+c[i]; end; close(f); end; procedure viet; var i,j:longint; begin assign(f,fo); rewrite(f); writeln(f,kq); close(f); 20 (21) end; function laygoc(u:longint):longint; begin while goc[u]<>-1 u:=goc[u]; laygoc:=u; end; procedure doi(var i,j:longint); var tg:longint; begin tg:=i; i:=j; j:=tg; end; procedure sort(d1,c1:longint); var i,j,gt:longint; begin if d1>=c1 then exit; i:=d1; j:=c1; gt:=c[(c1+d1)div 2]; repeat while c[i]>gt inc(i); while c[j]<gt dec(j); if i<=j then begin if i<j then begin doi(x[i],x[j]); doi(y[i],y[j]); doi(c[i],c[j]); 21 (22) end; dec(j); inc(i); end; until i>j; sort(d1,j); sort(i,c1); end; procedure lam; var i,j,dem,u,v,i1,j1,p:longint; begin for i:=0 to n goc[i]:=-1; sort(1,m); dem:=0; for i:=1 to m begin u:=laygoc(x[i]); v:=laygoc(y[i]); if u<>v then begin inc(dem); goc[u]:=x[i]; kq:=kq-c[i]; goc[x[i]]:=y[i]; chon[dem]:=i; if dem=n-1 then break; end; end; end; 22 (23) BEGIN doc; lam; viet; END Bài toán 2: Xây dựng thành phố - Mã bài: NKCITY Nước Anpha lập kế hoạch xây dựng thành phố và đại Theo kế hoạch, thành phố có N vị trí quan trọng, gọi là N trọng điểm và các trọng điểm này đánh số từ tới N Bộ giao thông đã lập danh sách M tuyến đường hai chiều có thể xây dựng hai trọng điểm nào đó Mỗi tuyến đường có thời gian hoàn thành khác Các tuyến đường phải xây dựng cho N trọng điểm liên thông với Nói cách khác, hai trọng điểm cần phải di chuyển đến qua số tuyến đường Bộ giao thông chọn số tuyến đường từ danh sách ban đầu để đưa vào xây dựng cho điều kiện này thỏa mãn Do nhận đầu tư lớn từ chính phủ, giao thông thuê hẳn đội thi công riêng cho tuyến đường cần xây dựng Do đó, thời gian để hoàn thành toàn các tuyến đường cần xây dựng thời gian lâu hoàn thành tuyến đường nào đó Yêu cầu: Giúp giao thông tính thời gian hoàn thành các tuyến đường sớm thỏa mãn yêu cầu đã nêu Dữ liệu Dòng chứa số N và M (1 ≤ N ≤ 1000; ≤ M ≤ 10000) M tiếp theo, dòng chứa ba số nguyên u, v và t cho biết có thể xây dựng tuyến đường nối trọng điểm u và trọng điểm v thời gian t Không có hai tuyến đường nào nối cùng cặp trọng điểm Kết Một số nguyên là thời gian sớm hoàn thành các tuyến đường thỏa mãn yêu cầu đã nêu Ví dụ Dữ liệu 23 (24) 57 122 151 251 143 132 532 344 Kết Thuật toán: Đề bài là tìm cây khung có cạnh lớn là nhỏ và đưa cạnh lớn đó, nhiên tôi nghĩ cây khung đã là nhỏ thì cạnh lớn nó là nhỏ số các cạnh lớn các cây khung Vì vậy, tôi dùng thuật toán Kruskal tìm cây khung nhỏ áp dụng cho bài toán này, cạnh cuối cùng thêm vào là cạnh lớn cây khung Chương trình: {$mode objfpc} const fi='nkcity.inp'; fo='nkcity.out'; max=1000; maxm=10000; vc=100000000; var f:text; n,m,kq1,kq2:longint; x,y,c:array[0 maxm+1]of longint; {a,ts:array[0 maxm*2+1]of longint;} goc:array[0 max+1]of longint; chon:array[0 maxm+1]of longint; dd:array[0 max+1]of boolean; procedure doc; var i,j:longint; 24 (25) begin assign(f,fi); reset(f); readln(f,n,m); for i:=1 to m begin read(f,x[i],y[i],c[i]); end; close(f); end; procedure viet; var i,j:longint; begin assign(f,fo); rewrite(f); writeln(f,kq1); close(f); end; function laygoc(u:longint):longint; begin while goc[u]<>-1 u:=goc[u]; laygoc:=u; end; procedure doi(var i,j:longint); var tg:longint; begin tg:=i; i:=j; j:=tg; end; 25 (26) procedure sort(d1,c1:longint); var i,j,gt:longint; begin if d1>=c1 then exit; i:=d1; j:=c1; gt:=c[(c1+d1)div 2]; repeat while c[i]<gt inc(i); while c[j]>gt dec(j); if i<=j then begin if i<j then begin doi(x[i],x[j]); doi(y[i],y[j]); doi(c[i],c[j]); end; dec(j); inc(i); end; until i>j; sort(d1,j); sort(i,c1); end; procedure lam; var i,j,dem,u,v,i1,j1,p:longint; begin for i:=0 to n goc[i]:=-1; sort(1,m); 26 (27) kq1:=0; dem:=0; for i:=1 to m begin u:=laygoc(x[i]); v:=laygoc(y[i]); if u<>v then begin inc(dem); kq1:=c[i]; goc[u]:=x[i]; goc[x[i]]:=y[i]; chon[dem]:=i; if dem=n-1 then break; end; end; end; BEGIN doc; lam; viet; END Bài toán 3: Mạng truyền thông - Mã bài: COMNET (Đề thi HSG QG 2013) Tổng công ty Z gồm N công ty con, đánh số từ 1-N Mỗi công ty có máy chủ Để đảm bảo truyền tin các công ty, Z thuê M đường truyền tin để kết nối N máy chủ thành mạng máy tính Tổng công ty Không có đường truyền nối cùng cặp máy chủ Đường truyền i nối máy chủ công ty u i, vi có chi phí là wi Mạng máy tính có tính thông suốt, nghĩa là từ máy chủ có thể truyền tin đến máy chủ bất kì khác đường truyền trực tiếp qua nhiều đường trung gian 27 (28) Một đường truyền gọi là không tiềm : mặt, việc loại bỏ đường truyền này không làm tính thông suốt; mặt khác, nó phải có tính không tiềm năng, nghĩa là không thuộc mạng thông suốt gồm N máy chủ và N-1 đường truyền tin với tổng chi phí thuê bao nhỏ nào mạng máy tính Trong thời gian tới, chi phí thuê bao số đường truyền tin thay đổi Tổng công ty muốn xác định với chi phí thì đường truyền thứ k có là đường không tiềm hay không để xem xét chấm dứt việc thuê đường truyền này Yêu cầu: Cho Q giả định, giả định cho biết danh sách các đường truyền tin với chi phí thuê và số k Với giả định chi phí thuê đường truyền tin, hãy xác định đường truyền tin thứ k có là đường truyền tin không tiềm mạng không Input  Dòng đầu là T – số testcase T nhóm dòng, nhóm cho thông tin testcase  Dòng thứ gồm số nguyên dương N, M, Q (Q <= 30)  Dòng thứ i M dòng chứa số nguyên dương u i, vi, wi (ui ≠ vi, wi < 109)  Dòng thứ j Q dòng mô tả giả định thứ j: o Số đầu tiên là số kj đường truyền tin cần xem xét o Tiếp theo là sj ( sj <= 100) cho biết số lượng đường truyền có chi phí thuê o Cuối cùng là sj cặp số nguyên dương tp, cp cho biết đường truyền thứ có chi phí thuê là cp (cp < 109) Output  Gồm T nhóm dòng, nhóm gồm Q dòng Mỗi dòng là câu trả lời cho giả định tương ứng input Ghi YES câu trả lời là khẳng định và NO trường hợp ngược lại Example Input: 332 121 Output: NO YES 28 (29) 132 233 322434 1114 Giới hạn  30% số test đầu có ≤ N ≤ 100;  30% số test có ≤ N ≤ 104 và ≤ M ≤ 105;  40% số test còn lại có ≤ N ≤ 105 và ≤ M ≤ 106 Thuật toán: Ta tóm tắt đề bài sau: Cho đồ thị vô hướng N đỉnh M cạnh và Q truy vấn Mỗi truy vấn yêu cầu thay đổi trọng số S cạnh đồ thị và hỏi xem cạnh K có thuộc cây khung nhỏ đồ thị hay không Nhận thấy, sau bỏ cạnh K khỏi đồ thị ta không tìm cây khung tìm cây khung nhỏ có trọng số lớn ban đầu thì K là cạnh nằm trên cây khung nhỏ Độ phức tạp O(Q x độ phức tạp tìm cây khung nhỏ nhất) 30% số test đầu: cài đặt thuật toán Prim Kruskal thông thường 30% số test tiếp theo, ta cải tiến thuật toán Prim sử dụng cấu trúc liệu Heap có độ phức tạp O(Q x NlogN), dùng thuật toán Kruskal với cấu trúc liệu Disjoint-set forest- độ phức tạp O(Q x (O(MlogM)+O(N))), đó O(MlogM) là chi phí xếp M cạnh và O(N) là chi phí quản lý Disjoint-set forest Để đạt 100% số test ta dùng dùng thuật toán Kruskal với cấu trúc liệu Disjoint-set forest, duyệt hết các cạnh có trọng số nhỏ cạnh K, duyệt đến cạnh (u,v) thì ta hợp tập chứa cạnh u và tập chứa cạnh v lại, Cuối cùng cạnh K là cạnh tiềm nó nối hai tập rời Chương trình: Program comnet; const fi='comnet.inp'; fo='comnet.out'; mn=100000+100; mm=1000000+1000; 29 (30) type tedge=record u,v,w:longint; end; Var edge:array[0 mm] of tedge; tmp:array[0 mm] of tedge; p:array[0 mn] of longint; n,m,q:longint; ntest:longint; Function getRoot(u:longint):Longint; begin if p[u]=u then exit(u); p[u]:=getRoot(p[u]); exit(p[u]); end; procedure union(u,v:longint); begin u:=getRoot(u); v:=getRoot(v); if u=v then exit; p[u]:=v; end; procedure solve; var i,k,s,t,c:longint; begin readln(n,m,q); for i:=1 to m with edge[i] readln(u,v,w); while q>0 begin 30 (31) dec(q); // dung mang tmp de luu so cac canh ban dau for i:=1 to m tmp[i]:=edge[i]; read(k,s); // thay doi s canh teo truy van for i:=1 to s begin read(t,c); tmp[t].w:=c; end; //khoi tao disjoin set for i:=1 to n p[i]:=i; //duyet qua cac canh co so nho hon canh K for i:=1 to m with tmp[i] if w<tmp[k].w then union(u,v); // thu xem canh k co noi dinh thuoc tap roi hay khong with tmp[k] begin if getRoot(u)<>getRoot(v) then writeln('YES') else writeln('NO'); end; end; end; begin{mai} assign(input,fi); reset(input); assign(output,fo); rewrite(output); 31 (32) readln(ntest); while ntest>0 begin dec(ntest); solve; end; end 32 (33) QUI HOẠCH ĐỘNG TRÊN ĐỒ THỊ CÓ HƯỚNG, KHÔNG CHU TRÌNH Giải các bài toán có nội dung đồ thị là phần quan trọng chương trình tin học, chuyên đề này là nội dung nhỏ lý thuyết đồ thị là "Các bài toán qui hoạch động trên đồ thị có hướng, không có chu trình" Chuyên đề trình bày số kinh nghiệm dạy đồ thị có hướng không chu trình Một điều khá lý thú là đây hai nội dung chính chương trình tin học là Qui hoạch động và lý thuyết đồ thị kết hợp Chính điều này cho phép xây dựng cho học sinh cách nhìn tổng quan tiếp cận hai dạng toán này Phần lớn các ví dụ minh họa chuyên đề lấy từ các kỳ thi học sinh giỏi khác Mục đích làm là tôi muốn trao đổi với các bạn đồng nghiệp cách xây dựng đề toán đồ thị cho vừa có thể ôn tập kiến thức học sinh, vừa có thể bám sát chương trình thi các kỳ thi học sinh giỏi tin học I-MỘT SỐ KHÁI NIÊM VÀ BÀI TOÁN CƠ BẢN Như chúng ta đã biết, đồ thị có thể hình dung là cặp (V, E) đó V là tập hợp các đỉnh (trong các bài toán tin học thì V là tập hợp hữu hạn các đỉnh có thểđánh số 1, 2, , N) còn E là tập hợp các cung đồ thị Một đồ thị có hướng không có chu trình là đồ thị không tồn đường khép kín Cũng có thể hình dung đây là đồ thị mà số lượng đỉnh tất các thành phần liên thông mạnh Một đồ thị có hướng không có chu trình luôn tồn xếp topo Chính xác hơn, xếp topo là cách xếp các đỉnh đồ thị thành dãy x1 , x2 , , xn 33 (34) Sao cho cung ( xi , x j )  E kéo theo i<j Việc xếp topo trên đồ thị có hướng không có chu trình là điều kiện tiên để làm các bài toán qui hoạch động trên loại đồ thị này Lý đơn giản là coi đỉnh đồ thị là trạng thái thì với việc xếp topo chúng ta có thứ tự trên các trạng thái này và đây chính là cách tiếp cận vấn đề theo quan điểm qui hoạch động Có hai cách chính đề xây dựng xếp topo trên đồ thị có hướng không có chu trình: Cách thứ nhất: Dựa vào tiêu chí tự nhiên mà xếp tăng/giảm theo tiêu chính này thì đương nhiên ta có xếp topo Ví dụ (VOI 2008): Cho n hình tròn bán kính r1 , r2, , rn Ta nói từ đường tròn bán kính a có thể nhảy tới hình tròn bán kính b tồn hình tròn bán kính c cho a+c=b (*) Hãy tìm đường qua nhiều hình tròn Dễ nhận thấy điều kiện (*) chứng tỏ từ hình tròn có thể nhảy đến hình tròn có bán kính lớn nên hiển nhiên ta xếp lại các hình tròn cho bán kính chúng tăng dần ta có xếp topo Thông thường các tiêu chí tự nhiên này thường dễ thấy và việc xếp topo qui việc xếp tăng/giảm trên tiêu chí này Do đó, hiển nhiên tiêu chí xếp phải dựa trên liệu có mối quan hệ xếp hoàn toàn (thông thường là các số) Cách thứ hai: Dựa vào thuật toán Tarjan tìm thành phần liên thông mạnh Chú ý đồ thị là không có chu trình thì các thành phần liên thông mạnh có số lượng đỉnh Do trường hợp này ta cần liệt kê các đỉnh theo thứ tự sau phép duyệt đồ thị ưu tiên chiều sâu Mã giả nó viết đây PROCEDURE visit(u) Đánh dấu u thăm 34 (35) For v  Ke(u) if (v chưa thăm) then visit(v) Đưa u vào danh sách topo (Có thể tham khảo mã Pascal sách giáo khoa chuyên tin Tập 1) Cách thứ hai dùng không thể tìm tiêu chí tự nhiên việc xếp topo Tuy đây là cách tổng quát áp dụng cho trường hợp theo kinh nghiệm tôi thì thông thường xếp topo ta hay sử dụng cách thứ Giả sử trên đồ thị có hướng không có chu trình G=(V,E) ta đã có xếp topo x1 , x2 , , xn Khi đó ta có hai bài toán sau: Bài toán 1:Cho cung đồ thị trọng số Hãy tìm đường dài từ đỉnh s đến đỉnh t Đặt f [ xi ] lần là độ dài đường dài từ s đến xi Khi đó f [ xi ] max{f [ xk ]  d ( xk , xi ) : ( xk , xi )  E} Một điều lý thú là thay vì tính toán trên các cung ngược (các cung tới xi ) đồ thị theo cách tư truyền thống qui hoạch động, chúng ta sửa (update) theo các cung xuôi (đây là đặc điểm chính thực qui hoạch động trên DAG vì nói chung xây dựng các cung ngược là vấn đề khá phức tạp): PROCEDURE DuongDiMax For i  {1, ,n} f[i]=- For i  {1, ,n} u=x[i] if (u=s) f[u]=0 35 (36) if (f[u]<>-) For v  Ke(u) if f[v]<f[u]+d(u,v) then f[v]=f[u]+d(u,v) Hoàn toàn tương tự ta có thể tìm đường ngắn Bài toán 2:Đếm số đường từ đỉnh s tới đỉnh t? Gọi f [ xi ] là số đường từ s đến xi ta có công thức f [ xi ]  {f [ xk ] : ( xk , xi )  E} Và lần ta có chương trình qui hoạch động tương tự trên: PROCEDURE SoDuongDi For i  {1, ,n} f[i]=0 For i  {1, ,n} u=x[i] if (u=s) f[u]=1 if (f[u]<>-) For v  Ke(u) f[v]=f[v]+f[u] Hai bài toán trên là hai bài toán các bài toán qui hoạch động trên đồ thị có hướng Một lần nhắc lại điều đặc biệt qui hoạch động trên đồ thị có hướng là ta tính toán theo cung đồ thị, ta thực việc sửa (update) nhãn thay vì tính max, tính đếm qui hoạch động thông thường (lý đơn giản là xây dựng đồ thị ngược nói chung là khá phức tạp và tốn kém) II-MỘT SỐ BÀI TẬP MINH HỌA 36 (37) Bài tập (VOI 2008): Nhảy lò cò là trò chơi dân gian Việt Nam Người trên hành tinh X thích trò chơi này và họ đã cải biên trò chơi này sau: Trên mặt phẳng vẽ n vòng tròn đánh số từ đến n Tại vòng tròn i người ta điền số nguyên dương Hai số trên hai vòng tròn tùy ý không thiết phải khác Tiếp đến người ta vẽ các mũi tên, mũi tên hướng từ vòng tròn đến vòng tròn khác Quy tắc vẽ mũi tên là: Nếu có ba số ai, aj, ak thỏa mãn ak = + aj thì vẽ mũi tên hướng từ vòng tròn i đến vòng tròn k và mũi tên hướng từ vòng tròn j đến vòng tròn k Người chơi di chuyển từ vòng tròn đến vòng tròn khác có mũi tên xuất phát từ số các vòng tròn, di chyển theo cách mũi tên đã vẽ để đến các vòng tròn khác Người thắng là người tìm cách di chuyển qua nhiều vòng tròn Yêu cầu:Hãy xác định xem trò chơi mô tả trên, nhiều có thể di chuyển qua bao nhiêu vòng tròn Như phần trước đã nhận xét, coi vòng tròn là đỉnh đồ thị Hai vòng tròn kề có thể nhảy trực tiếp đến thì ta có DAG và bài toán qui tìm đường dài trên đồ thị này Một điểm cần lưu ý là để chương trình chạy đạt yêu cầu thì điều kiện j  Ke(i) cần thực việc tìm kiếm nhị phân để kiểm tra Bài tập 2: Một ông chủ có cái máy cày cho thuê, có N người nông dân đăng ký thuê máy cày Người thứ i muốn thuê máy thời điểm s[i] đến hết thời điểm t[i] Giá thuê máy cày đơn vị thời gian đồng, cho người thứ i thuê ông chủ có thể thu t[i]-s[i]+1 đồng Tại thời điểm máy có nhiều người sử dụng Yêu cầu: Tính số tiền nhiều có thể thu Input: - Dòng đầu là số nguyên N (N<=100) 37 (38) - N dòng sau, dòng số nguyên thể số s[i], t[i] (0<=s[i]<=t[i]<=109) Output: - Gồm dòng chứa số là số tiền lớn có thể thu Ta xây dựng đồ thị sau: Tập đỉnh V={(i,j): với ý nghĩa là máy thứ làm công việc cuối cùng là i và máy thứ hai làm công việc cuối cùng là j} Tập cung E={(i,j)-(i,k): sau làm j máy thứ làm công việc k và (i,j)-(k,j) sau làm i máy thứ làm k} Dễ thấy bài toán qui tìm đường dài từ đỉnh (0,0) Đây là DAG và xếp topo tự nhiên là xếp theo thời gian kết thúc thuê máy tăng dần Do ta hoàn toàn có thể sử dụng mô hình bài toán để giải quyết: Bài tập 3: Cho đồ thị có hướng N đỉnh (N≤16) đó các cạnh có trọng số Hãy tìm đường Haminton (đường qua tất các đỉnh) ngắn nhất? Ta xây dựng đồ thị đó đỉnh là cặp gồm dãy bit ( b1 , b2 , , bn ) với bi 1 đỉnh i đã qua còn bi 0 đỉnh i chưa qua và đỉnh u thể đỉnh cuối cùng trên hành trình là u Như đỉnh là cặp (x, u) với x là số nguyên thể dãy bit trên Đỉnh (x, u) đến (y,v) bit v x và bít v y (các bit khác trùng nhau) và u đến v Dễ thấy đồ thị xây dựng trên là DAG với xếp topo tự nhiên là xếp các đỉnh (x, u) theo số lượng bit x tăng dần Khi đó trên xếp topo này các đỉnh chia thành lớp (x có bit 1, x có bit 1, , x có n bit 1) 38 (39) và ta có thể sử dụng mô hình bài toán (tìm đường dài từ tập đinh có bit đến tập đỉnh có n bít 1) với chút cải tiến là kết hợp với hàng đợi III-CÁC ĐỒ THỊ CÓ HƯỚNG KHÔNG CÓ CHU TRÌNH CẢM SINH Khi dạy học sinh các thuật toán duyệt đồ thị ưu tiên chiều rộng, duyệt đồ thị ưu tiên chiều sâu, tìm đường ngắn trên đồ thị không có chu trình âm, cần phải nhấn mạnh đến các sản phẩm các thuật toán này Một điều thú vị là có nhiều sản phẩm là đồ thị phận có hướng không có chu trình mà tôi tạm gọi là các đồ thị có hướng không có chu trình cảm sinh Có nhiều bài tập đồ thị các kỳ thi gần đây sử dụng các đồ thị phận này DAG đường ít cạnh Khi thực duyệt đồ thị ưu tiên chiều rộng (BFS) từ đỉnh s ta gọi d[i] là độ dài đường ít cạnh từ s đến i (d[i]= không có đường từ s đến i) Xây dựng đồ thị phận G'=(V',E') sau:  V'  V  E' = {(u,v)  E: d[v]=d[u]+1} Đồ thị này còn gọi là đồ thị đường ít cạnh vì tất các đường ngắn (theo nghĩa ít cạnh nhất) qua các cung đồ thị này Dễ dàng nhận thấy G' là DAG và đặc biệt, xếp topo tự nhiên nó là xếp theo giá trị d[i] tăng dần Nó chính là thứ tự các đỉnh đưa vào/lấy khỏi hàng đợi phép duyệt BFS (điều này khiến cho ta không phải thực phép sort đầy đủ nữa) Chính xác hơn, gọi x1 , x2, , x p là các đỉnh theo thứ tự đưa vào hàng đợi thì ta có xếp topo trên G' Với đồ thị G' ta có thể xây dựng số bài toán mở rộng cho BFS như: Bài toán 3: Hãy tìm đường ngắn (ít cạnh nhất) từ đỉnh s đến đỉnh t Nếu có nhiều đường thì tìm đường có:  Giá thành nhỏ nhất/lớn  Số đỉnh qua nhiều nhất/ít 39 (40) Để giải bài toán 3, trước tiên chúng ta thực BFS để xây dựng đồ thị G' sau đó trên G' ta giải bài toán tìm đường ngắn nhất/dài dựa trên xếp topo nó (bài toán 1) Bài toán 4:Hãy đếm số đường ngắn từ s đến t? Đây chính là bài toán (đếm số đường đi) trên đồ thị G' với xếp topo Bài tập (VOI 2009): Trong mạng xã hội, trang web tổ chức trên máy tính thành viên và cung cấp dịch vụ truy nhập tới số trang web khác Để truy nhập tới trang web nào đó không có danh mục kết nối trực tiếp mình, người dùng phải truy nhập tới trang web khác có kết nối với mình, dựa vào danh mục dịch vụ trang web này để chuyển tới trang web khác theo tùy chọn, tới trang web mình cần Thời gian để truy nhập tới trang web phụ thuộc chủ yếu và số lần mở trang web quá trình truy nhập Như vậy, người dùng cần chủ động chọn lộ trình truy nhập hợp lí Sau thời gian làm việc trên mạng, Sáng - thành viên nhiệt thành đã tích lũy kinh nghiệm, tạo sở liệu, cho biết từ trang web có thể tới trang web nào mạng Trong sở liệu, các trang web đánh số từ đến n và có m ghi, ghi có dạng cặp có thứ tự (u, v) cho biết trang web u có kết nối tới trang web v ( ≤ u, v ≤ n, u ≠ v) Cơ sở liệu chưa chuẩn hóa, vì có thể chứa các cặp (u, v) giống Trang web Sáng có số hiệu là s Dựa vào sở liệu, Sáng có thể xác định lộ trình truy nhập nhanh (tức là số lần phải mở trang web là ít nhất) từ trang web s tới trang web u bất kì Tuy vậy, mạng xã hội, chuyện có thể xảy ra: khu vực nào đó bị điện, máy thành viên bị hỏng, trang web đó bị đóng để nâng cấp, Kết là vài trang web nào đó có thể tạm thời không hoạt động Như vậy, từ s có ít hai lộ trình nhanh khác tới u thì khả thực truy nhập cách nhanh tới u là lớn so với trang web có lộ trình nhanh Hai lộ trình gọi là khác có ít trang web có lộ trình này mà không có lộ trình hai lộ trình cùng qua trang web theo các trình tự khác Những trang web mà từ s tới đó có ít là hai lộ trình nhanh 40 (41) khác gọi là ổn định s Trang web mà từ s không có lộ trình tới nó là không ổn định s Yêu cầu: Cho các số nguyên dương n, m, s và m cặp số (u, v) xác định từ u có thể kết nối trực tiếp tới v Hãy xác định số lượng trang web ổn định s Dữ liệu:  Dòng đầu tiên chứa số nguyên n, m và s (2 ≤ 10000, ≤ m ≤ 50000, ≤ s ≤ n)  Mỗi dòng m dòng chứa số nguyên u và v (1 ≤ u, v ≤ n, u ≠ v) Các số trên dòng ghi cách ít dấu cách Kết quả: Một số nguyên - số trang web ổn định s Bài toán trên là bài toán đếm các đỉnh có ít hai đường ngắn từ s Phương pháp giải nó là bài toán (với lưu ý là ta không thực đếm mà cần phân biệt các đỉnh có 0, 1, đường ngắn từ s) DAG đường ngắn Các thuật toán Dijkstra, Ford_bellman tìm đường ngắn từ đỉnh s cho mảng dist[i] là khoảng cách ngắn từ đỉnh s đến đỉnh i (dist[i]= không có đường từ s đến i) Tương tự trên, sau có mảng dist[i] ta có đồ thị phận G'=(V',E') sau:  V'  V  E'={(u,v)E: dist[v]=dist[u]+L(u,v)} G' là DAG, DAG này là DAG đường ngắn Nếu sử dụng thuật toán Dijkstra thì xếp topo trên DAG này là thứ tự lấy các đỉnh khỏi hàng đợi ưu tiên, còn sử dụng Ford_Bellman thì ta phải thực phép sort trên mảng dist Cũng DAG đường ít cạnh nhất, chúng ta có số bài toán dựa trên DAG này giống bài toán 3, bài toán Dưới đây là số ví dụ điển hình: Bài tập (VOI 2007): 41 (42) Trên mạng lưới giao thông có n nút, các nút đánh số từ đến n và hai nút có không quá đường nối trực tiếp (đường nối trực tiếp là đường hai chiều) Ta gọi đường từ nút s đến nút t là dãy các nút và các đường nối trực tiếp có dạng: s = u1, e1, u2, , ui, ei, ui+1, , uk-1, ek-1, uk = t, đó u1, u2, …, uk là các nút mạng lưới giao thông, ei là đường nối trực tiếp nút ui và ui+1 (không có nút uj nào xuất nhiều lần dãy trên, j = 1, 2, …, k) Biết mạng lưới giao thông xét luôn có ít đường từ nút đến nút n Một robot chứa đầy bình với w đơn vị lượng, cần từ trạm cứu hoả đặt nút đến nơi xảy hoả hoạn nút n, thời gian ít có thể Thời gian và chi phí lượng để robot trên đường nối trực tiếp từ nút i đến nút j tương ứng là tij và cij (1 ≤ i, j ≤ n) Robot có thể trên đường nối trực tiếp từ nút i đến nút j lượng còn lại bình chứa không ít cij (1 ≤ i, j ≤ n) Nếu robot đến nút có trạm tiếp lượng (một nút có thể có không có trạm tiếp lượng) thì nó tự động nạp đầy lượng vào bình chứa với thời gian nạp coi không đáng kể Yêu cầu: Hãy xác định giá trị w nhỏ để robot trên đường từ nút đến nút n thời gian ít Input  Dòng đầu tiên chứa số nguyên dương n (2 ≤ n ≤ 500);  Dòng thứ hai chứa n số, đó số thứ j tương ứng nút j có không có trạm tiếp lượng (j = 1, 2, …, n);  Dòng thứ ba chứa số nguyên dương m (m ≤ 30000) là số đường nối trực tiếp có mạng lưới giao thông;  Dòng thứ k số m dòng chứa số nguyên dương i, j, tij, cij (tij, cij ≤ 10000) mô tả đường nối trực tiếp từ nút i đến nút j, thời gian và chi phí lượng tương ứng Hai số liên tiếp trên dòng file liệu cách ít dấu cách 42 (43) Output: Ghi số nguyên dương w tìm Trước tiên sử dụng thuật toán Dijkstra chúng ta tìm DAG đường ngắn Một lần chú ý xếp topo trên DAG này chính là thứ tự lấy các đỉnh khỏi hàng đợi ưu tiên Trên DAG đường ngắn này ta giải bài toán tìm lượng tối thiểu Kỹ thuật dùng đây có thể là tìm kiếm nhị phân và ta đến bài toán "Cho lượng x, hỏi với lượng này có thể đến n hay không?" bài toán này hoàn toàn giải qui hoạch động Bài tập (IOICamp maraton 2006): Ngày 27/11 tới là ngày tổ chức thi học kỳ I trường ĐH BK Là sinh viên năm thứ nhất, Hiếu không muốn vì muộn mà gặp trục trặc phòng thi nên đã chuẩn bị khá kỹ càng Chỉ còn lại công việc khá gay go là Hiếu không biết đường nào tới trường là nhanh Thường ngày Hiếu không quan tâm tới vấn đề này cho nên bây Hiếu không biết phải làm Bản đồ thành phố là gồm có N nút giao thông và M đường nối các nút giao thông này Có loại đường là đường chiều và đường chiều Độ dài đường là số nguyên dương Nhà Hiếu nút giao thông còn trường ĐH BK nút giao thông N Vì lộ trình đường từ nhà Hiếu tới trường có thể gặp nhiều yếu tố khác là gặp nhiều đèn đỏ , qua công trường xây dựng, phải giảm tốc độ cho nên Hiếu muốn biết là có tất bao nhiêu lộ trình ngắn từ nhà tới trường Bạn hãy lập trình giúp Hiếu giải bài toán khó này Input  Dòng thứ ghi hai số nguyên N và M  M dòng tiếp theo, dòng ghi số nguyên dương K, U, V, L Trong đó: o K = có nghĩa là có đường chiều từ U đến V với độ dài L o K = có nghìa là có đường hai chiều U và V với độ dài L 43 (44) Output: Ghi hai số là độ dài đường ngắn nhấn và số lượng đường ngắn Biết số lượng đường ngắn không vượt quá phạm vì int64 pascal hay long long C++ Đầu tiên chúng ta xây dựng DAG đường ngắn (bằng thuật toán Dijkstra) Bài toán qui bài đếm số đường trên DAG này Đây chính là bài toán Bài tập (IOICAMP4): Theo thống kê cho biết mức độ tăng trưởng kinh tế nước Peace năm 2006 đáng khả quan Cả nước có tổng cộng N thành phố lớn nhỏ đánh số từ đến N phát triển khá đồng Giữa N thành phố này là mạng lưới gồm M đường hai chiều, tuyến đường nối N thành phố cho không có thành phố nào nối quá tuyến đường Trong N thành phố này thì thành phố và thành phố N là trung tâm kinh tế lớn nước và hệ thống đường đảm bảo luôn có ít cách từ thành phố đến thành phố N Tuy nhiên,cả trung tâm này có dấu hiệu quá tải mật độ dân số Vì vậy, đức vua Peaceful định chọn thêm thành phố để đầu tư thành trung tâm kinh tế thứ ba Thành phố này tạm ngưng hoạt động thường nhật, luồng lưu thông vào để tiến hành nâng cấp sở hạ tầng Nhưng thời gian sửa chữa ấy, phải bảo đảm đường ngắn từ thành phố đến thành phố N không bị thay đổi, không kinh tế quốc gia bị trì trệ Vị trí và đường nối N thành phố mô tả đồ thị N đỉnh M cạnh Hãy giúp nhà vua đếm số lượng thành phố có thể chọn làm trung tâm kinh tế thứ ba cho thành phố chọn thỏa mãn các điều kiện trên Input  Dòng đầu tiên ghi số nguyên dương N và M là số thành phố và số tuyến đường  Dòng thứ i số M dòng ghi số nguyên dương xi, yi và di với ý nghĩa tuyến đường thứ i có độ dài di và nối thành phố xi, yi Output: 44 (45)  Dòng đầu tiên ghi số tự nhiên S là số lượng các thành phố có thể chọn làm trung tâm kinh tế thứ ba  S dòng tiếp theo, dòng ghi số nguyên dương là số thứ tự thành phố chọn ( In theo thứ tự tăng dần ) Một thành phố chọn là thành phố mà rút nó khỏi đồ thị không ảnh hưởng đến số lượng đường ngắn từ đến n Đặt f[u] là số lượng đường ngắn từ đến u và g[u] là số lượng đường ngắn từ u đến n (hai mảng này có thể tính trên các DAG đường ngắn đồ thị xuôi và đồ thị ngược) u là thành phố chọn f[u]*g[u]<f[n] DAG Liên thông mạnh Khi tìm thành phần liên thông mạnh sản phẩm quan trọng là đồ thị các thành phần liên thông mạnh đó đỉnh đồ thị này là thành phần liên thông mạnh đồ thị ban đầu và thành phần liên thông A kề với thành phần liên thông B có cung đồ thị ban đầu từ đỉnh A đến đỉnh B Dễ nhận thấy đồ thị các thành phần liên thông mạnh là DAG (vì không ta có thể mở rộng thành phần liên thông mạnh nào đó) đây là DAG liên thông mạnh DAG liên thông mạnh có xếp topo tự nhiên là thứ tự tìm thấy các thành phần liên thông mạnh thuật toán Tarjan (thành phần liên thông mạnh nào tìm thấy trước thì xếp trước, thành phần liên thông mạnh nào tìm thấy sau thì xếp sau) DAG liên thông mạnh phải xây dựng riêng (bằng vòng lặp duyệt qua các cung đồ thị cũ) Hơn nữa, ta cần lưu thêm các thông tin đỉnh đồ thị này Bài tập 8: Tất các đường thành phố Siruseri là chiều Theo luật quốc gia này, giao lộ phải có máy ATM Điều đáng ngạc nhiên là các 45 (46) cửa hàng chơi điện tử nằm các giao lộ, nhiên, không phải giao lộ nào có cửa hàng chơi điện tử Banditji là tên trộm tiếng Hắn định làm vụ động trời: khoắng tiền các máy ATM trên đường đi, sau đó ghé vào cửa hàng chơi điện tử để thư giản Nhờ có mạng lưới thông tin rộng rãi, Banditji biết số tiền có máy ATM ngày hôm đó Xuất phát từ trung tâm, tên trộm lái xe dọc theo các phố, vét tiền các ATM gặp trên đường Banditji có thể lại nhiều lần trên số đoạn phố, không thu gì thêm từ các ATM đã bị khoắng trước đó Lộ trình Banditji phải kết thúc giao lộ có cửa hàng chơi điện tử Banditji biết cách vạch lộ trình để tổng số tiền trộm là lớn Yêu cầu: Cho biết n – số giao lộ, m – số đoạn đường nối giao lộ, p – số giao lộ có cửa hàng chơi điện tử và các nơi có cửa hàng, – số tiền ATM đặt giao lộ i, s – giao lộ trung tâm Hãy xác định tổng số lượng tiền bị trộm (n, m ≤ 500 000, ≤ ≤ 000) Dữ liệu: Vào từ file văn ATM.INP:  Dòng đầu tiên chứa số nguyên n và m,  Mỗi dòng m dòng chứa số nguyên u và v xác định đường từ giao lộ u tới giao lộ v,  Dòng thứ i n dòng chứa số nguyên ai,  Dòng thứ n+m+2 chứa số nguyên s và p,  Dòng cuối cùng chứa p số nguyên xác định các giao lộ có cửa hàng chơi điện tử Kết quả: Đưa file văn ATM.OUT số nguyên – số tiền bị trộm Sử dụng Tarjan chúng ta tìm DAG các thành phần liên thông mạnh Với đỉnh (tức là thành phần liên thông mạnh) chúng ta lưu hai thông tin: tổng số tiền các trạm ATM và số cửa hàng điện tử Bài toán trở thành tìm đường có tổng tiền lớn đến các đỉnh có số cửa hàng điện tử lớn không Do là DAG và có xếp topo nên điều này có thể làm qui hoạch động tương tự trên * * * 46 (47) Có thể thấy DAG cho lớp bài toán khá phong phú và đa dạng trên đồ thị Các DAG cảm sinh dựa trên các thuật toán BFS, Dijkstra, Tarjan có lẽ là DAG thú vị Điều làm cho việc giải các bài toán trên các DAG này là dễ dàng chính là các xếp topo tự nhiên mà các thuật toán mang lại Dưới quan điểm dạy học thì khai thác hết các kết các thuật toán là thói quen tốt cần xây dựng cho học sinh là kỹ rèn luyện Nếu các em có kỹ này thì việc áp dụng các thuật toán cách uyển chuyển là hệ hiển nhiên CHUYÊN ĐỀ ĐƯỜNG ĐI NGẮN NHẤT TRÊN ĐỒ THỊ 47 (48) A, MỞ ĐẦU Lý chọn đề tài Lý thuyết đồ thị là lĩnh vực phát triển từ lâu, nhiều nhà khoa học quan tâm nghiên cứu nó có vai trò quan trọng nhiều lĩnh vực Trong Tin học lý thuyết đồ thị ứng dụng cách rộng rãi có nhiều thuật toán nghiên cứu và ứng dụng Trong chương trình môn Tin học THPT chuyên phần lý thuyết đồ thị nói chung và các thuật toán tìm đường ngắn trên đồ thị là nội dung quan trọng, các kỳ thi học sinh giỏi xuất nhiều các bài toán liên quan đến việc tìm đường ngắn trên đồ thị Tuy nhiên qua trình giảng dạy, học sinh còn khó khăn việc phân tích bài toán để có thể áp dụng thuật toán và cài đặt giải bài toán Vì chuyên đề này để giúp học sinh có cái nhìn tổng quan các thuật toán tìm đường ngắn trên đồ thị A NỘI DUNG I, Giới thiệu bài toán đường ngắn - Trong thực tế có nhiều các bài toán chẳng hạn mạng lưới giao thông nối các Thành Phố với nhau, mạng lưới các đường bay nối các nước với người ta không quan tâm tìm đường các địa điểm với mà phải lựa chọn hành trình cho tiết kiệm chi phí ( chi phí có thể là thời gian, tiền bạc, khoảng cách…) Khi đó người ta gán cho cạnh đồ thị giá trị phản ánh chi phí qua cạnh đó và cố gắng tìm hành trình qua các cạnh với tổng chi phí thấp - Ta xét đồ thị có hướng G = (V, E) với các cung gán trọng số (trọng số đây là chi phí ) Nếu hai đỉnh u, v không có cạnh nối thì ta có thể thêm vào cạnh “giả” với trọng số aij = + Khi đó đồ thị G có thể giả thiết là đồ thị đầy đủ - Nếu dãy v0, v1, , vp là đường trên G thì độ dài nó định nghĩa là p tổng các trọng số trên các cung nó: ∑ a(v i − , v i ) i=1 - Bài toán đặt là tìm đường có độ dài nhỏ từ đỉnh xuất phát sV đến đỉnh đích tV Đường gọi là đường ngắn từ s đến t và độ dài nó ta còn gọi là khoảng cách từ s đến t, kí hiệu d(s, t) 48 (49) - Nhận xét: + Khoảng cách hai đỉnh đồ thị có thể là số âm + Nếu không tồn đường từ s đến t thì ta đặt d(s, t) = + + Nếu đồ thị, chu trình có độ dài dương thì đường ngắn không có đỉnh nào bị lặp lại Đường không có đỉnh lặp lại gọi là đường Còn đồ thị có chứa chu trình với độ dài âm (gọi là chu trình âm) thì khoảng cách số cặp đỉnh nào đó đồ thị là không xác định, vì cách vòng theo chu trình này số lần đủ lớn, ta có thể đường các đỉnh này có độ dài nhỏ bất kì số thực nào cho trước Trong trường hợp ta có thể đặt vấn đề tìm đường ngắn + Trong thực tế, bài toán tìm đường ngắn hai đỉnh đồ thị liên thông có ý nghĩa to lớn Nhiều bài toán có thể dẫn bài toán trên Ví dụ bài toán chọn hành trình tiết kiệm (theo tiêu chuẩn khoảng cách thời gian chi phí) trên mạng giao thông đường bộ, đường thuỷ đường không Bài toán lập lịch thi công các công đoạn công trình lớn, bài toán lựa chọn đường truyền tin với chi phí nhỏ mạng thông tin, Hiện có nhiều phương pháp để giải bài toán trên Trong bài này ta xét các giải thuật xây dựng trên sở lý thuyết đồ thị tỏ là hiệu cao II, Đường ngắn xuất phát từ đỉnh 1, Bài toán tìm đường ngắn xuất phát từ đỉnh phát biểu sau : Cho đồ thị có trọng số G=(V,E,w) hãy tìm đường ngắn từ đỉnh xuất phát s đến các đỉnh còn lại đồ thị Độ dài đường từ đỉnh s đến đỉnh t kí hiệu là δ(s,t) gọi là khoảng cách từ s tới t không tồn khoảng cách từ s tới t thì ta đặt khoảng cách đó là + ∞ 2,Giải thuật Ford-Bellman - Thuật toán Ford – Bellman có thể dùng để tìm đường ngắn xuất phát từ đỉnh s thuộc V trường hợp đồ thị G=(V,E,w) không có chu trình âm thuật toán sau: + Gọi d[v] là khoảng cách từ đỉnh s đến đỉnh vV, d[v]=0, d[t]=+ ∞ Sau đó ta thực phép co tức là phát d[u] + a[u, v] < d[v] thì cận trên d[v] tốt lên d[v] := d[u] + a[u, v] Quá trình trên kết thúc nào ta không 49 (50) thể làm tốt thêm cận trên nào Khi đó giá trị d[v] cho khoảng cách từ s đến v Nói riêng d[t] là độ dài ngắn hai đỉnh s và t Cài đặt thuật toán Procedure Ford_Bellman ; Begin For i := to n begin d [i]:=maxint ; tr[i]:=maxint ; end ; d[s]:=0; Repeat Ok:=true; For i:=1 to n if d[i]<>maxint then for j:=1 to n if (a[i,j]<>0)and(d[i]+a[i,j]<d[j]) then begin ok:=false; d[j]:=d[i]+a[i,j]; tr[j]:=i; end; until ok ; 50 (51) Nhận xét: - Việc chứng minh tính đúng đắn giải thuật trên dựa trên sở nguyên lý tối ưu quy hoạch động - Độ phức tạp tính toán giải thuật Ford-Bellman là O(n3) - Thực chất thuật toán này là thuật toán Quy Hoạch Động đó , d[i] là mảng độ dài ngắn từ s đến i t là đỉnh cần thiết thì d[t] là độ dài cần tìm Còn muốn lưu lại đường thì chúng ta dùng mảng Tr [i] để ngược lại 3, Thuật toán Dijkstra Trong trường hợp đồ thị G=(V,E,w) có trọng số trên các cung không âm thuật toán Dijkstra đề cập đây hoạt động hiệu nhiều so với thuật toán Ford – Bellman Thuật toán Dijkstra sau: Bước 1: Khởi tạo Với đỉnh v  V , ta gọi d[v] là độ dài đường từ s tới v ban đầu khởi tạo d[v]=0, d[t]=+ ∞ v s Nhẵn đỉnh có hai trạng thái tự hay cố định, nhẵn tự có nghĩa là có thể tối ưu nữa, nhẵn cố định d[v] là đường ngắn từ s tới v nên không thể tối ưu Ta dùng thêm mảng Free[] để đánh dấu d[v] là tự thì Free[v]=True, ngược lại Free[v]=Flase Ban đầu các nhẵn tự Bước 2: Lặp Bước lặp gồm hai thao tác : - Cố định nhẵn: chọn các đỉnh có nhẵn tự lấy đỉnh u có d[u] nhỏ và cố định d[u] - Sửa nhẵn: dùng đỉnh u để xét tất các đỉnh v và sửa lại các nhẵn d[v] theo công thức sau: d[v]= (d[v], d[u]+c[u,v]) Bước lặp kết thúc mà đỉnh t( đỉnh đích) đã cố định nhẵn Bước 3: Kết hợp với lưu vết đường trên bước sửa nhẵn, thông báo đường ngắn tìm cho biết không tồn đường d[t]=+ ∞ Cài đặt thuật toán: Const MAX_N = 100; 51 (52) FI = 'dijkstra.inp'; FO = 'dijkstra.out'; Var n, nU, s, t : integer; a : array[1 MAX_N, MAX_N] of integer; d, tr, U : array[1 MAX_N] of integer; f : text; Procedure Doc; Var i, j : integer; Begin assign(f, FI); reset(f); read(f, n, s, t); for i := to n for j := to n read(f, a[i, j]); close(f); End; Procedure Khoi_tao; Var i : integer; Begin for i := to n begin tr[i] := s; d[i] := a[s, i]; end; for i := to n U[i] := i; U[s] := U[n]; nU := n - 1; End; Function Co_dinh_nhan : integer; Var i, j, p : integer; Begin { Tim p } i := 1; for j := to nU if d[U[i]] > d[U[j]] then i := j; p := U[i]; 52 (53) { Loai p khoi U } U[i] := U[nU]; nU := nU - 1; Co_dinh_nhan := p; End; Procedure Sua_nhan(p : integer); Var i, x : integer; Begin for i := to nU begin x := U[i]; if d[x] > d[p] + a[p, x] then begin tr[x] := p; d[x] := d[p] + a[p, x]; end; end; End; Procedure Print(i : integer); Begin if i = s then begin writeln(f, d[t]); write(f, s); exit; end; Print(tr[i]); write(f, ' ', i); End; Procedure Ghi; Begin assign(f, FO); rewrite(f); Print(t); close(f); End; Procedure Dijkstra; Var p : integer; Begin Khoi_tao; repeat 53 (54) p := Co_dinh_nhan; Sua_nhan(p); until p = t; End; Begin Doc; Dijkstra; Ghi; End 4, Thuật toán Dijkstra với cấu trúc Heap Cấu trúc Heap và số phép xử lí trên Heap * Mô tả Heap: Heap mô tả cây nhị phân có cấu trúc cho giá trị khoá nút không vượt quá giá trị khoá hai nút nó (suy giá trị khoá gốc Heap là nhỏ nhất) * Hai phép xử lí trên Heap Phép cập nhật Heap Vấn đề: Giả sử nút v có giá trị khoá nhỏ đi, cần chuyển nút v đến vị trí trên Heap để bảo toàn cấu trúc Heap Giải quyết: + Nếu nút v chưa có Heap thì tạo thêm nút v thành nút cuối cùng Heap (hình 1) + Chuyển nút v từ vị trí đến vị trí thích hợp cách tìm đường ngược từ vị trí v phía gốc qua các nút cha có giá trị khoá lớn giá trị khoá v Trên đường dồn nút cha xuống nút con, nút cha cuối cùng chính là vị trí nút v (hình 2) Chú ý: trên cây nhị phân, đánh số các nút từ gốc đến lá và từ trái sang phải thì dễ thấy: biết số hiệu nút cha là i có thể suy số hiệu hai nút là 2*i và 2*i+1, ngược lại số hiệu nút là j thì số hiệu nút cha là j div 54 (55) - Phép loại bỏ gốc Heap Vấn đề: Giả sử cần loại bỏ nút gốc khỏi Heap, hãy xếp lại Heap (gọi là phép vun đống) Giải quyết: + Tìm đường từ gốc phía lá, qua các nút có giá trị khoá nhỏ hai nút gặp lá + Trên dọc đường ấy, kéo nút lên vị trí nút cha nó Ví dụ hình vẽ bỏ nút gốc có khoá 1, ta kéo nút lên vị trí nút cha trên đường qua các nút có giá trị khoá là 1, 2, 6, và Heap hình 55 (56) Thuật toán Dijkstra tổ chức trên cấu trúc Heap (tạm kí hiệu là Dijkstra_Heap) Tổ chức Heap: Heap gồm các nút là các đỉnh i tự (chưa cố định nhãn đường ngắn nhất), với khoá là nhãn đường ngắn từ s đến i là d[i] Nút gốc chính là đỉnh tự có nhãn d[i] nhỏ Mỗi lần lấy nút gốc để cố định nhãn nó và sửa nhãn cho các đỉnh tự khác thì phải thức hai loại xử lí Heap đã nêu (phép cập nhật và phép loại bỏ gốc) Vậy thuật toán Dijkstra tổ chức trên Heap sau: Cập nhật nút Heap (tương ứng với nút s có giá trị khoá 0) Vòng lặp Heap rỗng (không còn nút nào) Begin + Lấy đỉnh u nút gốc Heap (phép loại bỏ gốc Heap) + Nếu u= t thì thoát khỏi vòng lặp + Đánh dấu u là đỉnh đã cố định nhãn + Duyệt danh sách cung kề tìm các cung có đỉnh đầu u, đỉnh cuối là v Nếu v là đỉnh tự và d[v] > d[u] + khoảng cách (u,v) thì Begin Sửa nhãn cho v và ghi nhận đỉnh trước v là u Trên Heap, cập nhật lại nút tương ứng với đỉnh v End; End; * Đánh giá + Thuật toán Dijkstra tổ chức nêu mục Có độ phức tạp thuật toán là O(N2), nên không thể thực trên đồ thị có nhiều đỉnh 56 (57) + Các phép xử lí Heap đã nêu (cập nhật Heap và loại bỏ gốc Heap) cần thực không quá 2.lgM phép so sánh (nếu Heap có M nút) Số M tối đa là N (số đỉnh đồ thị) và ngày càng nhỏ dần (tới 0) Ngoài ra, đồ thị thưa (số cung ít) thì thao tác tìm đỉnh v kề với đỉnh u là không đáng kể ta tổ chức danh sách các cung kề này theo đoạn có đỉnh đầu giống (dạng Forward Star) Do đó trên đồ thị thưa, độ phức tạp Dijkstra_Heap có thể đạt tới O(N k.lgN) đó k không đáng kể so với N + Kết luận: Trên đồ thị nhiều đỉnh ít cung thì Dijkstra_Heap là thực thời gian có thể chấp nhận III, Đường ngắn tất các cặp đỉnh - Thuật toán Floyd Ta có thể giải bài toán tìm đường ngắn tất các cặp đỉnh đồ thị cách sử dụng n lần giải thuật đã Ford –Bellman Dijkstra , đó ta chọn s là các đỉnh đồ thị Khi đó ta thu giải thuật với độ phức tạp là O(n4) (nếu sử dụng giải thuật Ford - Bellman) O(n3) trường hợp đồ thị có trọng số không âm không có chu trình Trong trường hợp tổng quát việc sử dụng giải thuật Ford-Bellman n lần không phải là cách làm tốt Ở đây ta xét giải thuật Floyd giải bài toán trên với độ phức tạp tính toán O(n3) Đầu vào là đồ thị cho ma trận trọng số a[i, j], i, j = 1, 2, , n Đầu : - Ma trận đường ngắn các cặp đỉnh: d[i, j] (i, j = 1, 2, , n) - Ma trận ghi nhận đường tr[i, j] (i, j = 1, 2, , n) đó tr[i, j] ghi nhận đỉnh trước đỉnh j đường ngắn từ i đến j Procedure Floyd; Var i, j, k : integer; Begin { Khởi tạo } for i := to n for j := to n begin d[i, j] := a[i, j]; tr[i, j] := i; end; 57 (58) { Bước lặp } for k := to n for i := to n for j := to n if d[i, j] > d[i, k] + d[k, j] then begin d[i, j] := d[i, k] + d[k, j]; tr[i, j] := tr[k, j]; end; End; 6, Một số bài toán tìm đường ngắn Bài toán 1: Hướng dẫn viên du lịch Ông G là hướng dẫn viên du lịch Công việc ông ta là hướng dẫn vài “tua” du lịch từ thành phố này đến thành phố khác Trên các thành phố này, có vài đường hai chiều nối chúng Mỗi cặp thành phố có đường kết nối có dịch vụ xe buýt chạy hai thành phố này và chạy theo đường nối trực tiếp chúng Mỗi dịch vụ xe buýt có giới hạn lớn lượng khách mà xe buýt có thể trở Ông G có đồ các thành phố và đường nối chúng Ngoài ra, ông ta có thông tin dịch vụ xe buýt các thành phố Ông hiểu ông không thể đưa tất các khách du lịch đến thành phố thăm quan cùng chuyến Lấy ví dụ: Về đồ gồm thành phố, cạnh nối các thành phố biểu thị đường và các số viết trên cạnh cho biết cho biết giới hạn hành khách dịch vụ xe buýt chạy trên tuyến đường đó Bây giờ, ông G muốn đưa 99 khách du lịch từ thành phố đến thành phố Ông ta phải yêu cầu ít là chuyến đi, và lộ trình ông ta nên là – – – Nhưng, Ông G nhận thấy là thật khó để tìm tất lộ trình tốt để cho ông ta có thể đưa tất khách du lịch đến thành phố thăm quan với số chuyến là nhỏ Do mà ông ta cần trợ giúp các bạn Dữ liệu: Vào từ file Tourist.inp - Tệp Tourist.inp chứa hay nhiều trường hợp test 58 (59) - Dòng đầu tiên trường hợp test chứa hai số nguyên N (N ≤ 100) và R mô tả số thành phố và số đường các thành phố - R dòng tiếp theo, dòng chứa số nguyên: C1, C2, P C1, C2 mô tả lộ trình đường từ thành phố C1 đến thành phố C2 và P (P > 1) là giới hạn lớn có thể phục vụ dịch vụ xe buýt hai thành phố Các thành phố đánh dấu số nguyên từ đến N Dòng thứ (R+1) chứa ba số nguyên S, D, T mô tả thành phố khởi hành, thành phố cần đến và số khách du lịch phục vụ Kết quả: Đưa file Tourist.out Ghi số lộ trình nhỏ cần phải qua các thành phố thỏa mãn yêu cầu đề bài Ví 1 2 3 00 dụ: Tourist.inp 4 7 7 10 30 15 10 25 60 40 20 35 20 30 99 Tourist.out Lời giải : Đây là bài toán hay, đòi hỏi các bạn phải nắm vững thuật toán Dijkstra Bài toán này là bài toán biến thể bài toán kinh điển tìm đường ngắn Với đường (u,v) gọi C[u, v] là số người tối đa có thể trên đường đó lần C[u,v]-1 là số khách tối đa có thể trên đường đó lần C[u,v] = tương đương với u và v không có đường nào Gọi D[i] là số khách nhiều có thể lần từ điểm xuất phát đến i Với đỉnh j kề với i, ta cập nhật lại D[j] = min(D[i], C[i, j]) Số khách có thể cùng lúc 59 (60) từ điểm xuất phát tới điểm kết thúc T là D[T] Một chú ý là tính số lần đi, các bạn cần dùng các phép div, mod để tính Chương trình thể thuật toán trên (độ phức tạp: n ) {$R+,Q+} const INP = 'tourist.inp'; OUT = 'tourist.out'; maxn = 100; var fi, fo: text; c: array [1 maxn, maxn] of longint; d: array [1 maxn] of longint; chua: array [1 maxn] of boolean; n, m, s, t, w: longint; procedure open_file; begin assign(fi, INP); reset(fi); assign(fo, OUT); rewrite(fo); end; procedure close_file; begin close(fi); close(fo); end; procedure read_data; var i, u, v, x: longint; begin readln(fi, n, m); for i := to m 60 (61) begin readln(fi, u, v, x); c[u, v] := x - 1; c[v, u] := x - 1; end; readln(fi, s, t, w); end; function min2(x, y: longint): longint; begin if x > y then min2 := y else min2 := x; end; procedure process; var i, max, last: longint; begin fillchar(chua, sizeof(chua), true); fillchar(d, sizeof(d), 0); chua[s] := false; last := s; d[s] := maxlongint; Khởi tạo d[s] = vô cùng hay tất người có thể đến S cùng lúc while chua[t] begin for i := to n {Tìm các đỉnh i kề với last để cập nhật lại} if chua[i] and (d[i] < min2(c[last, i], d[last])) then d[i] := min2(c[last, i], d[last]); max := -1; for i := to n if chua[i] and (d[i] > max) then begin max := d[i]; last := i; 61 (62) end; chua[last] := false; end; end; procedure write_result; begin if w mod d[t] = then writeln(fo, w div d[t]) else writeln(fo, w div d[t] + 1); end; begin open_file; read_data; process; write_result; close_file; end Bài 2: Chuyển Hàng Bản đồ kho hàng hình chữ nhật kích thước mxn chia thành các ô vuông đơn vị (m hàng, n cột: các hàng đánh số từ trên xuống dưới, các cột đánh số từ trái qua phải) Trên các ô vuông đồ có số ký hiệu: - Các ký hiệu # đánh dấu các ô đã có kiện hàng xếp sẵn - Một ký hiệu *: Đánh dấu ô có rôbốt - Một ký hiệu $: Đánh dấu ô chứa kiện hàng cần xếp - Một ký hiệu @: Đánh dấu vị trí mà cần phải xếp kiện hàng vào $ vào ô đó - Các ký hiệu dấu chấm ".": Cho biết ô đó trống Tại môt thời điểm, rô bốt có thể thực số động tác ký hiệu là: - L, R, U, D: Tương ứng với phép di chuyển rô bốt trên đồ: sang trái, sang phải, lên trên, xuống Thực phép di chuyển công - +, − : Chỉ thực rôbốt đứng ô bên cạnh kiện hàng $ Khi thực thao tác +, rôbốt đứng yên và đẩy kiện hàng $ làm kiện hàng này trượt theo hướng đẩy, 62 (63) đến chạm kiện hàng khác tường nhà kho thì dừng lại Khi thực thao tác − , rô bốt kéo kiện hàng $ phía mình và lùi lại ô theo hướng kéo Thực thao tác đẩy kéo C công Rô bốt di chuyển vào ô không chứa kiện hàng kho Hãy tìm cách hướng dẫn rôbốt thực các thao tác để đưa kiện hàng $ vị trí @ cho số công phải dùng là ít Dữ liệu: Vào từ file văn CARGO.INP - Dòng 1: Ghi ba số nguyên dương m, n, C ( m, n ≤ 100; C ≤ 100) - m dòng tiếp theo, dòng thứ i ghi đủ n ký kiệu trên hàng i đồ theo đúng thứ tự trái qua phải Các ký hiệu ghi liền Kết quả: Ghi file văn CARGO.OUT - Dòng 1: Ghi số công cần thực - Dòng 2: Một dãy liên tiếp các ký tự thuộc {L, R, U, D, +, -} thể các động tác cần thực rô bốt Rằng buộc: Luôn có phương án thực yêu cầu đề bài Ví dụ: Phân tích: Thuật toán: Ta dùng thuật toán Dijkstra để giải bài toán này * Mô hình đồ thị: Mỗi đỉnh đồ thị đây gồm trường để phân biệt với các đỉnh khác: - i: Tọa độ dòng kiện hàng (i = m) - j: Tọa độ cột kiện hàng (j = n) - h: Hướng rô bốt đứng cạnh kiện hàng so với kiện hàng (h = 4: Bắc, Đông, 63 (64) Nam, Tây) Bạn có thể quan niệm đỉnh là (i,j,u,v): đó i,j: tọa độ kiện hàng; u,v: tọa độ rôbốt đứng cạnh kiện hàng Nhưng làm lãng phí nhớ và không chạy hết liệu Ta cần biết hướng h rôbốt so với kiện hàng là có thể tính tọa độ rôbốt cách dùng mảng lưu các số ra: dx : array[1 4] of integer = (-1,0,1,0) dy : array[1 4] of integer = (0,1,0,-1) Khi đó, tọa độ(u,v) rôbốt là : u := i + dx[h]; v := j + dy[h]; - Hai đỉnh (i1,j1,h1) và (i2,j2,h2) gọi là kề qua thao tác + - kiện hàng rôbốt đẩy kéo từ ô (i1, j1) đến ô (i2, j2) và rôbốt có thể di chuyển từ ô (u1,v1) đến ô (u2,v2) ( u1 = i1+dx[h1]; v1=j1+dy[h1]; u2=i2+dx[h2]; v2= j2+dy[h2]) Tất nhiên các ô (i2,j2) và (u2,v2) phải không chứa kiện hàng - Trọng số đỉnh là C (số công mà rô bốt đẩy kiện hàng từ ô (i1,j1) đến ô (i2,j2) ) cộng với công để rô bốt di chuyển từ ô (u1,v1) đến ô (u2,v2) Giả sử kiện hàng cần xếp ô (is,js) và hướng rôbốt đứng cạnh kiện hàng là hs và ô cần xếp kiện hàng vào là ô (ie, je) Khi đó, ta dùng thuật toán Dijkstra để tìm đường ngắn từ đỉnh (is,js,hs) đến đỉnh (ie,je,he) với he thuộc {1 4} Mảng d là mảng chiều: d[i,j,h]: Độ dài đường ngắn từ đỉnh xuất phát (is,js,hs) đến đỉnh (i,j,h) Kết bài toán là d[ie,je,he] với he thuộc {1 4} Để ghi nhận phương án ta dùng mảng chiều tr1, tr2, tr3 Khi ta di từ đỉnh (i1,j1,h1) đến đỉnh (i2,j2,h2) thì ta gán: tr1[i2,j2,h2]:= i1; tr2[i2,j2,h2]:= j1; tr3[i2,j2,h2] := h1 để ghi nhận các thông tin: tọa độ dòng, cột, huớng dỉnh trước đỉnh (i2,j2,h2) Từ mảng này ta có thể dễ dàng lần lại đường Bài 3: Ông Ngâu bà Ngâu Hẳn các bạn đã biết ngày "ông Ngâu bà Ngâu" hàng năm, đó là ngày đầy mưa và nước mắt Tuy nhiên, ngày trưước đó, nhà Trời cho phép "ông bà" đưược đoàn tụ Trong vũ trụ vùng thiên hà nơi ông Ngâu bà Ngâu ngự trị có N hành tinh đánh số từ đến N, ông hành tinh Adam (có số hiệu là S) và bà hành tinh Eva 64 (65) (có số hiệu là T) Họ cần tìm đến gặp N hành tinh nối với hệ thống cầu vồng Hai hành tinh có thể không có cầu vồng (hai chiều) nối chúng Họ luôn tới mục tiêu theo đường ngắn Họ với tốc độ không đổi và nhanh tốc độ ánh sáng Điểm gặp mặt họ có thể là hành tinh thứ nào đó Yêu cầu: Hãy tìm hành tinh cho ông Ngâu và bà Ngâu cùng đến đó lúc và thời gian đến là sớm Biết rằng, hai ngưười có thể cùng qua hành tinh họ đến hành tinh đó vào thời điểm khác Dữ liệu Trong file văn ONBANGAU.INP gồm Dòng đầu là số N M S T (N ≤ 100, ≤ S ≠ T ≤ N), M là số cầu vồng M dòng tiếp, dòng gồm hai số I J L thể có cầu vồng nối hai hành tinh I , J và cầu vồng đó có độ dài là L (1 ≤ I ≠ J ≤ N, < L ≤ 200) Kết Ra file văn ONBANGAU.OUT, tính chất cầu vồng, năm khác, nên nhưư không tồn hành tinh nào thoả mãn yêu cầu thì ghi dòng chữ CRY Nếu có nhiều hành tinh thoả mãn thì ghi hành tinh có số nhỏ Ví dụ: Tư tưởng thuật toán: Chúng ta có số nhận xét sau: + Hai hành tinh bất kì nối đến nhiều cầu vồng + Ông Ngâu và bà Ngâu luôn tới mục tiêu theo đường ngắn + Họ với vận tốc không đổi và nhanh vận tốc ánh sáng Thực chất đây là bài toán đồ thị: Từ hành tinh S(nơi ông Ngâu ở) ta xây dựng bảng SP Trong đó SP[i] là đường ngắn từ hành tinh S đến hành tinh i ( ông Ngâu luôn tới mục tiêu theo đường ngắn nhất) SP[i] = tức là không có đường từ hành tinh S đến hành tinh i Tương tự ta xây dựng bảng TP, đó TP[i] là đường ngắn từ hành tinh T đến hành tinh i Và TP[i] = tức là 65 (66) không có đường từ hành tinh T đến hành tinh i Do yêu cầu bài toán là tìm hành tinh khác S và T mà ông bà Ngâu cùng đến lúc và thời gian nhanh Tức là ta tìm hành tinh h cho (h khác S và T) và(SP[h] = ST[h] ) đạt giá trị nhỏ khác Nếu không có hành tinh h nào thoả mãn thì ta thông báo CRY Để xây dựng mảng SP và ST ta có nhiều giải thuật khác đây ta chọn giải thuật Djkstra tìm đường ngắn đỉnh đồ thị Chương trình sau: uses crt; const MaxN = 101; fi= 'ONBANGAU.inp'; fo= 'ONBANGAU.out'; var n,m,s,t,h:byte; a:array[0 MaxN,0 MaxN] of byte; SP,ST,B:array[0 MaxN] of integer; f:text; {* -*thnt* *} procedure Init; var i,u,v,ts:byte; begin fillchar(a,sizeof(a),0); assign(f,fi); reset(f); readln(f,n,m,s,t); for i:=1 to m begin readln(f,u,v,ts); a[u,v]:=ts; a[v,u]:=ts; end; close(f); end; {* -*thnt* *} procedure Build(s:byte); var tt:array[0 maxN] of byte; 66 (67) min,i,vtr:integer; begin fillchar(tt,sizeof(tt),0); fillchar(b,sizeof(b),0); for i:=1 to n b[i] := a[s,i]; tt[s]:=1; min:=0; while <> maxint begin min:=maxint; vtr:=0; for i:=1 to n if tt[i] = then if (b[i] <>0) and (b[i] begin min:=b[i]; vtr:=i; end; if vtr <> then tt[vtr]:=1; for i:=1 to n if (tt[i] = 0) then if a[vtr,i] <> then if (b[vtr] + a[vtr,i] b[i]:=b[vtr] + a[vtr,i]; end; end; {* -*thnt* *} procedure FindWay; var i:integer; begin build(s); {xay dung mang SP } SP:=B; build(t); {xay dung mang ST} ST:=B; h:= 0;{hanh tinh can tim} sp[0]:= Maxint; for i:=1 to n 67 (68) if (SP[i] = ST[i]) then if (SP[i]<>0) then if (SP[i] < SP[h]) then h:=i; end; {* -*thnt* *} procedure ShowWay; begin assign(f,fo); rewrite(f); if h <> then writeln(f,h) else writeln(f,'CRY'); close(f); end; {* -*thnt* *} begin Init; FindWay; ShowWay; end Bài 4: Máy chủ Một Công ty muốn phát triển hệ thống mạng máy tính lớn bao gồm các máy chủ cung cấp nhiều loại hình dịch vụ khác Mạng dược kết nối từ n máy chủ bở các kênh nối cho phép truyền tin hai chiều Hai máy chủ có thể nối trực tiếp với không quá kênh nối Mỗi máy chủ nối trực tiếp với không quá 10 máy chủ khác và hai máy chủ luôn có thể kết nối với thông qua kênh nối trực tiếp chúng thông qua các máy chủ khác Đối với kênh nối ta biết thời gian truyền thông đo mili giấy là số dương Khoảng cách (tính mili giấy) d(u,v) máy chủ u và máy chủ v xác định là độ dài đường ngắn (ứng với thời gian truyền thông trên cạnh) nối u và v trên mạng Để tiện dùng, ta qui ước d(v,v)=0 với v Có số máy chủ cung cấp nhiều dịch vụ các máy chủ khác Vì máy chủ v gán với số tự nhiên r(v) gọi là hạng nó Máy chủ có hạng càng cao càng là máy chủ mạnh Tại máy chủ liệu các máy chủ gần nó thường cất giữ Tuy nhiên không phải máy chủ nào là đáng quan 68 (69) tâm Dữ liệu các máy chủ lân cận với hạng thấp không cất giữ Chính xác hơn, máy chủ w là dáng quan tâm máy chủ v với máy chủ u cho d(v,u)d(v,w) ta có r(u)r(w) Chẳng hạn, tất các các máy chủ với hạng lớn là đáng quan tâm tất các máy chủ Nếu máy chủ v có hạng lớn thì rõ ràng có các máy chủ với hạng lớn là đáng quan tâm v Gọi B(v) là tập các máy chủ đáng quan tâm máy chủ v Ta gọi kích thước liệu các máy chủ đáng quan tâm máy chủ v là B(v) Yêu cầu: Tính tổng kích thước liệu các máy chủ đáng quan tâm tất các máy chủ toàn mạng Biết tổng này có giá trị không vượt quá 30n Dữ liệu: Vào từ file văn SERVER.INP: + Dòng đầu chứa hai số n, m đó n là số máy chủ (1n3000) và m là số kênh nối (1m5n) + Dòng thứ i số n dòng chứa ri (1ri10) là hạng máy chủ i + Tiếp đến là m dòng, dòng chứa thông tin kênh nối bao gồm a, b, t (1t1000, 1a,bn, ab), đó a, b là số hai máy chủ nối kênh xét còn t là thời gian truyền thông kênh (đo mili giây) Kết quả: Ghi file văn SERVER.OUT số nguyên là tổng kích thước liệu các máy chủ đáng quan tâm tất các máy chủ toàn mạng Ví dụ: SERVER.INP 43 1 30 20 20 SERVER.OUT Giải thích Ta có: B(1)={1,2} B(2)={2} B(3)={2,3} B(4)={1,2,3,4} Bài này ta dùng thuật toán Dijkstra để giải chương trình sau const tfi = 'SERVER.INP'; tfo = 'SERVER.OUT'; maxN = 3000; 69 (70) type mang1 mang2 var fi,fo N,M Sol r a d kq Q vt qn kc loai Rank Q1 q1n kcold t1 t2 rmax = = : : : : : : : : : : : : : : : : : : : array[1 10] of integer; array[1 maxN] of integer; text; longint; array[1 maxN] of byte; array[1 maxN] of byte; array[1 maxN] of ^mang1; array[1 maxN] of ^mang1; longint; array[1 maxN] of longint; ^mang2; longint; array[1 maxN] of longint; array[1 maxN] of byte; array[1 maxN] of byte; ^mang2; integer; longint; longint; longint absolute 0:$46c; byte; procedure CapPhat; var i: longint; begin for i:=1 to maxN new(a[i]); for i:=1 to maxN new(d[i]); new(q1); new(vt); end; procedure InitQ; begin qn:=0; 70 (71) end; procedure Put(u: longint); begin inc(qn); q[qn]:=u; end; function Get: longint; var i,u: longint; begin u:=1; for i:=2 to qn 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 i,u,v,w:longint; begin assign(fi,tfi); reset(fi); readln(fi,n,m); for i:=1 to N sol[i]:=0; for i:=1 to N readln(fi,r[i]); for i:=1 to M begin readln(fi,u,v,w); inc(sol[u]); a[u]^[sol[u]]:=v; d[u]^[sol[u]]:=w; inc(sol[v]); a[v]^[sol[v]]:=u; d[v]^[sol[v]]:=w; end; close(fi); rmax:=0; 71 (72) for i:=1 to N if rmax<r[i] then rmax:=r[i]; end; procedure Dijstra(xp: longint); var i,u,v,ll: longint; MaxRank: byte; begin InitQ; kcold:=-1; for i:=1 to N loai[i]:=0; for i:=1 to N rank[i]:=rmax; MaxRank:=0; Put(xp); loai[xp]:=1; kc[xp]:=0; repeat u:=Get; loai[u]:=2; if MaxRank<r[u] then MaxRank:=r[u]; if kc[u]=kcold then begin inc(q1n); q1^[q1n]:=u; end else begin q1n:=1; q1^[1]:=u; end; kcold:=kc[u]; for i:=1 to q1n Rank[q1^[i]]:=MaxRank; if (maxRank<rmax) then for i:=1 to sol[u] begin v:=a[u]^[i]; ll:=d[u]^[i]; if (loai[v]=1) and (kc[v]>kc[u]+ll) then kc[v]:=kc[u]+ll; 72 (73) if loai[v]=0 then begin Loai[v]:=1; kc[v]:=kc[u]+ll; Put(v); end; end; until Qempty; end; function Dem: longint; var k,i: longint; begin k:=0; for i:=1 to N if (Rank[i]<=r[i]) then k:=k+1; Dem:=k; end; procedure Solve; var i,k: longint; begin kq:=0; for i:=1 to N begin Dijstra(i); kq:=kq+Dem; end; end; procedure Inkq; begin assign(fo,tfo); rewrite(fo); writeln(fo,kq); close(fo); 73 (74) end; BEGIN clrscr; t1:=t2; CapPhat; Docdl; Solve; Inkq; writeln('Total time =',(t2-t1)/18.3:0:4,' s'); readln; END BÀI 5: HÀNH TRÌNH TRÊN XE LỬA Lịch hoạt động tuyến đường sắt ngày bao gồm thông tin chuyến tầu có ngày đó Thông tin chuyến tầu bao gồm: - Số hiệu chuyến tầu (được đánh số từ đến M), - Danh sách các ga mà chuyến tầu đó dừng lại, ga bao gồm: + Số hiệu ga (các ga đánh số từ trở đi), + Giờ đến (số thực), + Giờ (số thực) Các giá trị thời gian tính theo đơn vị và viết dạng thập phân (ví dụ 7.5 có nghĩa là 30 phút) Một hành khách đến ga nào đó (gọi là ga tại) cho biết yêu cầu mình gồm: thời điểm mà từ đó có thể được, số hiệu ga cần đến và thời gian tối thiểu cho lần chuyển tầu Nhân viên nhà ga phải trả lời là có đáp ứng yêu cầu khách không? Nếu đáp ứng được, nhân viên nhà ga phải đưa hành trình cần cho khách Hãy giải bài toán trường hợp: a Tìm hành trình đến ga cuối cùng sớm b Tìm hành trình ít phải chuyển tầu Nếu tồn nhiều phương án vậy, hãy tìm phương án đến ga cuối cùng sớm Dữ liệu: File vào gồm các dòng: 74 (75) - Dòng 1: Ghi số theo thứ tự: thời điểm đi, ga tại, ga cần đến và thời gian tối đa cho lần chuyển tầu; - Dòng 2: Ghi số nguyên dương M (M  50); - Dòng i+2 (i = 1, 2, , M): Ghi thông tin chuyến tầu số hiệu i bao gồm: số lượng ga mà chuyến tầu đó dừng lại ( 20), danh sách các ga theo trình tự đến chuyến tầu, đó ga mô tả số theo thứ tự: số hiệu ga, đến, Các số trên cùng dòng ghi cách dấu trắng Kết quả: Trong trường hợp không tìm thấy hành trình thì ghi giá trị Trái lại, ghi hành trình tìm dạng sau: - Dòng đầu ghi S là số hiệu chuyến tầu mà khách bắt đầu đi, - Dòng tiếp ghi T1 là thời điểm chuyến tầu này, - Dòng tiếp ghi K là số lần khách phải chuyển tầu, - K dòng tiếp, dòng ghi thông tin lần chuyển tầu gồm số hiệu ga mà khách phải chuyển tầu và số hiệu chuyến tầu cần tiếp (ghi cách dấu trắng), - Dòng cuối ghi T2 là thời điểm đến ga cuối cùng hành trình Kết câu a và câu b ghi cách dòng trắng Ví dụ: XELUA.INP 1.5 7 9.1 9.5 9.5 6 7.5 2 7.5 7.5 8 8 9.5 10 10 6.5 6.5 9.5 11 11 7 12 12 Chương trình sau Const FI = 'xelua.inp'; FO = 'xelua.out'; MAX_VALUE = 999999999; Var n, nU, ga_di, ga_den, dem : integer; 75 XELUA.OUT 26.02 23 54 10.0 6.5 11.0 (76) t0, t_di, t_cho : real; tau, ga, tr, U : array[0 1001] of integer; d, gio_den, gio_di : array[0 1001] of real; f : text; Procedure Doc; Var m, i, j, k : integer; Begin assign(f, FI); reset(f); read(f, t_di, ga_di, ga_den, t_cho, m); tau[0] := 0; ga[0] := ga_di; gio_den[0] := t_di; gio_di[0] := t_di; n := 0; for i := to m begin read(f, k); for j := to k begin n := n + 1; tau[n] := i; read(f, ga[n], gio_den[n], gio_di[n]); end; end; close(f); End; Function Khoang_cach(i, j : integer) : real; Var t : real; Begin if tau[i] = tau[j] then begin t := gio_di[i] - gio_den[i]; if (j = i+1) and (t <= t_cho) then Khoang_cach := gio_den[j] - gio_den[i] else Khoang_cach := MAX_VALUE; end 76 (77) else if ga[i] = ga[j] then begin t := gio_di[j] - gio_den[i]; if (t >= 0) and (t <= t_cho) then Khoang_cach := t + t0 else Khoang_cach := MAX_VALUE; end else Khoang_cach := MAX_VALUE; End; Procedure Khoi_tao; Var i : integer; Begin for i := to n begin d[i] := Khoang_cach(0, i); tr[i] := 0; end; nU := n; for i := to nU U[i] := i; End; Function Co_dinh_nhan : integer; Var i, j : integer; Begin i := 1; for j := to nU if d[U[j]] < d[U[i]] then i := j; Co_dinh_nhan := U[i]; U[i] := U[nU]; nU := nU - 1; End; Procedure Sua_nhan(p : integer); Var x, i : integer; kc : real; 77 (78) Begin for i := to nU begin x := U[i]; kc := Khoang_cach(p, x); if d[x] > d[p] + kc then begin d[x] := d[p] + kc; tr[x] := p; end; end; End; Procedure Print(i : integer); Begin if tr[i] = then begin writeln(f, tau[i]); writeln(f, gio_di[i] : : 1); writeln(f, dem); exit; end; if tau[tr[i]] <> tau[i] then dem := dem + 1; Print(tr[i]); if tau[tr[i]] <> tau[i] then writeln(f, ga[i], ' ', tau[i]); End; Procedure Ghi; Var dich, i : integer; som_nhat : real; Begin som_nhat := MAX_VALUE; for i := to n if (ga[i] = ga_den) and (d[i] < som_nhat) then begin som_nhat := d[i]; 78 (79) dich := i; end; if som_nhat = MAX_VALUE then writeln(f, 0) else begin dem := 0; Print(dich); writeln(f, gio_den[dich] : : 1); end; writeln(f); End; Procedure Dijktra; Var p : integer; Begin Khoi_tao; while nU > begin p := Co_dinh_nhan; Sua_nhan(p); end; Ghi; End; Procedure Xu_ly; Begin assign(f, fo); rewrite(f); { Cau a } t0 := 0; Dijktra; { Cau b } t0 := 9999; Dijktra; close(f); End; Begin Doc; 79 (80) Xu_ly; End Bài 6: Hội thảo trực tuyến Một trung tâm quản trị mạng gồm N ( 100) cổng truy cập đánh số từ đến N Giữa hai cổng có thể không có đường nối có đường nối trực tiếp và thông tin truyền hai chiều trên đường nối Mạng có M đường nối trực tiếp các cổng và đường nối trực tiếp hai cổng i, j sử dụng thì chi phí truyền tin phải trả là cij ( 32767) Trung tâm nhận hợp đồng tổ chức hội thảo trực tuyến từ địa điểm khác truy cập vào mạng từ cổng Bạn hãy giúp công ty tổ chức sử dụng các đường nối truyền tin cho tổng chi phí là ít có thể Dữ liệu: File vào gồm các dòng: - Dòng đầu tiên ghi hai số N và M; - M dòng tiếp theo, dòng chứa số nguyên dương đó số đầu là số hai cổng, số thứ là chi phí truyền tin trên hai cổng đó; - Dòng cuối cùng chứa số nguyên dương theo thứ tự là số cổng địa điểm hội thảo Kết quả: File gồm: - Dòng đầu ghi xâu ‘No’ không thể tổ chức hội thảo trực tuyến được, ngược lại ghi ‘Yes’ - Nếu tìm cách tổ chức thì dòng thứ hai ghi S là chi phí nhỏ tìm và dòng thứ ba ghi P là số đường nối cần sử dụng P dòng dòng ghi hai số i, j thể đường nối hai cổng i và j sử dụng Các số trên dòng ghi cách dấu cách Ví dụ: NET.INP 12 20 238 243 253 266 352 369 475 NET.OUT Yes 27 12 24 25 56 80 (81) 561 577 684 786 146 Lời giải: Chúng ta thấy chắn đoạn nối đó phải là cây Tức là có cây đồ thị bao lấy ba địa điểm đó Mà cây đó là cây có độ dài nhỏ Vì tồn điểm là trung gian T ( có thể trùng với ba địa điểm đó ) Thì tổng đường truyền từ T đến đỉnh đó phải nhỏ Tức là ta dùng thuật toán Floyd Sau đó tìm đỉnh nào có tổng khoảng cách nhỏ đến ba đỉnh làn nhỏ thì các đường nối đó chính là các đường nối thoả mãn Chương trình Program Hoi_thao_truc_tuyen; Uses crt; Const FI = 'net.inp'; FO = 'net.out'; MAX_N = 100; MAX_VALUE = 999999999; Var n, x, y, z, sum, so_canh : integer; c : array[1 MAX_N, MAX_N] of longint; tr : array[1 MAX_N, MAX_N] of byte; f : text; Procedure Doc; Var m, chi_phi, i, j, k : integer; Begin assign(f, FI); reset(f); readln(f, n, m); for i := to n for j := to n c[i, j] := MAX_VALUE; 81 (82) for k := to m begin readln(f, i, j, chi_phi); c[i, j] := chi_phi; c[j, i] := chi_phi; end; readln(f, x, y, z); close(f); End; Procedure Floyd; Var i, j, k : integer; Begin for i := to n for j := to n tr[i, j] := i; for k := to n for i := to n for j := to n if c[i, j] > c[i, k] + c[k, j] then begin c[i, j] := c[i, k] + c[k, j]; tr[i, j] := tr[k, j]; end; End; Procedure Print(i, j : integer); Begin if i = j then exit; if c[tr[i, j], j] <> -1 then begin so_canh := so_canh + 1; sum := sum + c[tr[i, j], j]; 82 (83) c[tr[i, j], j] := -1; c[j, tr[i, j]] := -1; end; Print(i, tr[i, j]); End; Procedure Ghi; Var min, t, i, j : longint; Begin assign(f, FO); rewrite(f); := MAX_VALUE; for i := to n if > c[x, i] + c[y, i] + c[z, i] then begin t := i; := c[x, i] + c[y, i] + c[z, i]; end; if = MAX_VALUE then write(f, 'No') else begin so_canh := 0; sum := 0; Print(x, t); Print(y, t); Print(z, t); writeln(f, 'Yes'); writeln(f, sum); writeln(f, so_canh); for i := to n for j := i+1 to n if c[i, j] = -1 then writeln(f, i, ' ', j); 83 (84) end; close(f); End; Begin Doc; Floyd; Ghi; End Bài 7: Chợ trung tâm Có N địa điểm dân cư đánh số từ đến N Giữa M cặp địa điểm số N địa điểm nói trên có tuyến đường nối chúng Cần xây dựng trung tâm dịch vụ tổng hợp địa điểm trùng với địa điểm dân cư, cho tổng khoảng cách từ trung tâm dịch vụ đến N địa điểm dân cư là nhỏ Ta gọi khoảng cách hai địa điểm là độ dài đường ngắn nối chúng Giả sử N địa điểm trên là liên thông với Nếu có nhiều phương án thì đưa phương án đặt trung tâm dịch vụ địa điểm có số hiệu nhỏ Dữ liệu: File vào gồm M+1 dòng: - Dòng 1: Chứa hai số nguyên dương N và M (N  100); - Dòng i+1 (1  i  M): Chứa số nguyên dương x, y, z, đó hai số đầu x, y là số hiệu hai địa điểm dân cư nối với tuyến đường này, còn số thứ ba z ( 32767) là độ dài tuyến đường này Kết quả: File gồm dòng: - Dòng 1: Ghi vị trí trung tâm dịch vụ; - Dòng 2: Ghi tổng khoảng cách từ trung tâm dịch vụ đến các địa điểm dân cư Ví dụ: MARKET.INP MARKET.OUT 84 (85) 57 129 234 142 455 531 515 314 15 Program Cho_trung_tam; Uses crt; Const FI = 'market.inp'; FO = 'market.out'; MAX_N = 100; MAX_VALUE = 999999999; Var n, dia_diem, : longint; d : array[1 MAX_N, MAX_N] of longint; f : text; Procedure Doc; Var i, j, k, m : integer; Begin assign(f, FI); reset(f); read(f, n, m); for i := to n begin d[i, i] := 0; for j := i+1 to n begin d[i, j] := MAX_VALUE; d[j, i] := MAX_VALUE; end; end; 85 (86) for k := to m begin read(f, i, j); read(f, d[i, j]); d[j, i] := d[i, j]; end; close(f); End; Procedure Floyd; Var sum, i, j, k : longint; Begin for k := to n for i := to n for j := to n if d[i, j] > d[i, k] + d[k, j] then d[i, j] := d[i, k] + d[k, j]; := MAX_VALUE; for i := to n begin sum := 0; for j := to n sum := sum + d[i, j]; if sum < then begin dia_diem := i; := sum; end; end; End; Procedure Ghi; Begin assign(f, FO); rewrite(f); 86 (87) writeln(f, dia_diem); write(f, min); close(f); End; Begin Doc; Floyd; Ghi; End Bài 8: Thành phố trên hoả Đầu kỷ 21, người ta thành lập dự án xây dựng thành phố trên Hoả để kỷ 22 người có thể sống và sinh hoạt đó Giả sử kỷ 22, phương tiện giao thông chủ yếu là các phương tiện giao thông công cộng nên để lại hai điểm thành phố người ta có thể yên tâm chọn đường ngắn mà không sợ bị trễ kẹt xe Khi mô hình thành phố chuyển lên Internet, có nhiều ý kiến phàn nàn tính hợp lý nó, đặc biệt, tất các ý kiến cho hệ thống đường phố là quá nhiều, làm tăng chi phí xây dựng bảo trì Hãy bỏ số đường dự án xây dựng thành phố thoả mãn: + Nếu hai địa điểm dự án ban đầu có ít đường thì sửa đổi này không làm ảnh hưởng tới độ dài đường ngắn hai địa điểm đó + Tổng độ dài đường phố giữ lại là ngắn có thể Dữ liệu: Vào từ file văn CITY.INP, chứa đồ dự án + Dòng thứ ghi số địa điểm N và số đường phố m (giữa hai địa điểm có nhiều là đường phố nối chúng, n200; 0mn*(n-1)/2) + m dòng tiếp theo, dòng ghi ba số nguyên dương u, v, c cho biết có đường hai chiều nối hai địa điểm u, v và độ dài đường đó là c (c10000) Kết quả: Ghi file văn CITY.OUT, chứa kết sau sửa đổi 87 (88) + Dòng thứ ghi hai số k,d Trong đó k là số đường phố còn lại còn d là tổng độ dài các đường phố còn lại + k dòng tiếp theo, dòng ghi hai số nguyên dương p, q cho biết cần phải giữ lại đường nối địa điểm p với địa điểm q Các số trên dòng các file CITY.INP, CITY.OUT ghi cách ít dấu cách Ví dụ: CITY.INP 10 12 121 152 267 341 372 488 563 671 692 785 10 10 CITY.OUT 21 12 15 34 37 56 67 69 78 10 Chương trình 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; Gr : array[1 maxN] of ^mangB; 88 (89) Tr S,D : : array[1 maxN,1 maxN] of byte; LongInt; procedure CapPhat; var i: integer; begin for i:=1 to maxN new(a[i]); for i:=1 to maxN new(Gr[i]); end; procedure GiaiPhong; var i: integer; begin for i:=1 to maxN Dispose(a[i]); for i:=1 to maxN 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 for j:=1 to N a[i]^[j]:=Unseen; for i:=1 to N for j:=1 to N Gr[i]^[j]:=0; for i:=1 to M 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; procedure Floyd; var k,i,j: integer; begin 89 (90) Fillchar(Tr,sizeof(Tr),0); for k:=1 to N for i:=1 to N for j:=1 to N if a[i]^[j]>=a[i]^[k]+a[k]^[j] then begin a[i]^[j]:=a[i]^[k]+a[k]^[j]; Tr[i,j]:=k; end; end; procedure Solve; var i,j: LongInt; begin for i:=1 to N for j:=1 to N if (Gr[i]^[j]=1) and (Tr[i,j]>0) then begin Gr[i]^[j]:=0; Gr[j]^[i]:=0; end; S:=0; D:=0; for i:=1 to N-1 for j:=i+1 to N if Gr[i]^[j]=1 then begin S:=S+a[i]^[j]; D:=D+1; end; end; procedure inkq; var i,j: LongInt; begin assign(fo,tfo); rewrite(fo); 90 (91) writeln(fo,d,' ',S); for i:=1 to N-1 for j:=i+1 to N if Gr[i]^[j]=1 then writeln(fo,i,' ',j); close(fo); end; BEGIN CapPhat; Docdl; Floyd; Solve; Inkq; GiaiPhong; END B KẾT LUẬN Để tìm đường ngắn trên đồ thị còn có nhiều thuật toán và còn nhiều cách để cài đặt các thuật toán trên hiệu Tuy nhiên chuyên đề này đưa các cách cài đặt để từ đó học sinh tự nghiên cứu và phát triển thêm TÀI LIỆU THAM KHẢO 1, Tài liệu chuyên tin – Hồ Sỹ Đàm 2, Giải thuật và lập trình – Lê Minh Hoàng 3, Một số tài liệu khác các đồng nghiệp 91 (92) TÌM KIẾM TRÊN ĐỒ THỊ Ứng dụng BFS và DFS giải bài tập lý thuyết đồ thị Phần mở đầu 1.1 Lý - Bước sang kỷ 21, nhìn lại kỷ 20 là kỷ mà người đạt nhiều thành tựu khoa học rực rỡ nhất, thành tựu đó là bùng nổ ngành khoa học máy tính Sự phát triển kỳ diệu máy tính kỷ này gắn liền với phát triển toán học đại, đó là toán rời rạc Toán rời rạc nói chung và lý thuyết đồ thị nói riêng là công cụ thiết yếu cho nhiều ngành khoa học kỹ thuật - Trong chương trình học tập học sinh chuyên Tin trường THPT trang bị các kiến thức lý thuyết đồ thị để nhằm phục vụ cho việc lập trình giải toán, làm bài tập lập trình Bởi điều thông qua giải bài tập, học sinh phải thực hoạt động định bao gồm nhận dạng và thể định nghĩa, định lý, quy tắc hay phương pháp, hoạt động toán học phức hợp Học sinh nắm lý thuyết cách vững vàng thông qua việc làm bài tập - Việc cung cấp thêm phương pháp giải bài tập cho học sinh chuyên Tin là nhu cầu cần thiết Hiện việc nghiên cứu khai thác số yếu tố lý thuyết đồ thị số tác giả quan tâm Nếu ta có các phương pháp giúp học sinh chuyên Tin trung học phổ thông vận dụng kiến thức lý thuyết đồ thị vào giải toán thì giúp học sinh giải số lớp bài toán góp phần nâng cao chất lượng dạy học giải bài tập cho học sinh chuyên Tin 92 (93) - BFS và DFS là thuật toán tìm kiếm quan trọng trên đồ thị Những thuật toán này là móng quan trọng để có thể xây dựng và thiết kế thuật giải khác lý thuyết đồ thị 1.2 Mục tiêu, nhiệm vụ đề tài - Mục tiêu đề tài: Chỉ hướng vận dụng DFS và BFS lý thuyết đồ thị vào giải các bài toán và tìm các biện pháp để giúp học sinh chuyên Tin trung học phổ thông hình thành và phát triển lực vận dụng lý thuyết đồ thị vào giải bài tập lập trình - Nhiệm vụ đề tài: + Tìm hiểu nội dung lý thuyết đồ thị trang bị cho học sinh chuyên Tin Trong đó sâu vào hai thuật toán tìm kiếm trên đồ thị là DFS và BFS + Chỉ hệ thống bài tập chương trình toán có thể vận dụng DFS và BFS để giải các bài tập lý thuyết đồ thị + Kiểm tra hiệu các biện pháp, phương án lý thuyết đồ thị vào giải toán thực tế 1.3 Phương pháp nghiên cứu - Nghiên cứu lý luận + Tài liệu Giáo khoa chuyên tin, sách nâng cao, sách chuyên đề + Các tài liệu lý thuyết đồ thị và ứng dụng nó thực tiễn sống và dạy học 93 (94) + Các công trình nghiên cứu các vấn đề liên quan trực tiếp đến phương pháp đồ thị - Thực nghiệm sư phạm + Chỉ cho học sinh các dấu hiệu "nhận dạng" và cách thức vận dụng lý thuyết đồ thị vào giải bài tập toán + Biên soạn hệ thống bài tập luyện tập cho học sinh và số đề bài kiểm tra để đánh giá khả vận dụng lý thuyết đồ thị vào giải toán + Tiến hành thực nghiệm và đánh giá kết thực nghiệm 94 (95) Phần nội dung 2.3 Quá trình thực a Các Khái niệm lý thuyết đồ thị - Định nghĩa đồ thị: Đồ thị là cấu trúc rời rạc bao gồm các đỉnh và các cạnh nối các đỉnh này Chúng ta phân biệt các loại đồ thị khác kiểu và số lượng cạnh nối hai đỉnh nào đó đồ thị - Định nghĩa Đơn đồ thị vô hướng G = (V,E) bao gồm V là tập các đỉnh, và E là tập các cặp không có thứ tự gồm hai phần tử khác V gọi là các cạnh - Định nghĩa Đa đồ thị vô hướng G= (V, E) bao gồm V là tập các đỉnh, và E là tập các cặp không có thứ tự gồm hai phần tử khác V gọi là các cạnh Hai cạnh e1 và e2 gọi là cạnh lặp chúng cùng tương ứng với cặp đỉnh - Định nghĩa Giả đồ thị vô hướng G = (V, E) bao gồm V là tập các đỉnh và E là tập các cặp không có thứ tự gồm hai phần tử (không thiết phải khác nhau) V gọi là cạnh Cạnh e gọi là khuyên nó có dạng e = (u, u) - Định nghĩa Đơn đồ thị có hướng G = (V, E) bao gồm V là tập các đỉnh và E là tập các cặp có thứ tự gồm hai phần tử khác V gọi là các cung - Định nghĩa Đa đồ thị có hướng G = (V, E) bao gồm V là tập các đỉnh và E là tập các cặp có thứ tự gồm hai phần tử khác V gọi là các cung Hai cung e1, e2 tương ứng với cùng cặp đỉnh gọi là cung lặp - Cạnh liên thuộc: Hai đỉnh u và v đồ thị vô hướng G gọi là kề (u,v) là cạnh đồ thị G Nếu e = (u, v) là cạnh đồ thị ta nói cạnh này là liên thuộc với hai đỉnh u và v, nói là nối đỉnh u và đỉnh v, đồng thời các đỉnh u và v gọi là các đỉnh đầu cạnh (u, v) - Bậc đỉnh: Bậc đỉnh v đồ thị G=(V, E), ký hiệu deg(v) là số cạnh liên thuộc với nó Nếu cạnh là khuyên thì tính là 95 (96) Thí dụ Xét đồ thị cho hình 1, ta có deg(a) = 1, deg(b) = 4, deg(c) = 4, deg(f) = 3, deg(d) = 1, deg(e) = 3, deg(g) = Đỉnh bậc gọi là đỉnh cô lập Đỉnh bậc gọi là đỉnh treo Trong ví dụ trên đỉnh g là đỉnh cô lập, a và d là các đỉnh treo Định lý Giả sử G = (V, E) là đồ thị vô hướng với m cạnh Khi đó tông bậc tất các đỉnh hai lần số cạnh Thí dụ Đồ thị với n đỉnh có bậc là có bao nhiêu cạnh? Giải: Theo định lý ta có 2m = 6n Từ đó suy tổng các cạnh đồ thị là 3n Ta gọi bán bậc (bán bậc vào) đỉnh v đồ thị có hướng là số cung đồ thị khỏi nó (đi vào nó) và ký hiệu là deg+(v) (deg-(v)) 96 (97) Thí dụ Xét đồ thị cho hình Ta có deg-(a)=1, deg-(b)=2, deg-(c)=2, deg-(d)=2, deg-(e) = deg+(a)=3, deg+(b)=1, deg+(c)=1, deg+(d)=2, deg+(e)=2 Định lý Giả sử G = (V, E) là đồ thị có hướng Khi đó Tổng tất các bán bậc tổng tất các bán bậc vào số cung Đồ thị vô hướng thu cách bỏ qua hướng trên các cung gọi là đồ thị vô hướng tương ứng với đồ thị có hướng đã cho - Đường đi, chu trình trên đồ thị Đường độ dài n từ đỉnh u đến đỉnh v, đó n là số nguyên dương, trên đồ thị vô hướng G = (V, E) là dãy x0, x1,…, xn-1, xn đó u = x0 , v = xn , (xi , xi+1)  E, i = 0, 1, 2,…, n-1 Đường nói trên còn có thể biểu diễn dạng dãy các cạnh: (x0, x1), (x1, x2), …, (xn-1, xn) Đỉnh u gọi là đỉnh đầu, còn đỉnh v gọi là đỉnh cuối đường Đường có đỉnh đầu trùng với đỉnh cuối (tức là u = v) gọi là chu trình Đường hay chu trình gọi là đơn không có cạnh nào bị lặp lại - Tính liên thông đồ thị - Đồ thị vô hướng G = (V, E) gọi là liên thông luôn tìm đường hai đỉnh nó 97 (98) b Biểu diễn đồ thị trên máy tính - Có nhiều cách khác để lưu trữ các đồ thị máy tính Sử dụng cấu trúc liệu nào thì tùy theo cấu trúc đồ thị và thuật toán dùng để thao tác trên đồ thị đó Trên lý thuyết, người ta có thể phân biệt các cấu trúc danh sách và các cấu trúc ma trận Tuy nhiên, các ứng dụng cụ thể, cấu trúc tốt thường là kết hợp hai Người ta hay dùng các cấu trúc danh sách cho các đồ thị thưa (sparse graph), chúng đòi hỏi ít nhớ Trong đó, các cấu trúc ma trận cho phép truy nhập liệu nhanh hơn, lại cần lượng nhớ lớn đồ thị có kích thước lớn - Các cấu trúc danh sách Danh sách liên thuộc (Incidence list) - Mỗi đỉnh có danh sách các cạnh nối với đỉnh đó Các cạnh đồ thị có thể lưu danh sách riêng (có thể cài đặt mảng (array) danh sách liên kết động (linked list)), đó phần tử ghi thông tin cạnh, bao gồm: cặp đỉnh mà cạnh đó nối (cặp này có thứ tự đồ thị có hướng), trọng số và các liệu khác Danh sách liên thuộc đỉnh chiếu tới vị trí các cạnh tương ứng danh sách cạnh này Danh sách kề (Adjacency list) - Mỗi đỉnh đồ thị có danh sách các đỉnh kề nó (nghĩa là có cạnh nối từ đỉnh này đến đỉnh đó) Trong đồ thị vô hướng, cấu trúc này có thể gây trùng lặp Chẳng hạn đỉnh nằm danh sách đỉnh thì đỉnh phải có danh sách đỉnh Lập trình viên có thể chọn cách sử dụng phần không gian thừa, có thể liệt kê các quan hệ kề cạnh lần Biểu diễn liệu này thuận lợi cho việc từ đỉnh tìm đỉnh nối với nó, các đỉnh này đã liệt kê tường minh - Các cấu trúc ma trận 98 (99) Ma trận liên thuộc (Incidence matrix) - Đồ thị biểu diễn ma trận kích thước p × q, đó p là số đỉnh và q là số cạnh, liệu quan hệ đỉnh đầu cạnh và cạnh Đơn giản nhất: đỉnh chứa là , các trường hợp khác Ma trận kề (Adjaceny matrix) - ma trận N × N, đó N là số đỉnh đồ thị Nếu có cạnh nào đó nối đỉnh với đỉnh thì phần tử 1, không, nó có giá trị Cấu trúc này tạo thuận lợi cho việc tìm các đồ thị và để đảo các đồ thị Ma trận dẫn nạp (Admittance matrix) ma trận Kirchhoff (Kirchhoff matrix) hay ma trận Laplace (Laplacian matrix) - định nghĩa là kết thu lấy ma trận bậc (degree matrix) trừ ma trận kề Do đó, ma trận này chứa thông tin quan hệ kề (có cạnh nối hay không) các đỉnh lẫn bậc các đỉnh đó c Thuật toán tìm kiếm trên đồ thị * Thuật toán tìm kiếm theo chiều rộng Trong lý thuyết đồ thị, tìm kiếm theo chiều rộng (BFS) là thuật toán tìm kiếm đồ thị đó việc tìm kiếm bao gồm thao tác: (a) thăm đỉnh đồ thị; (b) thêm các đỉnh kề với đỉnh vừa thăm vào danh sách có thể thăm tương lai Có thể sử dụng thuật toán tìm kiếm theo chiều rộng cho hai mục đích: tìm kiếm đường từ đỉnh gốc cho trước tới đỉnh đích, và tìm kiếm đường từ đỉnh gốc tới tất các đỉnh khác Trong đồ thị không có trọng số, thuật toán tìm kiếm theo chiều rộng luôn tìm đường ngắn có thể Thuật toán BFS đỉnh gốc và thăm các đỉnh kề với đỉnh gốc Sau đó, với đỉnh số đó, thuật toán lại thăm các đỉnh kề với nó mà chưa 99 (100) thăm trước đó và lặp lại Xem thêm thuật toán tìm kiếm theo chiều sâu, đó sử dụng thao tác trên có trình tự thăm các đỉnh khác với thuật toán tìm kiếm theo chiều rộng Thuật toán sử dụng cấu trúc liệu hàng đợi để lưu trữ thông tin trung gian thu quá trình tìm kiếm: Chèn đỉnh gốc vào hàng đợi Lấy đỉnh đầu tiên hàng đợi và thăm nó  Nếu đỉnh này chính là đỉnh đích, dừng quá trình tìm kiếm và trả kết  Nếu không phải thì chèn tất các đỉnh kề với đỉnh vừa thăm chưa thăm trước đó vào hàng đợi Nếu hàng đợi là rỗng, thì tất các đỉnh có thể đến đã thăm – dừng việc tìm kiếm và trả "không thấy" Nếu hàng đợi không rỗng thì quay bước Thủ tục BFS(G,v): tạo hàng đợi Q chèn v vào Q đánh dấu đã thăm v while Q còn khác rỗng: lấy phần tử t đầu tiên Q if t là đỉnh đích: 10 trả t for all cung e=(t, o) xuất phát từ t if chưa thăm o: 100 (101) 11 đánh dấu đã thăm o 12 chèn o vào Q Thuật toán tìm kiếm theo chiều rộng dùng để giải nhiều bài toán lý thuyết đồ thị, chẳng hạn như: - Tìm tất các đỉnh thành phần liên thông - Thuật toán Cheney cho việc dọn rác - Tìm đường ngắn hai đỉnh u và v (với chiều dài đường tính số cung) - Kiểm tra xem đồ thị có là đồ thị hai phía - Thuật toán Cuthill–McKee - Thuật toán Ford–Fulkerson để tìm luồng cực đại mạng * Thuật toán tìm kiếm theo chiều sâu Tư tưởng chính thuật toán là: Giả sử chúng ta xét trên đồ thị G(V,E) Từ đỉnh u V thời nào đó ta thăm tới đỉnh kề v u và quá trình lặp lại đỉnh v bước tổng quát, giả sử xét đỉnh u 0, chúng ta có hai khả xảy ra: -Nếu tồn đỉnh v0 kề với u0 mà chưa thăm thì đỉnh v0 đó trở thành đỉnh đã thăm và quá trình tìm kiếm lại đỉnh v0 đó -Ngược lại, đỉnh kề với u0 đã thăm thì ta quay trở lại đỉnh mà trước đó ta đến đỉnh u0 để tiếp tục quá trình tìm kiếm 101 (102) Như vậy, quá trình thăm đỉnh thuật toán tìm kiếm theo chiều sâu, đỉnh thăm càng muộn càng sớm duyệt xong (Cơ chế Last In First Out - Vào sau trước) Do đó, ta có thể tổ chức quá trình này thủ tục đệ quy sau: Procedure DFS(u); Begin Visit(u); Daxet[u]:=True; For v Kề(u if not Daxet[v] then DFS(v); End; Và thủ tục duyệt hệ thống toàn đỉnh đồ thị là: Procedure Find; Begin Fillchar(Daxet,SizeOf(Daxet),False); For u V If not Daxet[u] then DFS(u); End; Dễ nhận thấy rằng, lần gọi DFS(u) thì toàn các đỉnh cùng thành phần liên thông với u viếng thăm Thủ tục Visit(u) là thao tác trên đỉnh u bài toán đặt cụ thể Độ phức tạp không gian DFS thấp BFS (tìm kiếm ưu tiên chiều rộng) Độ phức tạp thời gian hai thuật toán là tương đương và O(|V| + |E|) Ý tưởng thuật toán 102 (103) DFS trên đồ thị vô hướng giống khám phá mê cung với cuộn và thùng sơn đỏ để đánh dấu, tránh bị lạc Trong đó đỉnh s đồ thị tượng trưng cho cửa mê cung Ta đỉnh s, buộc đầu cuộn vào s và đánh đấu đỉnh này "đã thăm" Sau đó ta đánh dấu s là đỉnh hành u Bây giờ, ta theo cạnh (u,v) Nếu cạnh (u,v) dẫn chúng ta đến đỉnh "đã thăm" v, ta quay trở u Nếu đỉnh v là đỉnh mới, ta di chuyển đến v và lăn cuộn theo Đánh dấu v là "đã thăm" Đặt v thành đỉnh hành và lặp lại các bước Cuối cùng, ta có thể đến đỉnh mà đó tất các cạnh kề với nó dẫn chúng ta đến các đỉnh "đã thăm" Khi đó, ta quay lui cách cuộn ngược cuộn và quay lại trở lại đỉnh kề với cạnh còn chưa khám phá Lại tiếp tục quy trình khám phá trên Khi chúng ta trở s và không còn cạnh nào kề với nó chưa bị khám phá là lúc DFS dừng d Bài tập áp dụng DFS và BFS Bài toán Bài toán tìm thành phần liên thông đồ thị Cho đồ thị G=(V.E) Hãy cho biết số thành phần liên thông đồ thị và thành phần liên thông gồm đỉnh nào Gợi ý làm bài: Điều kiện liên thông đồ thị thường là yêu cầu tất yếu nhiều ứng dụng, chẳng hạn mạng giao thông hay mạng thông tin không liên thông thì xem bị hỏng, cần sửa chữa Vì thế, việc kiểm tra đồ thị có liên thông hay không là thao tác cần thiết nhiều ứng dụng khác đồ thị Dưới đây ta xét tình đơn giản (nhưng là bản) là xác định 103 (104) tính liên thông đồ thị vô hướng với nội dung cụ thể sau: “cho trước đồ thị vô hướng, hỏi nó có liên thông hay không?” Để trả lời bài toán, xuất phát từ đỉnh tùy ý, ta bắt đầu thao tác tìm kiếm từ đỉnh này (có thể chọn hai thuật toán tìm kiếm đã nêu) Khi kết thúc tìm kiếm, xảy hai tình huống: tất các đỉnh đồ thị thăm thì đồ thị đã cho là liên thông, có đỉnh nào đó không thăm thì đồ thị đã cho là không liên thông Như vậy, câu trả lời bài toán xem hệ trực tiếp thao tác tìm kiếm Để kiểm tra xem có phải tất các đỉnh đồ thị có thăm hay không, ta cần thêm thao tác nhỏ quá trình tìm kiếm, đó là dùng biến đếm để đếm số đỉnh thăm Khi kết thúc tìm kiếm, câu trả lời bài toán phụ thuộc vào việc so sánh giá trị biến đếm này với số đỉnh đồ thị: giá trị biến đếm số đỉnh thì đồ thị là liên thông, trái lại thì đồ thị là không liên thông Trong trường hợp đồ thị là không liên thông, kết tìm kiếm xác định thành phần liên thông chứa đỉnh xuất phát Bằng cách lặp lại thao tác tìm kiếm với đỉnh xuất phát khác, không thuộc thành phần liên thông vừa tìm, ta nhận thành phần liên thông thứ hai, , ta giải bài toán tổng quát là xác định các thành phần liên thông đồ thị vô hướng Như ta đã biết, các thủ tục DFS(u) và BFS(u) cho phép viếng thăm tất các đỉnh có cùng thành phần liên thông với u nên số thành phần liên thông đồ thị chính là số lần gọi thủ tục trên Ta dùng thêm biến đếm Connect để đếm số thành phần liên thông Và vòng lặp chính các thủ tục tìm kiếm theo chiều sâu hay chiều rộng cần sửa lại sau: Procedure Find; Begin 104 (105) Fillchar(Daxet,SizeOf(Daxet),False); Connect:=0; For u V If not Daxet[u] then Begin Inc(Connect); DFS(u); (*BFS(u)*) End; End; Thủ tục Visit(u) làm công việc đánh số thành phần liên thông đỉnh u: LienThong[u]:=Connect; Bài toán Bài toán tìm đường hai đỉnh đồ thị Cho đồ thị G=(V,E) Với hai đỉnh s và t là hai đỉnh nào đó đồ thị Hãy tìm đường từ s đến t Gợi ý làm bài: Do thủ tục DFS(s) và BFS(s) thăm các đỉnh liên thông với u nên sau thực xong thủ tục thì có hai khả năng: -Nếu Daxet[t]=True thì có nghĩa: tồn đường từ đỉnh s tới đỉnh t -Ngược lại, thì không có đường nối s và t Vấn đề còn lại bài toán là: Nếu tồn đường nối đỉnh s và đỉnh t thì làm cách nào để viết hành trình (gồm thứ tự các đỉnh) từ s đến t Về kỹ thuật lấy đường là: Dùng mảng Truoc với: Truoc[v] là đỉnh trước v đường Khi đó, câu lệnh If thủ tục DFS(u) sửa lại sau: If not Daxet[v] then Begin DFS(v); Truoc[v]:=u; End; 105 (106) Còn với thủ tục BFS ta sửa lại lệnh If sau: If not Daxet[w] then Begin Kết nạp w vào Queue; Daxet[w]:=True; Truoc[w]:=v; End; Việc viết đường lên màn hình (hoặc file) có thể có cách: -Viết trực tiếp dựa trên mảng Truoc: Hiển nhiên đường hiển thị ngược từ đỉnh t trờ s sau: -Dùng thêm mảng phụ P: cách này dùng để đảo đường từ mảng Truoc để có đường thuận từ đỉnh s đến đỉnh t -Cách thứ 3: là dùng chương trình đệ quy để viết đường Procedure Print_Way(i:Byte); If i<>s then Begin Print_Way(Truoc[i]); Write('đ',i); End; Lời gọi thủ tục đệ quy sau: Write(s); Print_Way(s); Các bạn có thể tuỳ chọn cách mà mình thích thiết nghĩ đó chưa phải là vấn đề quan trọng Nếu tinh ý dựa vào thứ tự thăm đỉnh thuật toán tìm kiếm theo chiều rộng BFS ta có nhận xét quan trọng, đó là: Nếu có 106 (107) đường từ s đến t, thì đường tìm thuật toán tìm kiếm theo chiều rộng cho chúng ta hành trình cực tiểu số cạnh Bài toán 3: Truyền tin Một lớp gồm N học viên, học viên cho biết bạn mà học viên đó có thể liên lạc (chú ý liên lạc này là liên lạc chiều, ví dụ : Bạn An có thể gửi tin tới Bạn Vinh Bạn Vinh thì chưa đã có thể gửi tin tới Bạn An) Thầy chủ nhiệm có thông tin quan trọng cần thông báo tới tất các học viên lớp (tin này phải truyền trực tiếp) Để tiết kiệm thời gian, thầy nhắn tin tới số học viên sau đó nhờ các học viên này nhắn lại cho tất các bạn mà các học viên đó có thể liên lạc được, và làm cho tất các học viên lớp nhận tin Câu hỏi Có phương án nào giúp thầy chủ nhiệm với số ít các học viên mà thầy chủ nhiệm cần nhắn? Gợi ý làm bài: - Có thể nhận thấy bài toán này chính là bài toán đã phát biểu phía trên Có thể coi học sinh là đỉnh đồ thị Hai học sinh có thể liên lạc với là cạnh Từ đó suy bài toán này là Bài toán tìm thành phần liên thông đồ thị Bài toán 4: Đường đến số Mỗi số nguyên dương có thể biểu diễn dạng tích số nguyên dương X,Y cho X<=Y Nếu phân tích này ta thay X X-1 còn Y Y+1 thì sau tính tích chúng ta thu là số nguyên dương là số 107 (108) Ví dụ: Số 12 có cách phân tích 1*12,3*4, 2*6 Cách phân tích thứ cho ta tích là : (1-1)*(12+1) = 0, cách phân tích thứ hai cho ta tích 10 : (31)*(4+1) = 10, còn cách phân tích thứ ba cho ta : (2-1)*(6+1)=7 Nếu kết là khác không ta lại lặp lại thủ tục này số thu Rõ ràng áp dụng liên tiếp thủ tục trên, cuối cùng ta đến 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<=10000), hãy đưa tất các số nguyên dương khác có thể gặp việc áp dụng thủ tục đã mô tả N Dữ liệu: Vào từ file Zeropath.Inp chứa số nguyên dương N Kết quả: Ghi file văn Zeropath.Out : Dòng đầu tiên ghi K là số lượng số tìm Dòng chứa K số tìm theo thứ tự tăng dần số Lưu ý: Có thể có số xuất trên nhiều đường biến đổi khác nhau, nó tính lần kết Ví dụ: ZEROPATH.INP ZEROPATH.OUT 12 6 10 Gợi ý làm bài: 108 (109) Đơn giản là sau lần phân tích thì chắn kết luôn nhỏ số đó Vì ta cần Lưu trữ mảng A: [0 10000] of boolean ; đó A[i] =true nó xuất trên đường đó, ngược lại thì A[i] =false Bằng cách loang theo chiều sâu, chúng ta đánh dấu các số nó dùng đến, không thể nào loang thì dừng Bài toán Con ngựa Một bàn cờ hình chữ nhật kích thước MxN, M,N nguyên dương không lớn 100 Bàn cờ chia thành các ô vuông đơn vị các đường song song với các cạnh Các dòng ô vuông đánh số từ đến M từ trên xuống dưới, các cột đánh số từ đến N từ trái sang phải Cho trước số nguyên dương K<=1000 Một ngựa đứng ô [u,v] và nhảy không quá k bước Yêu cầu: Hãy cho biết ngựa có thể nhảy đến bao nhiêu ô khác ô[u,v] trên bàn cờ và đó là ô nào (khi đứng ô, ngựa có thể nhảy tới ô đối đỉnh hình chữ nhật kích thước 2x3) Dữ liệu: Vào từ file MA.INP đó : Dòng đầu tiên ghi hai số M,N Dòng thứ hai ghi số K Dòng thứ ba ghi hai số U,V Kết quả: Ghi file MA.OUT : Dòng đầu tiên ghi S là số ô ngựa có thể nhảy đến Tiếp theo là S dòng, dòng ghi số dòng và số cột ô mà ngựa có thể nhảy đến 109 (110) Ví dụ: MA.INP MA.OUT 55 111531354244 23 Gợi ý làm bài Chúng ta loang theo chiều sâu, tìm kiếm xem ô nào mã có thể đặt chân đến vòng K bước nhảy Bài toán 6: Đường trên lưới ô vuông Cho lưới ô vuông kích thước N x N Các dòng lưới đánh số từ đến N từ trên xuống dưới, các cột lưới đánh số từ đến N từ trái qua phải Ô nằm trên giao dòng i, cột j gọi là ô (i, j) lưới Trên ô (i, j) lưới người ta ghi số nguyên dương aị, i, j = 1,2, , N Từ ô lưới phép di chuyển sang ô có chung cạnh với nó Thời gian để di chuyển từ ô này sang ô khác là phút Cho trước thời gian thực di chuyển là K (phút), hãy xác định cách di chuyển ô (1, 1) cho tổng các số trên các ô di chuyển qua là lớn (Mỗi ô lưới có thể di chuyển qua bao nhiêu lần được) Dữ liệu: Vào từ file văn NETSUM.INP:  Dòng đầu tiên chứa các số nguyên dương N, K (2 N 100), K 10000) 110 (111)  Dòng thứ i số N dòng chứa các số nguyên ai1, ai2 , aiN, < aị 10000 (Các số trên cùng dòng ghi cách ít dấu cách) Kết quả: Ghi file văn NETSUM.OUT:  Dòng đầu tiên ghi tổng số các số trên đường di chuyển tìm  K dòng dòng ghi toạ độ ô trên đường di chuyển (bắt đầu t ô (1, 1)) Ví dụ: NETSUM.INP NETSUM.OUT 1 1 1 1 1 1 1 1 1 1 1 4 Gợi ý làm bài: 111 (112) Loang các ô có thể đến các đường trên lưới Tìm cách nào có đường mà tổng lớn thì lấy Bài toán 7:Bàn cờ  2793 Bàn cờ Mã bài: CHESSCBG Một bàn cờ là bảng gồm dòng, cột Mỗi cờ là cách xếp quân cờ, hai quân khác hai ô khác Bài toán đặt là cho hai cờ và 2, hãy tìm số ít bước di chuyển quân để chuyển từ sang 2; bước di chuyển quân là lần chuyển quân cờ sang ô trống kề cạnh với ô quân cờ đứng Dữ liệu vào Từ file văn gồm dòng, dòng là xâu nhị phân độ dài mà số 1/0 tương ứng với vị trí có không có quân cờ Bốn dòng đầu là cờ 1, bốn dòng sau là cờ Dữ liệu Gồm dòng là số bước chuyển quân ít Ví dụ Dữ liệu vào: 1111 0000 1110 0010 1010 0101 1010 0101 Dữ liệu : Gợi ý làm bài : Chúng ta giải nhờ phương pháp đơn giản : Tìm kiếm theo chiều rộng Ta coi trạng thái l bảng là đỉnh đồ thị 112 (113) Mỗi lần di chuyển quân cờ trên bàn thì nó tạo trạng thái bảng, tức là đến đỉnh Trong bài toán này chúng ta xét với (m=n=4) Tức là file input, không có dòng đầu tiên Mỗi trạng thái bảng là loạt các ô có giá trị và Chúng ta trải nó thành hàng thì tạo bảng chiều toàn các số và Vì có 16 ô, nên bảng tương ứng với hệ nhị phân số nào đó nằm word (16 bit) Tức là số đỉnh đồ thị có thể có là 216 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 Bảng Bảng sau trải sau : a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 Bảng Khi di chuyển các quân thì các quân (ở bảng 1) có thể tới ô bên cạnh có thể (trừ trờng hợp ngoài bảng) Tức là tương ứng bảng 2, giả sử vị trí Ai thì nó có thể có đến ô : Ai-1, Ai+1, Ai-4, Ai+4 (Phải trừ trờng hợp nó ngoài bảng) Quá trình di chuyển quân tức là bít thứ i tắt, còn các bít đến bật Loang theo chiều rộng quá trình 113 (114) chuyển bit chuyển đến trạng thái mong muốn Đường di chuyển đó chính là cách di chuyển cờ với số bước ít để đến trạng thái mong muốn Bài toán Số rõ ràng Các nhà toán học đưa voà lý thuyết nhiều cách phân loại số, ví dụ, với các số nguyên ta có số chẵn và số lẻ, số nguyên tố và hợp số, số chính phương và không chính phương… Bob muốn đặt dấu ấn mình lĩnh vực phân loại số Bob chia các số nguyên dương thành loại: rõ ràng là luẫn quẫn Việc xác định số thuộc loại nào thực theo giải thuật sau: Với số nguyên dương n, ta tạo số cách lấy tổng bình phương các chữ số nó, với số này ta lặp lại công việc trên Nếu quá trình trên, ta nhận số là 1, thì số n ban đầu gọi là số rõ ràng Ví dụ với n=19, ta có: 19→82(=12+92)→68→100→1 Như vậy, 19 là số rõ ràng Không phải số rõ ràng Ví dụ, với n=12 ta có: 12→5→25→29→85→89→145→42→20→4→16→37→58→89→145 Rất thú vị với cách phân loại mình, Bob muốn biết, thực tế, số rõ ràng nhiều hay ít? Yêu cầu: Cho hai số nguyên dương A và B(1≤A≤B≤10000000) Hãy xác định Ksố lương số rõ ràng nằm khoảng [A,B] Dữ liệu: Vào từ file CLEAR.INP gồm dòng chứa số nguyên A và B Kết quả: Đưa file văn CLEAR.OUT số nguyên K Ví dụ: 114 (115) CLEAR.INP CLEAR.OUT 20 Gợi ý làm bài: Bằng cách loang theo chiều sâu, chúng ta đánh dấu các số nó dùng đến là số rõ ràng Bài toán 9: Từ tập các bài có trên SPOJ (oi) 2195 Điều kiện thời tiết Mã bài: WEATHER Hóng hàng khụng OlympAirways thực cỏc chuyến bay n sõn bay đỏnh số từ đến n Hệ thống cỏc chuyến bay thiết lập cho sõn bay phục vụ hóng luụn cú đường bay bao gồm nhiều chuyến bay trực tiếp hai sõn bay Mỗi chuyến bay thực việc di chuyển hai thành phố theo hai chiều Trung tõm điều khiển hóng đưa khỏi niệm độ dớnh kết cặp hai sõn bay A và B xỏc định là số lượng cỏc chuyến bay mà việc khụng thực số chỳng (cỏc chuyến bay khỏc thực bỡnh thường) dẫn đến khụng thể bay từ sõn bay A đến sõn bay B Một nghiờn cứu cho biết rằng, điều kiện thời tiết xấu, tổng độ dớnh kết cỏc cặp sõn bay phải đạt đến giỏ trị định thỡ hệ thống đường bay gọi là an toàn Yờu cầu: Hóy giỳp trung tõm điều khiển tớnh tổng độ dớnh kết cặp sõn bay Dữ liệu Dòng đầu tiên chứa số nguyên n (1 ≤ n ≤ 100) Dòng thứ hai chứa số nguyên m (1 ≤ m ≤ 5000) - số lượng các chuyến bay 115 (116) Mỗi dòng số m dòng chứa thông tin chuyến bay, bao gồm hai số nguyên dương khoảng từ đến n: số hai sân bay nối chuyến bay Kết qủa In số nguyờn là tổng độ dớnh kết cặp sõn bay (A, B) (với A < B) Vớ dụ Dữ liệu: 5 12 42 45 32 31 Kết qủa 10 Gợi ý làm bài: Bài này với cạnh chúng ta cần kiểm tra xem nó có là cầu không (dùng DFS BFS) Nếu là cầu thì tổng độ kết dính tăng lên giá trị = tích các đỉnh thuộc hai miền mà cạnh đó làm cầu Bài toán 10: 2719 Bãi cỏ ngon 116 (117) Mã bài: VBGRASS Bessie dự định ngày nhai cỏ xuõn và ngắm nhỡn cảnh xuõn trờn cỏnh đồng nụng dõn John, cỏnh đồng này chia thành cỏc ụ vuụng nhỏ với R (1 <= R <= 100) hàng và C (1 <= C <= 100) cột Bessie ước gỡ cú thể đếm số khúm cỏ trờn cỏnh đồng Mỗi khúm cỏ trờn đồ đỏnh dấu ký tự ‘#‘ là ký tự ‘#’ nằm kề (trờn đường chộo thỡ khụng phải) Cho đồ cỏnh đồng, hóy núi cho Bessie biết cú bao nhiờu khúm cỏ trờn cỏnh đồng Vớ dụ cỏnh đồng dõy với R=5 và C=6: # # # # ## .# Cỏnh đồng này cú khúm cỏ: khúm hàng đầu tiờn, khúm tạo hàng thứ và thứ cột thứ 2, khúm là ký tự nằm riờng rẽ hàng 3, khúm tạo cột thứ và thứ hàng 4, và khúm cuối cựng hàng Dữ liệu Dòng 1: số nguyên cách dấu cách: R và C Dòng R+1: Dòng i+1 mô tả hàng i cánh đồng với C ký tự, các ký tự là ‘#’ ‘.’ Kết Dòng 1: Một số nguyên cho biết số lượng khóm cỏ trên cánh đồng Ví dụ Dữ liệu 56 117 (118) # # # # ## .# Kết Gợi ý làm bài: Coi ô trên cánh đồng là đỉnh đồ thị Sử dụng thuật toán loang theo chiều rộng để đếm số bãi cỏ Bài toán 11 2969 Bin Laden Mã bài: BINLADEN Bin Laden Trùm khủng bố Bin Laden trốn hầm đào sâu xuống mặt đất M tầng, tầng có N phòng Các phòng ngăn cách các cửa khó phá Các phòng có cửa xuống phòng phía và phòng bên Từ trên mặt đất có N cửa xuống N phòng tầng -1 Bin Laden tầng cùng (tầng -M) phòng thứ N (phòng bên phải nhất) Mỗi cửa làm kim loại khác với độ dày khác nên việc phá cửa cần thời gian khác Bạn hãy tìm cách từ mặt đất xuống phòng Bin Laden nhanh không thoát Dữ liệu Dòng ghi M và N 118 (119) Dòng đến 2M + 1, dòng chẵn ghi N số, dòng lẻ ghi N - số là chi phí để phá cửa Kết Ghi số là thời gian nhỏ để đến phòng Bin Laden Ví dụ Dữ liệu 42 99 10 10 99 99 10 10 99 Kết 44 + 99 + 10 + | | | | | | | | + 10 + 99 + | | | | | | | | + 99 + 10 + 119 (120) | | | | | | | | + 10 + 99 + | | | | | | | | + + + Đi theo đường zigzac Giới hạn  <= M <= 2222  <= N <= 10  Chi phí các cánh cửa thuộc [0, 1000] Gợi ý làm bài: Coi phòng là đỉnh đồ thị Hai đỉnh có đường nối các phòng kề cạnh, và có trọng số thời gian phá tường ngăn cách Bài toán trở thành tìm đường ngắn từ phòng nào đó tầng trên xuống phòng cuối cùng tầng Bài toán 12: 3892 Trồng cây Mã bài: GARDEN25 Nhà sherry có khu vườn rộng và trồng nhiều loại cây Để đón tết năm 2010 sherry trồng thật nhiều mai và đào Và có mai và đào mà thôi Khu vườn nhà sherry có dạng hình chữ nhật, kích thước M x N Trên đó có số ô đánh dấu để trồng cây Để tăng tính thẩm mỹ khu vườn sherry muốn số cây mai và đào khu vườn chênh lệch không quá Đồng thời số cây mai, đào trên hàng, cột khu vườn chênh lệch không quá Input Dòng 1: ghi số nguyên M, N (1 ≤ M, N ≤ 250) M dòng tiếp theo: Mỗi dòng ghi N số, đó số thứ j hàng thứ i 1/0 tương ứng với ô (i, j) có/không trồng cây 120 (121) Output Gồm M dòng: Mỗi dòng ghi N số nguyên, các ô không trồng cây ghi 0, các ô trồng cây có giá trị 1/2 tương ứng đó trồng mai/đào Example Input: 44 1010 0101 1010 0101 Output: 2010 0201 1020 0102 Gợi ý làm bài: Ta coi hàng , cột là đỉnh đồ thị Nếu ô (i,j) có giá trị <> thì đỉnh hàng i nối với đỉnh cột j Bài toán trở thành : Tìm các tô các cạnh đồ thị hai màu , cho : - với đỉnh thì độ chênh lệch hai màu tô các cạnh nối nó chênh lệch không quá 121 (122) - Với đồ thị chúng chênh lệch không quá Chúng ta có phương pháp giải bài toán này sau : - Nhận xét : Nếu xuất phát từ đỉnh bậc lẻ và cách theo các cung đồ thị , cung qua lần thì trạng thái tắc đường phải xảy đỉnh bậc lẻ khác ( số đỉnh bậc lẻ có đồ thị là số chẵn ) - Nhận xét : Nếu xuất phát từ đỉnh bậc chẵn đồ thị không có đỉnh bậc lẻ và cách các cung đồ thị , cung qua lần thì trạng thái tắc đường phải xảy chính đỉnh xuất phát Trạng thái tắc đường là trạng thái mà đỉnh vừa tới không còn cung nào chưa qua Dựa vào hai nhận xét chúng ta có : - Trường hợp : Khi đồ thị còn đỉnh bậc lẻ Chọn đỉnh lẻ để xuất phát Bằng cách qua các cung đồ thị màu chưa tô , cung qua ta tô xen kẽ hai màu tắc đường Trong trường hợp này ,tại đỉnh bậc lẻ kết thúc đường trên ,không còn cung nào chứa nó chưa tô , đồng thời , đỉnh xuất phát , số cung còn lại chưa tô ( có ) là số chẵn , còn các đỉnh còn lại trên đường số cung tô các màu - Trường hợp : Khi đồ thị còn đỉnh bậc chẵn Trong trường hợp này , tất các cung kề với các đỉnh bậc lẻ ( có ) đồ thị ban đầu đã tô Chọn đỉnh nào đó còn có cung chưa tô chứa nó làm đỉnh xuất phát và cách theo các cung chưa tô đạt trạng thái kết thúc ( đỉnh xuất phát ) Bằng cách tô màu các cung xen kẽ trên lộ trình đã qua Khi đó só lượng các cung tô hai màu tô kề với đỉnh trên lộ trình là 2.4 Kết thu 122 (123) - Học sinh sau học chuyên đề này hứng thú với việc học lý thuyết đồ thị Khi gặp bài toán lý thuyết đồ thị tự tin làm bài DFS và BFS còn là tảng để dạy các phần lý thuyết khác chuyên đề đồ thị 123 (124) Phần kết luận - BFS và DFS là thuật toán tìm kiếm quan trọng trên đồ thị Những thuật toán này là móng quan trọng để có thể xây dựng và thiết kế thuật giải khác lý thuyết đồ thị Tuy nhiên có thể thấy phương pháp này còn hạn chế số lượng các phần tử tập D lớn Nó thể chỗ thời gian tính toán kết thường không chấp nhận Do đó phương pháp Tìm kiếm theo DFS và BFS cần phải bổ sung các phương pháp cho phép bỏ qua gộp số phần tử Điều này cải thiện đáng kể thời gian thực chương trình - Phương pháp Tìm kiếm theo DFS và BFS là phương pháp dễ hiểu với học sinh và có thể áp dụng để giải nhiều bài toán tối ưu với liệu nhỏ (thường đạt đến 50% đến 60% số tets bài thi) 124 (125) Tài liệu tham khảo: Cấu trúc liệu và giải thuật – Lê Minh Hoàng (DHSP Hà Nội) Tài liệu tập huấn phát triển chuyên môn giáo viên Tin học - Nhiều tác giả Tài liệu hội thảo phát triển chuyên môn giáo viên Tin học - Nhiều tác giả Thuật toán quay lui – Lê Sỹ Hùng (Hương Sơn – Hà Tĩnh) Tài Liệu sách giáo khoa chuyên tin tập 1,2 - Nhiều tác giả VNOI - Olympic tin học Việt Nam - Mục lục diễn đàn - Forum 125 (126) Rèn luyện kỹ duyệt đồ thị BÀI ĐÈN TRANG TRÍ RÔN MUA MỘT BỘ ĐÈN TRANG TRÍ GỒM N ĐÈN (1 ≤ N ≤ 000) MỖI ĐÈN CÓ MỘT CÔNG TẮC ĐỂ BẬT HAY TẮT RIÊNG ĐÈN ĐÓ MỖI GIÂY RÔN CÓ THỂ BẬT HOẶC TẮT MỘT BÓNG ĐÈN TÙY CHỌN BAN ĐẦU TẤT CẢ CÁC BÓNG ĐỀU Ở TRẠNG THÁI TẮT MỘT CẤU HÌNH CỦA BỘ ĐÈN LÀ TRẠNG THÁI KHI MỘT SỐ ĐÈN NÀO ĐÓ ĐƯỢC BẬT SÁNG, NHỮNG ĐÈN CÒN LẠI – TẮT RÔN ĐẶC BIỆT THÍCH MỘT SỐ CẤU HÌNH VÌ CHÚNG CÓ VẺ PHÙ HỢP VỚI KHUNG CẢNH CĂN PHÒNG CỦA RÔN.MỖI TRẠNG THÁI CỦA BỘ ĐÈN ĐƯỢC BIỂU DIỄN BẰNG MỘT XÂU N KÝ TỰ TỪ TẬP {0, 1} KÝ TỰ THỨI XÁC ĐỊNH TRẠNG THÁI ĐÈN THỨI, TƯƠNG ỨNG VỚI TRẠNG THÁI ĐÈN TẮT, LÀ TRẠNG THÁI ĐÈN ĐƯỢC BẬT SÁNG VÍ DỤ, VỚI N = VÀ RÔN ĐẶC BIỆT THÍCH CẤU HÌNH {1, 0, 1}, {0, 1, 0}, {1, 1, 1} ĐỂ KIỂM TRA XEM CẤU HÌNH NÀO LÀ THÍCH HỢP NHẤT RÔN PHẢI LẦN LƯỢT BẬT TẮT MỘT SỐ ĐÈN TRONG TRƯỜNG HỢP NÀY RÔN CẦN GIÂY ĐỂ XEM XÉT HẾT MỌI CẤU HÌNH Yêu cầu: Cho biết n và m, đó m – số cấu hình khác mà Rôn đặc biệt yêu thích (1 ≤ m ≤ 15) Hãy xác định thời gian tối thiểu cần thiết để kiểm tra hết tất các trạng thái mà Rôn quan tâm Dữ liệu: Vào từ file văn GARLAN.INP:  Dòng đầu tiên chứa số nguyên n và m, 126 (127)  Mỗi dòng m dòng chứa xâu n ký tự xác định cấu hình Rôn yêu thích Kết quả: Đưa file văn GARLAN.OUT số nguyên – thời gian tối thiểu kiểm tra các cấu hình Ví dụ: Lời giải : là đỉnh số ) GARLAN.INP GARLAN.OUT 33 101 010 111 Mỗi trạng thái coi đỉnh đồ thị (trạng thái ban đầu - Trong số cạnh là chi phí chuyển từ trạng thái sang trạng thái - Bìa toán trở thành tìm đường từ qua các đỉnh với tổng trọng số nhỏ  GỌI MỖI CANH LÀ (U,V,W), TRONG ĐÓ U,V LÀ ĐỈNH , W LÀ TRỌNG SỐ TRONG VÍ DỤ TRÊN TA CÓ CÁC CẠNH (0,1,2) , (0,2,1) , (0,3,3) , (1,2,3) , (1,3,1) , (2,3,2) Ta đường tốt là 0-2-1-3 ( 0-2-3-1) với chi phí = BÀI 2BẮC CẦU Chính phủ quốc đảo Oceani định xây dựng m cầu nối n đảo mình, tạo mạng lưới giao thông đường cho phép từ dảo tới đảo khác đường (trực tiếp qua số đảo trung gian) Mỗi cây cầu nối đảo khác và cho phép lại hai chiều Các đảo đánh số từ đến n-1.Bị hạn chế kinh phí và nguồn nhân lực, người ta định xây dựng cầu và lên kế hoạch xác định cầu và trình tự xây Mỗi cây cầu xác định cặp đảo u, v mà nó nối Trong quá trình 127 (128) thực kế hoạch có thể đến lúc nào đó từ đảo đã có thể đến đảo khác đường Ví dụ, Oceani có đảo và người ta định xây dựng cầu theo trình tự là – 1, – 2, – 2, – 3, – Tuy vậy, không cần chờ đợi đến hoàn thành kế hoạch xây cầu, sau cầu thứ xây xong tất các đảo đã nối liền đường Yêu cầu: Cho n, m và các cây cầu dự kiến xây Thông tin các cây cầu đưa theo đúng trình tự xây dựng Hãy xác định số cầu tối thiểu cần xây theo kế hoạch để từ đảo đã có thể đến đảo khác đường Dữ liệu: Vào từ file văn BRIDGES.INP:  Dòng đầu tiên chứa số nguyên n và m (1 ≤ n ≤ 106, ≤ m ≤ 5106),  Dòng thứ i m dòng chứa số nguyên u và v xác định cây cầu thứ i cần xây Kết quả: Đưa file văn BRIDGES.OUT kết tìm dạng số nguyên Ví dụ: BRIDGES.INP 45 01 02 12 23 30 Lời giải : m) BRIDGES.OUT Tìm đáp số tìm kiếm nhị phân ( Ds = n-1 , ds max = - Với ds dự đoán , ta việc kiểm tra tính liên thông với danh sách cạnh từ đến ds 128 (129) Bài 3Đường Robot Một bảng hình chữ nhật có kích thước MxN (M,N nguyên dương và không lớn 100) chia thành các ô vuông đơn vị các đường thẳng song song với các cạnh Một số ô vuông nào đó có thể đặt các vật cản Từ ô vuông, Robot có thể đến ô vuông kề cạnh với nó ô vuông đó không có vật cản Hỏi Robot bắt đầu xuất phát từ ô vuông không có vật cản thuộc dòng K, cột L thì có thể đến ô vuông không có vật cản thuộc dòng H, cột O hay không? Nếu có thì hãy đường qua ít ô vuông Dữ liệu vào là tệp văn BAI3.INP có cấu trúc: - Dòng đầu tiên ghi các chữ số M, N, K, L, H, O Các số ghi cách ít ký tự trống; - M dòng tiếp theo, dòng ghi N số tuỳ thuộc vào ô vuông tương ứng bảng hình chữ nhật nêu trên có vật cản hay không (ghi số có vật cản); các số trên dòng ghi liên tiếp Dữ liệu là tệp văn BAI3.OUT có cấu trúc: Nếu Robot có thể từ ô vuông thuộc dòng K, cột L đến ô vuông thuộc dòng H, cột O thì: - Dòng đầu tiên ghi ‘Co duong di ‘; - Các dòng tiếp theo, dòng ghi số là số dòng và số cột các ô vuông đường tìm từ ô vuông thuộc dòng K, cột L đến ô vuông thuộc dòng H, cột O mà qua ít ô vuông Hai số trên dòng ghi cách ít ký tự trống; - Ngược lại, Robot không thể từ ô vuông thuộc dòng K, cột L đến ô vuông thuộc dòng H, cột O thì ghi ‘Khong co duong di’ 129 (130) Ví dụ 1: robot.inp: robot.out: 473426 Co duong di 1000000 34 0010100 35 0000000 36 1101000 26 robot.inp: robot.out: 472213 Khong co duong di Ví dụ 2: 1010000 0010100 0100000 1101000 Phân tích: Yêu cầu bài toán thực chất là tìm đường từ ô [K,L] đến ô [H,O] cho qua ít ô vuông Ta dễ thấy thuật toán để xử lý cách hợp lý là thuật toán 130 (131) Loang Ta bắt dầu “loang” từ ô [K,L], “loang” đến ô [H,O] thì có đường đi, ngược lại không có đường Hàng đợi phục vụ “loang” thể mảng chiều Q:Array[1 2,Mmax*Max] of Byte; hàng thứ Q để lưu thông tin số hàng, hàng thứ lưu thông tin số cột các ô nạp vào Q Mảng A lưu thông tin tình trạng các ô - có vật cản hay không bảng hình chữ nhật chứa các ô vuông Mảng P dùng để đánh dấu ô đã “loang” đến; đồng thời để phục vụ cho việc truy xuất đường sau này nên ô [i,j] “loang” đến thì P[i,j] gán giá trị là r (r là giá trị tương ứng với hướng mà ô trước đó “loang” đến, hướng nào tương ứng với giá trị k bao nhiêu tuỳ theo quy định, ví dụ r = - sang phải, - xuống, - sang trái, - lên) Sau thực xong việc “loang”, P[H,O] = thì điều có có nghĩa là ô [H,O] chưa “loang” đến (không có đường đi), P[H,O] = r (r=1 - loang theo hướng) thì dựa vào hướng “loang” đến mà ta tìm ô trước đó, ta lại dựa vào giá trị k ô tìm ta tìm ô trước đó quá trình trên kết thúc tìm ô [K,L] Sau “loang” xong thì giá trị các phần tử mảng Q không còn giá trị sử dụng nên ta có thể dùng mảng Q phục vụ cho việc truy xuất kết Bài 4Gặp gỡ hai Robot Trên lưới ô vuông MxN (M,N<100), người ta đặt Robot A góc trái trên, Robot B góc phải Mỗi ô lưới ô có thể đặt vật cản không (ô trái trên và ô phải không có vật cản) Hai Robot bắt đầu di chuyển đồng thời với tốc độ và không Robot nào dừng lại Robot di chuyển (trừ nó không thể nữa) Tại bước, Robot có thể di chuyển theo hướng - lên, xuống, sang trái, sang phải - vào các ô kề cạnh Hai Robot gặp chúng cùng đứng ô vuông Bài toán đặt là tìm cách di chuyển ít mà Robot phải thực để có thể gặp 131 (132) Dữ liệu vào cho tệp robot.inp: - Dòng đầu ghi số M, N cách ít ký tự trống; - M dòng tiếp theo, dòng ghi N số liên tiếp mô tả trạng thái các ô vuông: - có vật cản, - không có vật cản Dữ liệu ghi vào tệp robot.out: - Nếu Robot không thể gặp thì ghi ký tự ‘#’ - Ngược lại, ghi hai dòng, dòng là dãy các ký tự viết liền mô tả các bước Robot: U - lên, D - xuống, L - sang trái, R - sang phải Dòng đầu là các bước Robot A, dòng sau là các bước Robot B Ví dụ: robot.inp robot.out robot.inp robot.out 46 DRRR 34 # 011000 LULU 0000 000001 0000 001001 0000 010100 Phân tích: Với dạng bài toán thì ta nghĩ đến thuật toán Loang để tìm đường cho Robot Như là phải “loang” từ phía (loang Robot A và loang Robot B) Nhưng vì Robot di chuyển đồng thời không cho phép ta cài đặt việc “loang” song song từ phía nên ta phải thiết kế “loang” nào cho hợp lý Xin đề xuất ý tưởng “loang” sau: Cứ Robot A loang lớp thì dừng lại để Robot B loang lớp, quá trình đó lặp lặp lại Robot gặp ô Robot dừng “loang” Một lớp “loang” đây là “loang” từ 132 (133) các phần tử có hàng đợi (từ phần tử Queue[dau] đến phần tử Queue[cuoi]) Sau lớp “loang”, biến dau và biến cuoi lại điều chỉnh để trở thành vị trí đầu và vị trí cuối các phần tử Queue Ta có thể mô tả cụ thể các lớp “loang” Robot với liệu vào là tệp robot.inp thứ trên: Lớp Lớp Lớp Queue Robot A 1 1 Queue 3 3 Robot B 4 4 Lớp Lớp Lớp Q1,Q2 là mảng dùng để biểu diễn cấu trúc hàng đợi để phục vụ việc “loang” Robot Trong quá trình “loang” ta phải lưu giữ thông tin hàng, cột ô “loang” đến, các phần tử Q1, Q2 là các record có kiểu HC HC = Record h,c:Byte; {h: lưu số hàng, c: lưu số cột} end; Hai hàng đợi Q1, Q2 khởi tạo sau: Procedure KT_Queue; Begin dau1:=1; cuoi1:=1; 133 (134) Q1[cuoi1]:=1; Q1[cuoi1]:=1; {Robot A xuất phát từ ô [1,1]} dau2:=1; cuoi2:=1; Q2[cuoi2]:=M; Q2[cuoi2]:=N; {Robot B xuất phát từ ô [M,N]} End; Ngay sau khởi tạo thì Q1 chứa ô [1,1], Q2 chứa ô [M,N] Đó là các ô xuất phát để “loang” Robot Mỗi Robot từ ô có thể “loang” theo bốn hướng: xuống, sang trái, lên, sang phải; nên để thuận tiện cho việc cài đặt ta sử dụng kỷ thuật “rào”: Mảng A[i,j] chứa thông tin các ô lưới ô vuông khai báo A:Array[0 Mmax + 1,0 Nmax + 1] of Byte (chứ không phải thông thường là [1 Mmax,1 Nmax]) và khởi tạo FillChar(A,SizeOf(A),1) (như là xung quanh lưới ô vuông “rào” bới số 1); đồng thời sử dụng mảng Hi=(1,0,-1,0), Hj=(0,-1,0,1) Khi đó việc “loang” theo lớp Robot A thực sau: Procedure LoangA; Var k:Byte; Begin j:=Cuoi1; For i:=dau1 to cuoi1 For k:=1 to Begin h:= Q1[i].h + Hi[k]; {k=1 - xuống, - sang trái, - lên, - sang phải} 134 (135) c:= Q1[i].c + Hj[k]; If A[h,c] = then {ô [h,c] không có vật cản và chưa “loang” đến} Begin Inc(j); Q1[j].h:= h; Q1[j].c:= c; {Nạp ô [h,c] vào hàng đợi Q1} A[h,c]:=k;{Đánh dấu ô cách gán giá trị tương ứng với hướng loang} B[h,c]:=True; {Dấu hiệu cho Robot B nhận biết đã gặp Robot A} End; End; dau1:=cuoi1 + 1; cuoi1:=j; {Điều chỉnh lại biến dau1, cuoi1 cho các phần tử Q1} If dau1 > cuoi1 then ST:=True; {ST=True là Q1 rỗng, kết thúc “loang”} End; Việc “loang” theo lớp Robot B tương tự Robot A khác chổ “loang” đến ô [h,c] nào đó thì phải xét dấu hiệu B[h,c] xem thử đã gặp Robot A chưa: If B[h,c] then {Nếu ô [h,c] Robot B gặp Robot A thì} Begin lk:=k; {Lưu lại giá trị tương ứng với hướng “loang” để lấy kết quả} hm:=h; {Lưu lại số hàng ô mà Robot gặp để lấy kết quả} cm:=c; {Lưu lại số cột ô mà Robot gặp để lấy kết quả} 135 (136) TT:=True; {Dấu hiệu dừng “loang” Robot vì đã gặp nhau} Exit; End; Sở dĩ ta phải lưu lại giá trị tương ứng với hướng “loang” (lk:=k) là vì ô gặp [h,c] Robot A đã “loang” đến trước nên đã gán giá trị A[h,c] giá trị tương ứng với hướng “loang” đến nên Robot B “loang” đến ô [h,c] buộc ta phải lưu lại giá trị tương ứng với hướng “loang” vào biến lk để sau này truy xuất đường Robot B Quá trình “loang” theo lớp Robot thực sau: Procedure Loang_lop; Begin TT:=False; ST:=False; While (ST=False) and (TT=False) Begin FillChar(B,SizeOf(B),False); {Đánh dấu theo lớp loang} Loang1; Loang2; End; End; Lệnh đánh dấu theo lớp “loang” vị trí trên: FillChar(B,SizeOf(B),False) là quan trọng vì Robot B gặp Robot A ô [h,c] B[h,c] = True thời điểm lớp “loang” Robot A cùng lớp “loang” với Robot B Còn B[h,c] = True lớp “loang” trước nào đó Robot A thì 136 (137) không thể kết luận Robot gặp vì đó Robot di chuyển khập khểnh không đồng thời Việc lấy kết dựa vào giá trị biến TT: TT=True - Hai Robot gặp nhau, TT=False - Hai Robot không gặp Trong trường hợp gặp thì dựa vào việc đã lưu thông tin ô gặp vào biến hm ,cm (hm - số hàng, cm - số cột) ta truy xuất đường Robot 137 (138) MỘT SỐ ỨNG DỤNG THUẬT TOÁN DIJKSTRA LỜI MỞ ĐẦU Lý thuyết đồ thị là phần quan trọng nội dung chương trình chuyên môn Tin học các trường chuyên Hầu các đề thi học sinh giỏi có các bài toán liên quan đến lý thuyết đồ thị, đó để học sinh có kết cao chúng ta cần trang bị cho các em tảng tốt các kỹ thuật cài đặt các bài toán lý thuyết đồ thị Trong tham luận này đề cập đến Một số ứng dụng thuật toán Dijkstra - tìm đường ngắn đỉnh s với tất các đỉnh đồ thị có trọng số không âm THUẬT TOÁN DIJKSTRA Bài toán: Cho G = (V, E) là đơn đồ thị có hướng gồm n đỉnh và m cung, trọng số trên các cung không âm Yêu cầu tìm đường ngắn từ đỉnh xuất phát s ∈ V đến đỉnh đích f ∈ V Thuật toán Dijkstra (E.Dijkstra - 1959) có thể mô tả sau: Bước 1: Khởi tạo Với đỉnh v ∈ V, gọi nhãn d[v] là độ dài đường ngắn từ s tới v Ban đầu d[v] khởi gán thuật toán Ford-Bellman (d[s] = và d[v] = +∞ với ∀v ≠ s) Nhãn đỉnh có hai trạng thái tự hay cố định, nhãn tự có nghĩa là có thể còn tối ưu và nhãn cố định tức là d[v] đã độ dài đường ngắn từ s tới v nên không thể tối ưu thêm Để làm điều này ta có thể sử dụng kỹ thuật đánh dấu: Free[v] = TRUE hay FALSE tuỳ theo d[v] tự hay cố định Ban đầu các nhãn tự Bước 2: Lặp Bước lặp gồm có hai thao tác: - Cố định nhãn: Chọn các đỉnh có nhãn tự do, lấy đỉnh u là đỉnh có d[u] nhỏ nhất, và cố định nhãn đỉnh u 138 (139) - Sửa nhãn: Dùng đỉnh u, xét tất đỉnh v và sửa lại các d[v] theo công thức: d [v] := min(d [v] , d [u] + c [u,v ]) Bước lặp kết thúc mà đỉnh đích f cố định nhãn (tìm đường ngắn từ s tới f); thao tác cố định nhãn, tất các đỉnh tự có nhãn là +∞ (không tồn đường đi) Có thể đặt câu hỏi, thao tác 1, đỉnh u cố định nhãn, giả sử d[u] còn có thể tối ưu thêm thì tất phải có đỉnh t mang nhãn tự cho d[u] > d[t] + c[t, u] Do trọng số c[t, u] không âm nên d[u] > d[t], trái với cách chọn d[u] là nhỏ Tất nhiên lần lặp đầu tiên thì s là đỉnh cố định nhãn d[s] = Bước 3: Kết hợp với việc lưu vết đường trên bước sửa nhãn, thông báo đường ngắn tìm cho biết không tồn đường (d[f] = +∞) for (∀v ∈ V) d[v] := +∞; d[s] := 0; repeat u := arg min(d[v]|∀v ∈ V); {Lấy u là đỉnh có nhãn d[u] nhỏ nhất} if (u = f) or (d[u] = +∞) then Break; {Hoặc tìm đường ngắn từ s tới f, kết luận không có đường} for (∀v ∈ V: (u, v) ∈ E) {Dùng u tối ưu nhãn đỉnh v kề với u} d[v] := (d[v], d[u] + c[u, v]); until False; Chú ý: Nếu đồ thị thưa (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ị, nhiên tốc độ thuật toán Dijkstra khá chậm vì trường hợp xấu nhất, nó cần n lần cố định nhãn và lần tìm đỉnh để cố định nhãn đoạn chương trình với độ phức tạp O(n) Để thuật toán làm việc hiệu hơn, người ta thường sử dụng cấu trúc liệu Heap để lưu các đỉnh chưa cố định nhãn 139 (140) Bài tập Bài 1: Ông Ngâu bà Ngâu Hẳn các bạn đã biết ngày "ông Ngâu bà Ngâu" hàng năm, đó là ngày đầy mưa và nước mắt Tuy nhiên, ngày trước đó, nhà Trời cho phép "ông bà" đoàn tụ Trong vũ trụ vùng thiên hà nơi ông Ngâu bà Ngâu ngự trị có N hành tinh đánh số từ đến N, ông hành tinh Adam (có số hiệu là S) và bà hành tinh Eva (có số hiệu là T) Họ cần tìm đến gặp N hành tinh nối với hệ thống cầu vồng Hai hành tinh có thể không có cầu vồng (hai chiều) nối chúng Họ luôn tới mục tiêu theo đường ngắn Họ với tốc độ không đổi và nhanh tốc độ ánh sáng Điểm gặp mặt họ có thể là hành tinh thứ nào đó Yêu cầu: Hãy tìm hành tinh cho ông Ngâu và bà Ngâu cùng đến đó lúc và thời gian đến là sớm Biết rằng, hai người có thể cùng qua hành tinh họ đến hành tinh đó vào thời điểm khác Dữ liệu: vào từ file văn ONGBANGAU.INP: - Dòng đầu là số N, M, S, T (N ≤ 100, ≤ S ≠ T ≤ N), M là số cầu vồng - M dòng tiếp, dòng gồm ba số nguyên I, J, L thể có cầu vồng nối hai hành tinh i và J có độ dài là L (1 ≤ I ≠ J ≤ N, < L ≤ 200) Kết quả: ghi file văn ONGBANGAU.OUT: tính chất cầu vồng, năm khác, nên không tồn hành tinh nào thoả mãn yêu cầu thì ghi dòng chữ CRY Nếu có nhiều hành tinh thoả mãn thì ghi hành tinh có số nhỏ Ví dụ: ONGBANGAU.INP ONGBANGAU.OUT 4414 121 241 132 342 Thuật toán: Ta có nhận xét: + Hai hành tinh bất kì nối đến nhiều cầu vồng + Ông Ngâu và bà Ngâu luôn tới mục tiêu theo đường ngắn + Họ với vận tốc không đổi và nhanh vận tốc ánh sáng 140 (141) Thực chất đây là bài toán đồ thị, ta có thuật toán sau: Từ hành tinh S (nơi ông Ngâu ở) ta xây dựng bảng SP, đó SP[i] là đường ngắn từ hành tinh S đến hành tinh i (do ông Ngâu luôn tới mục tiêu theo đường ngắn nhất) SP[i] = tức là không có đường từ hành tinh S đến hành tinh i Tương tự ta xây dựng bảng TP, đó TP[i] là đường ngắn từ hành tinh T đến hành tinh i Và TP[i] = tức là không có đường từ hành tinh T đến hành tinh i Do yêu cầu bài toán là tìm hành tinh khác S và T mà ông bà Ngâu cùng đến lúc và thời gian nhanh Tức là ta tìm hành tinh h cho (h khác S và T) và(SP[h] = ST[h] ) đạt giá trị nhỏ khác Nếu không có hành tinh h nào thoả mãn thì ta thông báo CRY Để xây dựng mảng SP và ST ta chọn giải thuật Dijkstra tìm đường ngắn đỉnh đồ thị Bài 2: Đôi bạn Trước Tuấn và Mai là hai bạn cùng lớp còn bây hai bạn học khác trường Cứ sáng, đúng hai từ nhà tới trường mình theo đường ít thời gian (có thể có nhiều đường thời gian và ít nhất) Nhưng hôm nay, hai bạn muốn gặp để bàn việc họp lớp cũ nhân ngày 20-11 Cho biết sơ đồ giao thông thành phố gồm N nút giao thông đánh số từ đến N và M tuyến đường phố (mỗi đường phố nối nút giao thông) Vị trí nhà Mai và Tuấn trường hai bạn nằm các nút giao thông Cần xác định xem Mai và Tuấn có cách nào thoả mãn yêu cầu nêu trên, đồng thời họ lại có thể gặp nút giao thông nào đó trên đường tới trường hay không ? (Ta nói Tuấn và Mai có thể gặp nút giao thông nào đó họ đến nút giao thông này cùng thời điểm) Nếu có nhiều phương án thì hãy phương án để Mai và Tuấn gặp sớm Dữ liệu: vào từ file văn FRIEND.INP - Dòng đầu tiên chứa số nguyên dương N, M (1  N  100); - Dòng chứa số nguyên dương Ha, Sa, Hb, Sb là số hiệu các nút giao thông tương ứng với: Nhà Tuấn, trường Tuấn, nhà Mai, trường Mai 141 (142) - Dòng thứ i số M dòng chứa số nguyên dương A, B, T Trong đó A và B là hai đầu tuyến đường phố i Còn T là thời gian (tính giây  1000) cần thiết để Tuấn (hoặc Mai) từ A đến B từ B đến A Giả thiết là sơ đồ giao thông thành phố đảm bảo để có thể từ nút giao thông đến tất các nút còn lại Kết : ghi file văn FRIEND.OUT - Dòng 1: Ghi từ YES hay NO tuỳ theo có phương án giúp cho hai bạn gặp hay không Trong trường hợp có phương án: - Dòng 2: Ghi thời gian ít để Tuấn tới trường - Dòng 3: Ghi các nút giao thông theo thứ tự Tuấn qua - Dòng 4: Ghi thời gian ít để Mai tới trường - Dòng 5: Ghi các nút giao thông theo thứ tự Mai qua - Dòng 6: Ghi số hiệu nút giao thông mà hai bạn gặp - Dòng 7: Thời gian sớm tính giây kể từ sáng mà hai bạn có thể gặp Ví dụ : Với sơ đồ giao thông sau: (N=6,M=7, Ha=1, Sa=6, Hb=2, Sb=5) Dòn g FRIEND.I NP 67 1625 10 10 235 345 15 20 15 FRIEND.O UT YES 25 146 30 2345 10 10 10 15 5 20 15 Thuật toán: Sử dụng thuật toán Dijkstra, xây dựng thủ tục: Dijkstra(start:intger, var d: mảng_nhãn); để xây dựng mảng nhãn d cho đường ngắn từ điểm xuất phát start đến đỉnh (có thể tới từ xuất phát) Sau đó gọi thủ tục này lần các lời gọi: 142 (143) Dijkstra(ha,d1); d1 cho biết các đường ngắn xuất phát từ nhà Tuấn Dijkstra(sa,d2); d2 cho biết các đường ngắn xuất phát từ nhà Mai Dijkstra(hb,d3); d3 cho biết các đường ngắn xuất phát từ trường Tuấn Dijkstra(sb,d4); d4cho biết các đường ngắn xuất phát từ trường Mai Điểm hẹn là nút u cần thỏa mãn các điều kiện sau: d1[u] + d3[u]=d1[sa] {thời gian Tuấn từ nhà tới điểm hẹn + Tuấn} d2[u] + d4[u] = d2[sb] {thời gian Mai từ nhà tới điểm hẹn + từ điểm hẹn tới trường Mai} d1[u] = d2[u] {thời gian từ nhà tới điểm hẹn Tuấn và Mai nhau} d1[u] nhỏ {thời gian Tuấn từ nhà tới điểm hẹn sớm nhất} Để ghi kết vào file FRIENDS.OUT, cần gọi thủ tục Dijkstra lần nữa: Dijkstra(u,d); mảng d(N) cho biết nhãn đường ngắn Bài 3: Đường giới hạn Một mạng giao thông gồm N nút giao thông đánh số từ đến N Với cặp nút i, j có đường hai chiều và trên đoạn đường đó, người ta quy định chiều cao nguyên không âm c[i,j] không lớn 6000 là chiều cao tối đa cho xe trên đoạn đường đó (c[i,j]=0 có nghĩa là không có đường từ i đến j) Cho hai nút s và t Hãy tìm hành trình từ s đến t qua các nút khác cho chiều cao cho phép tối đa với xe chạy trên hành trình đó là lớn có thể Dữ liệu: vào từ file văn HIGHT.INP : - Dòng thứ ghi số N, s, t (N<=100) - Tiếp theo là số dòng, dòng ghi số i, j, m với ý nghĩa có đường hai chiều từ i đến j với chiều cao cho phép h Kết quả: ghi file văn HIGHT.OUT 143 (144) - Dòng thứ ghi số h là chiều cao cho phép, h>0 số dòng tiếp theo, dòng ghi đỉnh trên hành trình từ s đến t với chiều cao tối đa cho phép là h ∞ Thuật toán: Gọi H[i] là chiều cao lớn có thể xe để từ s đến i Khởi tạo gán H[s]:=+∞ và H[i] =0 với i ≠ s Thuật toán sửa nhãn tương tự thuật toán Dijkstra Repeat u:=0; max:=0; for v:=1 to n if free[v] and (h[v] > max) then begin max:=h[v]; u:=v; end; if u=0 then break; free[u]:=false; for v:=1 to n if a[u,v] then if h[v] < min(h[u],c[u,v]) then begin h[v]:=min(h[u],c[u,v]); trace[v]:=u; end; until false; 144 (145) Bài 4: Tổng số đường ngắn Cho đồ thị vô hướng G gồm N đỉnh, M cạnh, cạnh có trọng số nguyên dương, hai đỉnh bất kì có không quá cạnh nối Cho trước hai đỉnh s và t, hãy tính số đường ngắn từ s đến t Hai đường khác thứ tự các đỉnh trên đường khác Thuật toán: Kết hợp Dijkstra với quy hoạch động - Theo thuật toán Dijkstra gọi d[i] là độ dài đường ngắn từ đỉnh s đến đỉnh i Khởi tạo d[i]=+∞ với i ≠ s và d[s]=0 - Quy hoạch động gọi f[i] là số đường ngắn từ đỉnh s đến đỉnh i Khởi tạo f[i]=0 với i ≠ s và f[s]=1 Trong chương trình Dijkstra: - Mỗi tìm đường có độ dài ngắn (d[v]>d[u]+c[u,v]) ta tiến hành thay đổi d[v]:=d[u]+c[u,v]) đồng thời f[v]:=f[u] - Mỗi tìm đường có độ dài (d[v]=d[u]+c[u,v]) ta thay đổi f[v]:=f[v]+f[u] Kết cần tìm là f[t] Đoạn chương trình Dijkstra kết hợp quy hoạch động For v:=1 to n d[v]:=maxlongint; d[s]:=0; For v:=1 to n f[v]:=0; f[s]:=1; Fillchar(Free,sizeof(Free),true); Repeat U:=0; mi:=maxlongint; For v:=1 to n If (Free[v]) and (d[v]<mi) then Begin mi:=d[v]; u:=v; end; If u=0 then break; Free[u]:=false; For v:=1 to n If Free[v] then If d[v]>d[u] + c[u,v] then 145 (146) Begin d[v]:=d[u] + c[u,v]; f[v]:=f[u]; end Else if d[v] = d[u] + c[u,v] then f[v]:=f[v] + f[u]; Until false; 146 (147) Sử dụng Dijkstra để đặt cận cho số bài toán duyệt Bài 5: ROADS N thành phố đánh số từ đến N nối với các đường chiều Mỗi đường có hai giá trị: độ dài và chi phí phải trả để qua Bob thành phố Bạn hãy giúp Bob tìm đường ngắn đến thành phố N, biết Bob có số tiền có hạn là K mà thôi Dữ liệu: vào từ file văn ROADS.INP Dòng đầu tiên ghi t là số test Với test: - Dòng đầu ghi số nguyên K (0 ≤ K ≤ 10000) là số tiền tối đa mà Bob còn có thể chi cho lệ phí đường - Dòng ghi số nguyên N (2 ≤ N ≤ 100) là số thành phố - Dòng ghi số nguyên R (1 ≤ R ≤ 10000) là số đường nối - Mỗi dòng N dòng sau ghi số nguyên S, D, L, T mô tả đường nối S và D với độ dài L (1 ≤ L ≤ 100) và chi phí T (0 ≤ T ≤ 100) Kết quả: ghi file văn ROADS.OUT Với test, in độ dài đường ngắn từ đến N mà tổng chi phí không quá K Nếu không tồn tại, in -1 Ví dụ: ROADS.INP 1223 2433 3424 1341 4621 3520 5432 4 1452 1210 2311 3410 ROADS.OUT 11 -1 147 (148) Thuật toán: Sử dụng thuật toán Dijkstra: - Lần 1: tìm đường ngắn (về khoảng cách) ngược từ đỉnh N các đỉnh khác để tạo mảng mindist - Lần 2: tìm đường ngắn (về chi phí tiền) ngược từ đỉnh N các đỉnh khác để tạo mảng mincost Hai mảng mindist và mincost dùng làm cận cho quá trình duyệt sau: Thực duyệt theo các đỉnh từ đỉnh Giả sử đã duyệt tới đỉnh i, và đã quãng đường là d và số tiền đã tiêu là t Ngay đầu thủ tục Duyet(i,d,t) đặt cận: Nếu (d+mindist[i]>= đường phương án tốt nhất) thì không cần duyệt tiếp phương án thời Nếu (t+mincost[i]>số tiền có Bob là k) thì không cần duyệt tiếp phương án thời Trong chương trình chính gọi thủ tục Duyet(1,0,0) Chú ý: Để quá trình tìm đỉnh duyệt nhanh chóng ta cần tổ chức danh sách kề Chương trình tham khảo: const fi = 'ROADS.INP'; fo = 'ROADS.OUT'; maxn = 100; infinity = 20000; maxtime = 180; type pt tnode m1 = ^tnode; = record v : byte; l, t : byte; next : pt; end; = array[1 maxn] of word; 148 (149) m2 = array[1 maxn, maxn] of word; var list : array[1 maxn] of pt; dd : array[1 maxn] of b∞lean; cost, dist : m2; mincost, mindist : m1; k : word; n : byte; best : word; f,g : text; t,test: longint; procedure init; var i, r, u, v, l, t : word; tmp : pt; begin readln(f, k); {so tien cua Bob} readln(f, n); {so pho} readln(f, r); {so duong} for u:=1 to n {khoi tri nhan gia tien , nhan khoang cach} for v:=1 to n begin cost[u, v]:=infinity; dist[u, v]:=infinity; end; {to chuc cac danh sach lien ket chieu cua cac duong Moi danh sach list[i] cho biet cac co duong truc tiep tu i sang} for i:=1 to n {khoi tri cac nut goc cua cac danh sach lien ket list[i]} list[i]:=nil; for i:=1 to r begin readln(f, u, v, l, t); new(tmp); tmp^.v:=v; tmp^.l:=l; tmp^.t:=t; 149 (150) tmp^.next:=list[u]; list[u]:=tmp; {so gian lai du lieu} if l < dist[u, v] then dist[u, v]:=l; if t < cost[u, v] then cost[u, v]:=t; end; end; procedure dijkstra(var a : m2; var dist : m1); {Thuat toan dijkstra tim khoang cach ngan nhat tu i toi N} var chua : array[1 maxn] of b∞lean; : word; i, j, last : byte; begin fillchar(chua, sizeof(chua), true); {mang danh dau da xet} for i:=1 to n dist[i]:=infinity; {khoi tri mang nhan} dist[n]:=0; {nhan cua dinh N} chua[n]:=false; {danh dau da xet N} last:=n; {last: dinh chua xet co nhan nho nhat} for i:=2 to n {n-1 lan sua nhan thi xong} begin {sua nhan cho cac dinh j chua xet dua vao nhan cua last} for j:=1 to n if chua[j] and (a[j, last] + dist[last] < dist[j]) then dist[j]:=dist[last] + a[j, last]; {tim dinh chua xet o nhan nho nhat} min:=infinity+1; for j:=1 to n if chua[j] and (dist[j] < min) then begin min:=dist[j]; last:=j; end; {danh dau da xet xong dinh last} chua[last]:=false; 150 (151) end; end; procedure try(last : byte; l, t : word); {Duyet tiep Bob da toi last, da di doan duong l, da tieu t xu} var tmp : pt; begin if (l + mindist[last] >= best) {dk can ve duong di} or (t + mincost[last] > k) then {dk can ve tien} exit; if last = n then {ve toi dich: Diem dung de quy} begin best:=l; exit; end; tmp:=list[last]; {tmp: last} while tmp <> nil {duyet chon cac de cu cho tiep theo last} begin if not dd[tmp^.v] then {thanh v chua qua} begin dd[tmp^.v]:=true; {danh dau da qua v} try(tmp^.v, l+tmp^.l, t+tmp^.t); {di tiep tu v} dd[tmp^.v]:=false; {quay lui} end; tmp:=tmp^.next; {de cu khac} end; end; procedure process; begin {xay dung cac mang cost va dist de lam can phuc vu duyet de quy} dijkstra(cost, mincost); dijkstra(dist, mindist); {khoi tri} best:=infinity; fillchar(dd, sizeof(dd), false); try(1, 0, 0); {duyet tu (duong da di =0, tien da tieu=0} end; 151 (152) procedure done; begin if best = infinity then writeln(g, -1) else writeln(g, best); end; BEGIN assign(f, fi); reset(f); assign(g, fo); rewrite(g); readln(f,test); for t:=1 to test begin init; process; done; end; close(f); close(g); END Bài 6: Du lịch Cho N thành phố đánh số từ đến N Một người muốn du lịch từ thành phố A đến thành phố B và sau đó quay lại A Người đó muốn trên đường từ B A không quay lại thành phố đã qua trên đường từ A đến B Hãy tìm cho người đó hành trình với chi phí ít Dữ liệu: vào từ file văn TOURIST.INP - Dòng thứ ghi số nguyên dương N, A, B (N<=100, 1<=A, B<=N) đó N là số thành phố, A là thành phố xuất phát, B là thành phố cần đến - Các dòng dòng ghi số nguyên dương i, j, k với ý nghĩa: thành phố i và thành phố j có đường trực tiếp và chi phí quãng đường đó là k Kết quả: ghi file văn TOURIST.OUT - Dòng thứ ghi chi phí tổng cộng trên hành trình tìm - Dòng thứ hai ghi các thành phố qua trên hành trình tìm theo đúng thứ tự, cách dấu cách Nếu không có cách nào thì ghi thông báo “No Solution” 152 (153) Ví dụ: TOURIST.INP TOURIST.OUT 10 14 122 10 232 342 452 561 671 781 891 10 10 1 195 935 375 755 Thuật toán: Dùng thuật toán duyệt có quay lui và đánh giá cận để tìm đường từ thành phố xuất phát A đến thành phố đích B 153 (154) Tại bước, thử chọn thành phố j vào hành trình đó, ta đánh dấu tất các thành phố đã qua thành phố A và thành phố j, sau đó dùng thuật toán Dijkstra để tìm độ dài đường (là chi phí) ngắn từ thành phố j quay thành phố A (không qua các thành phố đã đánh dấu) Nếu không tìm thấy đường thì gán chi phí là +∞ Nếu chi phí từ A đến thành phố j (tại bước quá trình duyệt) cộng với chi phí cho đường ngắn từ j A không tốt giá trị phương án tối ưu tìm trước đó thì loại phương án chọn thành phố j và thử sang phương án khác Tổ chức liệu: - Gọi X[0 N] là mảng lưu các thành phố qua quá trình duyệt, X[i] là thành phố qua bước thứ i tiến trình duyệt Đặc biệt X[0]:= A Để có nghiệm tối ưu, dùng mảng LX[0 N] lưu lại hành trình tốt duyệt - Gọi D là mảng đánh dấu, ta đánh dấu các số 0, 1, Khởi tạo ban đầu các đỉnh chưa đánh dấu ngoạit trừ đỉnh xuất phát A : D[i]=0 với i ≠A ; D[A]:=1; - Mảng Tien[0 N] có ý nghĩa: Tien[i] cho ta biết chi phí đến thành phố thứ i duyệt (là thành phố X[i]) Khởi tạo Tien[i]:=0 - Mỗi bước thử chọn thành phố j vào hành trình bước thứ i (D[j]=0), ta đặt chi phí tới thành phố j là Tien[i] chi phí thành phố trước đó là Tien[i-1] cộng với chi phí từ thành phố trước đó (là X[i-1]) tới thành phố j vừa chọn Đồng thời đánh dấu thành phố j đã qua là D[j]:=1; - Viết hàm Dijkstra(j) cho ta chi phí ít từ j A o Trước hết xóa đánh dấu cho đỉnh j và A: D[j]=D[A]=0 o Sau đó áp dụng thuật toán Dijkstra trên tập các đỉnh i có D[i]=0 Mỗi lần cố định nhãn cho đỉnh i ta đặt D[i]=2 o Trước kết thúc, đánh dấu lại đỉnh j và A, đồng thời đặt lại tất các D[i]=2 trở (nghĩa là phục hồi lại mảng đánh dấu D cũ để không làm hỏng tiến trình duyệt tiếp) o Để tăng tốc độ, hàm này không cần lưu vết đường mà cần trả lại độ dài đường ngắn (hàm này trả +∞ không có đường quay - Tại nút thứ i duyệt, ta đánh giá cận: Tien[i] + Dijkstra(X[i]) là độ dài đường từ A đến X[i] cộng với độ dài đường ngắn từ X[i] quay 154 (155) A Nếu số này nhỏ chi phí đường trước đó là MinT thì ta tiếp tục tìm kiếm, ngược lại thì không duyệt tiếp Khi đến B thì ghi nhận đường - Kết thúc duyệt, không ghi nhận đường nào (MinT=+∞) thì ghi “No Solution” Ngược lại, tìm đường từ A đến B (và có đường quay A không lặp lại thành phố nào) và chi phí trên chu trình là tối thiểu thì in đường từ A đến B (dựa vào mảng LX) và áp dụng thuật toán Dijkstra (lần này có lưu vết đường đi) để in đường quay từ B đến A Bài 7: Cuộc đua tiếp sức Vùng đất Alpha có N thành phố đánh số từ đến N Giữa hai thành phố có thể có đường nối trực tiếp không Các đường này đánh số từ tới M Ban lãnh đạo thể dục thể thao vùng Alpha tổ chức chạy đua tiếp sức “thông minh” theo quy luật sau: - Thành phố xuất phát là thành phố 1, thành phố đích là thành phố N - Mỗi đội thi đấu có K người dự thi Lần lượt người chạy từ thành phố thành phố N - Khi người thứ đến thành phố N thì người thứ hai bắt đầu rời khỏi thành phố 1, người thứ hai đến thành phố N thì người thứ ba bắt đầu rời khỏi thành phố 1, …, người thứ i đến thành phố N thì người thứ i+1 mời bắt đầu rời khỏi thành phố 1, , (i<K) Người thứ K tới đích thời điểm nào thì thời điểm đó coi là thời điểm đích toàn đội - Đường chạy các đội viên không giống hoàn toàn - Có thể chạy lại đoạn đường đã chạy Hãy viết chương trình tính thời gian nhỏ để đội hoàn thành chạy đua tiếp sức nêu trên các vận động viên có tốc độ chạy Dữ liệu: vào từ file văn RELAY.INP - Dòng đầu tiên ghi số nguyên dương K, N, M (2<=K<=40; 4<=N<=800; 1<=M<=4000) - M dòng tiếp theo, dòng chứa số nguyên i, j, w thể đường trực tiếp hai thành phố i và j thời gian chạy là w (đơn vị thời gian) (1<=i,j<=N; 1<=w<=9500) Kết quả: ghi file văn RELAY.OUT: 155 (156) - Dòng thứ chứa số nguyên là thời gian chạy nhỏ đội - K dòng tiếp theo, dòng thể hành trình chạy vận động viên đội là dãy số hiệu các thành phố liên tiếp trên hành trình đó Ví dụ: RELAY.INP RELAY.OUT 458 23 121 1325 132 135 142 12125 232 125 253 343 354 456 Thuật toán: Cải tiến từ thuật toán Dijkstra cổ điển Gọi L[i,j] là độ dài đường thứ j K đường ngắn từ đỉnh đến đỉnh i (i=1, 2, …, N; j=1, 2, ,k) Khởi tạo L[i,j] vô cùng với i, j và L[1,1]=0 - Mỗi lần tìm cặp (ii,jj) chưa đánh dấu có nhãn L[ii,jj] nhỏ - Từ (ii,jj) ta tiến hành sửa nhãn cho các cặp (i,j) thỏa mãn: i kề với ii , cặp (i,j) chưa đánh dấu và L[i,j] >= L[ii,jj] + C[ii,i] (*) Khi điều kiện (*) xảy thì đường ngắn thứ j tới đỉnh i thành đường ngắn thứ j+1 tới i và đường ngắn thứ j tới i thành đường qua ii trước, tới i 156 (157) Do đó với cặp (i,j) thỏa mãn (*) ta sửa nhãn cho cặp (i,j) và các cặp có liên quan sau: L[i,j+s]:=L[i,j+s-1] với s=1 đến k-j và L[i,j]=L[ii,jj] + C[ii,i] Tương tự cập nhật lại vết đường các cặp (i,j) - Đánh dấu cặp (ii,jj) đã cố định nhãn Quá trình lặp lại không còn cặp (i,j) nào chưa cố định nhãn cặp (n,k) đã cố định nhãn Sau cùng ta tính tổng độ dài tối ưu toàn đội K vận động viên Minpath = L[N,1] + L[N,2] + … + L[N.K] và tìm hành trình vận động viên dựa vào mảng theo dõi vết đường Với số đỉnh và số cạnh đồ thị tương đối lớn, cần tổ chức danh sách kề LUYỆN TẬP Bài 1: Đến trường Ngày 27/11 tới là ngày tổ chức thi học kỳ I trường ĐH BK Là sinh viên năm thứ nhất, Hiếu không muốn vì muộn mà gặp trục trặc phòng thi nên đã chuẩn bị khá kỹ càng Chỉ còn lại công việc khá gay go là Hiếu không biết đường nào tới trường là nhanh Thường ngày Hiếu không quan tâm tới vấn đề này cho nên bây Hiếu không biết phải làm Bản đồ thành phố là gồm có N nút giao thông và M đường nối các nút giao thông này Có loại đường là đường chiều và đường chiều Độ dài đường là số nguyên dương Nhà Hiếu nút giao thông còn trường ĐH BK nút giao thông N Vì lộ trình đường từ nhà Hiếu tới trường có thể gặp nhiều yếu tố khác là gặp nhiều đèn đỏ , qua công trường xây dựng, phải giảm tốc độ cho nên Hiếu muốn biết là có tất bao nhiêu lộ trình ngắn từ nhà tới trường Bạn hãy lập trình giúp Hiếu giải bài toán khó này Dữ liệu: vào từ file văn ROADS.INP - Dòng thứ ghi hai số nguyên N và M - M dòng tiếp theo, dòng ghi số nguyên dương K, U, V, L Trong đó: K = có nghĩa là có đường chiều từ U đến V với độ dài L 157 (158) K = có nghìa là có đường hai chiều U và V với độ dài L Kết quả: ghi file văn ROADS.OUT hai số là độ dài đường ngắn nhấT và số lượng đường ngắn Biết số lượng đường ngắn không vượt quá phạm vì int64 pascal hay long long C++ Ví dụ: ROADS.INP 32 1123 2231 ROADS.OUT 41 Giới hạn: ≤ N ≤ 5000 ≤ M ≤ 20000 Độ dài các đường ≤ 32000 Bài 2: HIWAY Một mạng giao thông gồm N nút giao thông, và có M đường hai chiều nối số cặp nút, thông tin đường gồm ba số nguyên dương u, v là tên hai nút đầu mút đường, và w là độ dài đoạn đường đó Biết hai nút giao thông bất kì có không quá đường hai chiều nhận chúng làm hai đầu mút Cho hai nút giao thông s và f, hãy tìm hai đường nối s với f cho hai trên hai đường không có cạnh nào qua hai lần và tổng độ dài đường là nhỏ Dữ liệu: vào từ file văn HIWAY.INP - Dòng đầu ghi N, M (N ≤ 100) - Dòng thứ ghi hai số s, f - M dòng tiếp theo, dòng mô tả đường gồm ba số nguyên dương u, v, w Kết quả: ghi file văn HIWAY.OUT - Dòng đầu ghi T là tổng độ dài nhỏ tìm -1 không tìm - Nếu tìm được, hai dòng sau, dòng mô tả đường gồm: số đầu là số nút trên đường này, là dãy các nút trên đường s, kết thúc f (Phạm vi tính toán vòng Longint) 158 (159) Ví dụ: HIWAY.INP 58 15 121 148 235 241 351 438 451 131 HIWAY.OUT 3135 41245 Bài 3: SHORTEST Một hệ thống giao thông gồm N thành phố và M đoạn đường chiều Các thành phố có số hiệu từ đến N Mỗi đoạn đường ta biết thành phố xuất phát và thành phố đích và độ dài Ta nói đoạn đường F là tiếp nối đoạn đường E thành phố đích đoạn đường E là thành phố xuất phát đoạn đường F Một hành trình từ thành phố A đến thành phố B là dãy liên tiếp các đoạn đường cho thành phố xuất phát đoạn đường đầu tiên là A, đoạn đường khác là tiếp nối đoạn đường trước đó và thành phố đích đoạn đường cuối cùng là thành phố B Độ dài hành trình là tổng độ dài các đoạn đường hành trình Một hành trình từ A đến B là hành trình ngắn không có hành trình nào từ A đến B có độ dài ngắn Yêu cầu: Với đoạn đường, cho biết có bao nhiêu hành trình ngắn chứa đoạn đường đó Dữ liệu: Cho tệp SHORTEST.INP gồm có: - Dòng đầu ghi hai số nguyên N và M (1 ≤ N ≤ 1500, ≤ M ≤ 5000), là số thành phố và số đoạn đường - Dòng thứ i M dòng tiếp chứa ba số nguyên Ui , Vi , Li tương ứng là thành phố xuất phát, thành phố đích và độ dài đoạn đường thứ i (các đoạn đường là chiều; các số Ui, Vi là khác và giá trị Li tối đa là 10000) 159 (160) Kết quả: Ghi tệp SHORTEST.OUT gồm có M dòng, đó dòng thứ i dòng ghi số nguyên Ci là số hành trình ngắn khác chứa đoạn đường thứ i (vì số Ci có thể là lớn nên bạn hãy viết nó dạng số dư 000 000 007) Ví dụ: SHORTEST.INP SHORTEST.OUT 43 125 235 345 44 125 235 345 148 58 20 132 232 423 423 345 435 20 160 (161) 161 (162) CÂY KHUNG CỦA ĐỒ THỊ Trong chương trình tin học chuyên thì đồ thị là vấn đề phong phú nhất, đa dạng nhất, khó nhất… và là nguồn cảm hứng chưa cạn không chúng tôi Vì năm chúng ta chọn vấn đề đồ thị làm đề tài nghiên cứu là lựa chọn hay Việc chọn vấn đề cây khung đồ thị để tìm tòi, nghiên cứu là seri các vấn đề cần nghiên cứu đồ thị Đồ thị là cấu trúc rời rạc gồm các đỉnh và các cạnh nối các đỉnh đó Mô hình đồ thị đã sử dụng từ lâu ngày lại có ứng dụng đại Những ý tưởng đồ thị nhà toán học người Thuỵ Sĩ Leonhard Euler đưa từ kỷ 18 để giải bài toán các cây cầu Konígberg tiếng Đồ thị dùng để giải các bài toán nhiều lĩnh vực khác Chẳng hạn, lĩnh vực giao thông có bài toán thực tế sau: Hệ thống đường giao thông địa phương nào đó biểu diễn đơn đồ thị Để đường có thể lại mùa đông thì cách là phải cào tuyết thường xuyên Chính quyền địa phương muốn cào tuyết trên số ít các đường cho cho luôn có đường thông suốt nối hai thành phố Có thể làm điều đó cách nào? A C B D E F 162 (163) Rõ ràng là phải cào tuyết trên ít năm đường đó là (A,C); (A,F); (A,B); (B,D); (B,E) Đây là sơ đồ biểu diễn tập các đường đó: A B C E D F Sơ đồ trên cho ta hình ảnh cây, gồm tất các đỉnh đồ thị biểu diễn hệ thống giao thông và số ít các cạnh nối các đỉnh để hệ thống thông suốt Đó chính là cây khung (câybao trùm) đồ thị Một đồ thị có thể có cây khung Từ bài toán thực tế trên mở hai vấn đề: Thứ nhất, từ đồ thị cho trước, tìm cây khung nó Thứ hai, cạnh đồ thị gán cho trọng số thì hãy tìm cây khung có tổng trọng số nhỏ Trong khuôn khổ văn này, chúng tôi xin trình bày cách giải các vấn đề nêu trên CÂY KHUNG CỦA ĐỒ THỊ 1.1 Định nghĩa cây Cây : là đồ thị hữu hạn, vô hướng, liên thông và không có chu trình Rừng: là đồ thị hữu hạn, vô hướng và không có chu trình Bụi: Đồ thị G=(X,U) hữu hạn, có hướng là bụi có gốc x Є X nó có ít hai đỉnh và thoả mãn điều kiện sau: 163 (164)  Mỗi đỉnh khác x1 là điểm cuối cung  Đỉnh x1 không là đỉnh cuối bất kì cung nào  Đồ thị không có chu trình Ví dụ: Quan sát các đồ thị đây: Dựa vào định nghĩa cây ta thấy: G1, G2 là cây; G3 không là cây có chu trình 1.2 Tính chất cây Định lý Nếu T là đồ thị vô hướng, n đỉnh (n>1) và T có sáu tính chất sau thì T là cây Mỗi tính chất là mệnh đề Khi đó, các mệnh đề sau là tương đương: (1) (2) (3) (4) (5) T là cây T không có chu trình và có (n-1) cạnh T có (n-1) cạnh và liên thông T liên thông và cạnh T là cạnh cắt (cầu) Hai đỉnh bất kì T nối với đúng đường đơn 164 (165) (6) T không chứa chu trình thêm cạnh bất kì vào T thì ta thêm đúng chu trình Chứng minh định lý: (1) → (2): T là cây → T không chứa chu trình và có (n-1) cạnh  Hiển nhiên T không chứa chu trình (do T là cây)  Ta cần chứng minh T có (n-1) cạnh  Xét Tn là cây có n đỉnh Ta chứng minh quy nạp theo n: o n = Cây có đỉnh thì có cạnh Đúng o Giả sử cây có k đỉnh thì có (k-1) cạnh o Xét Tk+1 là cây có (k+1) đỉnh Dễ thấy cây này luôn tồn ít đỉnh treo o Loại đỉnh treo này (cùng với cạnh nối) khỏi Tk+1 ta đồ thị T’ có k đỉnh Dễ thấy, T’ liên thông và không có chu trình (do Tk+1 không có chu trình) o Suy T’ là cây Theo giả thiết quy nạp, T’ có k đỉnh thì có (k1) cạnh Vậy Tk+1 có k cạnh (đpcm) (2) → (3): T không có chu trình và có (n-1) cạnh → T liên thông và có (n1) cạnh Hiển nhiên T có (n-1) cạnh (theo giả thiết) Ta cần chứng minh T liên thông Giả sử T có k thành phần liên thông với số đỉnh là n1, n2,…, nk Khi đó thành phần liên thông T là cây và có số cạnh là n1-1, n2-1,…, nk-1  Suy ra, số cạnh T là: n1-1 + n2-1 +…+ nk-1 = n-k  Theo giả thiết, số cạnh cây là (n-1) Từ đó suy k=1 hay T có thành phần liên thông Suy ra, T liên thông (đpcm) (3) → (4): T có (n-1) cạnh và liên thông → T liên thông và cạnh T là cạnh cắt (cầu)      Hiển nhiên T liên thông (theo giả thiết) 165 (166)  Ta cần chứng minh cạnh T là cạnh cắt  Xét (u,v) là cạnh bất kì T Nếu bỏ (u,v) khỏi T thì ta đồ thị T’ có n đỉnh và (n-2) cạnh  Ta đã chứng minh đồ thị có n đỉnh và (n-2) cạnh thì không thể liên thông  Vậy bỏ cạnh (u,v) thì làm tình liên thông đồ thị Suy (u,v) là cạnh cắt (đpcm) (4) → (5): T liên thông và cạnh T là cạnh cắt (cầu) → Hai đỉnh bất kì T nối với đúng đường đơn  Xét u,v là đỉnh bất kì T  Do T liên thông nên luôn tồn đường u,v Ta chứng minh đường này là  Giả sử có hai đường đơn khác u và v Khi đó hai đường này tạo thành chu trình  Suy các cạnh trên chu trình này không thể là các cạnh cắt  Vậy u và v tồn đúng đường đơn (đpcm) (5) → (6): Hai đỉnh bất kì T nối với đúng đường đơn → T không chứa chu trình thêm cạnh bất kì vào T thì ta thêm đúng chu trình  T không thể có chu trình, vì có chu trình thì đỉnh trên chu trình này có hai đường đơn khác → Mâu thuẫn với giả thiết  Giả sử ta thêm vào T cạnh (u,v) bất kì (trước đó không có cạnh này T)  Khi đó cạnh này cùng với đường u và v T tạo thành chu trình (vì tạo hai chu trình thì chứng tỏ trước đó có hai đường khác u và v → Mâu thuẫn với giả thiết) (6) → (1): T không chứa chu trình thêm cạnh bất kì vào T thì ta thêm đúng chu trình → T là cây  Hiển nhiên T không chứa chu trình 166 (167)  Giả sử T không liên thông Khi đó T có nhiều thành phần liên thông  Suy ra, thêm vào cạnh bất kì hai đỉnh thuộc hai thành phần liên thông khác không tạo thêm chu trình nào → Mâu thuẫn với giả thiết  Vậy T phải liên thông → T là cây (đpcm) Định lí Một bụi thay các cung cạnh thì thành cây 1.3 Cây khung đồ thị Định nghĩa cây khung: Cho đồ thị vô hướng G=(X,E) liên thông, có n đỉnh (n>1) Mỗi đồ thị phận G là cây thì gọi là cây khung đồ thị G (hoặc cây bao trùm) Ví dụ: Đồ thị và các cây khung nó Định lí: Mọi đồ thị vô hướng có số đỉnh n>1 liên thông và nó có cây khung Chứng minh định lí: 167 (168)  Nếu G có chứa cây khung thì tính chất cây khung là liên thông và cây khung chứa tất các đỉnh G Suy các đỉnh G luôn nối với hay G liên thông  Xét G liên thông Giả sử G còn tồn chu trình, xoá bớt cạnh chu trình này, đó đồ thị còn liên thông Nếu còn chu trình thì lặp lại bước trên Cứ không còn chu trình Khi đó ta cây khung 1.4 Thuật toán tìm cây khung 1.4.1 Bài toán Cho đồ thị G liên thông, vô hướng, hãy tìm cây khung nó  Dữ liệu: số đỉnh và danh sách các cạnh đồ thị  Kết quả: các cạnh cây khung CK.inp CK.out 16 16 23 23 45 45 25 25 15 15 12 65 53 1.4.2 Thuật toán Thuật toán 1: 168 (169) Ý tưởng: Duyệt và thăm các đỉnh, đỉnh lần Vì đồ thị liên thông nên thăm đủ n đỉnh (cùng lúc với đã qua n-1 cạnh , ta cây khung) Có thể dùng thuật toán DFS BFS để thăm các đỉnh Cài đặt: program tim_caykhung_bang_DFS; const fi='CayKhung.inp'; fo='CKhung.DFS.out'; MN=1000; var A:array[1 MN,1 MN] of longint; vs:array[1 MN] of boolean; n,m:integer; procedure nhap; var i,j:integer; begin assign(input,fi); reset(input); readln(n,m); while not seekeof(input) begin readln(i,j); a[i,j]:=1; A[j,i]:=1; end; close(input); end; // procedure DFS_VS(i:integer); var j:integer; begin vs[i]:=true; for j:=1 to n if not vs[j] and (a[i,j]=1) then begin writeln(i,' ',j); DFS_VS(j); end; //doc cac canh cua thi 169 (170) end; // Begin nhap; fillchar(vs, sizeof(vs),false); assign(output,fo); rewrite(output); DFS_VS(1); close(output); end Thuật toán 2: Hợp dần các vùng liên thông Ý tưởng: Mỗi lần hợp hai vùng liên thông khác cạnh nối hai vùng này thì nạp cạnh đó vào cây khung hình thành Quá trình chấm dứt nạp đủ (n-1) cạnh Thực cụ thể: Bước 1: Coi đỉnh thuộc vùng có mã vùng là v[i]=i, số cạnh đã nạp vào cây khung là sl=0 Bước 2: Duyệt tất các cạnh đồ thị:  Nếu sl=n-1 thì dừng vòng lặp duyệt  Nếu cạnh (i,j) có đỉnh i và j khác mã vùng (v[i]≠v[j]) thì: o Nếu v[i]<v[j]: tất các đỉnh cùng mã vùng với j gán lại mã vùng là v[i], nạp vào cây khung cạnh (i,j), tăng biến sl đơn vị o Nếu v[i]>v[j]: tất các đỉnh cùng mã vùng với i gán lại mã vùng là v[j], nạp vào cây khung cạnh (i,j), tăng biến sl đơn vị Cài đặt: program CayKhung; const fi='CayKhung.inp'; fo='CK.out'; 170 (171) var b,dau,cuoi:array[1 10000] of longint; i,j,k,n,t,sc:longint; f:text; procedure nhap; begin assign(f,fi); reset(f); readln(f,n); for i:=1 to n b[i]:=i; sc :=0; while not eof(f) {doc cac canh cua thi} begin readln(f,i,j); if b[i] <> b[j] then {khac ma vung lien thong} begin inc(sc); dau[sc] := i; {tang so canh} {nap them mot canh vao mang dau va cuoi} cuoi[sc] := j; if b[i]<b[j] then for t:=1 to n {hop nhat vung lien thong} if b[t]=b[j] then b[t]:=b[i]; if b[i]>b[j] then 171 (172) for t:=1 to n if b[t]=b[i] then b[t]:=b[j]; end; if sc = n-1 then break; {nap du n-1 canh thi dung lai} end; close(f); end; procedure xuat; begin assign(f,fo); rewrite(f); for i := to n -1 writeln(f,dau[i],' ',cuoi[i]); close(f); end; begin nhap; xuat; end Thuật toán 3: Hợp dần các cây Ý tưởng: Mỗi lần hợp hai cây có gốc khác cạnh đồ thị (nối hai đỉnh thuộc hai cây này)thì cạnh đó xác nhận là cạnh cây khung hình thành Quá trình kết thúc nạp đủ n-1 cạnh cây khung 172 (173) Cài đặt: program CayKhung; {su dung thuat toan hop nhat hai cay} const fi='Caykhung.inp'; fo='CKhung.out'; MN=5000; var cha,dau,cuoi:array[1 MN] of integer; n,m,socanh:longint; // -function root(x:integer):integer; //tim goc cay chua dinh x var i:integer; begin i:=x; while cha[i]>0 i:=cha[i]; exit(i); end; // -procedure Union(x,y:integer); // hop nhat hai cay oc x, goc y var temp:integer; begin temp:=cha[x]+cha[y]; if cha[x]>cha[y] then // cay chua dinh x co it nut hon begin cha[x]:=y; //tam coi y la cha cua x cay hop nhat cha[y]:=temp; //goc moi cua cay la y cay chua dinh x co it nut hon end else begin //cay chua dinh y co it nut hon cha[y]:=x; cha[x]:=temp; end; end; // -procedure nhap_taocay; var i, x, y,r1,r2:longint; begin assign(input,fi); reset(input); 173 (174) readln(n); for i:=1 to n cha[i]:=-1; // moi dinh la cay co goc la chinh no socanh :=0; while not seekeof(input) //doc cac canh cua thi begin if socanh=n-1 then exit; //la cay khung, ket thuc readln(x,y); r1:=root(x); r2:=root(y); if r1 <> r2 then //hai cay co goc khac nha begin inc(socanh); //tang so canh dau[socanh] := x; //nap them mot canh vao mang dau va cuoi cuoi[socanh] := y; union(r1,r2); //hop nhat hai cay end; end; close(input); end; procedure xuat; var i:integer; begin assign(output,fo); rewrite(output); writeln(socanh); for i := to n -1 writeln(dau[i],' ',cuoi[i]); close(output); end; begin nhap_taocay; xuat; end 1.5 Thuật toán tìm cây khung ngắn 1.5.1 Bài toán 174 (175) Định nghĩa: Cho đồ thị G vô hướng, liên thông và có trọng số không âm Cây khung ngắn đồ thị G là cây khung có tổng trọng số trên các cạnh nó nhỏ (gọi là trọng số cây khung ngắn nhất) Bài toán: Cho đồ thị G vô hướng, liên thông và không có trọng số âm Tìm cây khung ngắn đồ thị G Dữ liệu: số đỉnh, danh sách các cạnh kèm theo trọng số cạnh (mỗi dòng mô tả cạnh gồm số i, j, l có nghĩa cạnh nối đỉnh i và j thì có trọng số là l) Kết quả: các cạnh cây khung ngắn và trọng số cây khung ngắn Ck.inp Ck.out Cay khung la: 122 (1,4) 132 (3,4) 141 (1,2) 154 (2,5) 235 Tong so : 243 253 342 354 458 Để giải bài toán cây khung ngắn có thuật toán thông dụng: Kruskal và Prim 1.5.2 Thuật toán Kruskal 175 (176) Ý tưởng: Nạp dần các cạnh ngắn và cây khung cạnh không tạo thành chu trình với các cạnh đã nạp Thuật toán:  Sắp xếp các cạnh tăng dần (thường dùng Quicksort)  Lân lượt kết nạp các cạnh có trọng số nhỏ các cạnh còn lại vào cây sau kết nạp cạnh này không tạo thành chu trình cây Để thực yêu cầu này, ta có thể sử dụng thuật toán hợp các vùng liên thông trên Quá trình này dừng kết nạp n-1 cạnh vào cây Cài đặt: program Kruskal; const fi='ck.inp'; fo='ck.out'; type canh = record d,c,l : longint; end; var b:array[1 10000] of longint; a,ck : array[1 10000] of canh; i,j,k,n,m,t,sc,sum:longint; f:text; Procedure QuickSort(dau, cuoi : longint); var x, L, R : longint; tmp : canh; begin 176 (177) x := a[(dau+cuoi) div 2].l; L := dau; R := cuoi; repeat while a[L].l < x inc(L); while a[R].l > x dec(R); if L <= R then begin tmp := a[L];a[L] := a[R]; a[R] := tmp; inc(L);dec(R); end; until L > R; if R > dau then QuickSort(dau,R); if L < cuoi then QuickSort(L,cuoi); end; procedure nhap; begin assign(f,fi); reset(f); readln(f,n); m := 0; while not eof(f) {doc cac canh cua thi} begin inc(m); readln(f,a[m].d,a[m].c,a[m].l); end; 177 (178) close(f); end; procedure xuli; begin QuickSort(1,m); for i:=1 to n b[i]:=i; sc :=0;sum := 0; for k := to m begin i := a[k].d;j:= a[k].c; if b[i] <> b[j] then {khac ma vung lien thong} begin inc(sc); {tang so canh} ck[sc] := a[k]; {them canh vao cay khung} sum := sum + a[k].l; if b[i]<b[j] then for t:=1 to n {hop nhat vung lien thong} if b[t]=b[j] then b[t]:=b[i]; if b[i]>b[j] then for t:=1 to n if b[t]=b[i] then b[t]:=b[j]; end; 178 (179) if sc = n-1 then break; {nap du n-1 canh thi dung lai} end; end; procedure xuat; begin assign(f,fo); rewrite(f); writeln(f,'Cay khung la: '); for i := to n -1 writeln(f,' (',ck[i].d,',',ck[i].c,')'); writeln(f,'Tong so : ',sum); close(f); end; begin nhap; xuli; xuat; end 1.5.3 Thuật toán Prim Ý tưởng: Nạp dần tập cách đỉnh vào cây khung Mỗi lần chọn đỉnh chưa nạp là đỉnh kề và gần các đỉnh đã nạp Thuật toán: Bước 1: Nạp đỉnh đầu tiên vào cây khung (thường là đỉnh 1) 179 (180) Bước 2: Lần lượt nạp n-1 đỉnh còn lại (tương ứng với n-1 cạnh) vào cây khung cách: lần chọn cạnh có trọng số nhỏ mà đầu cạnh đã thuộc cây, đầu chưa thuộc cây (nghĩa là chọn đỉnh gần các đỉnh đã nạp nhất) Cài đặt program Prim; const max=100; f1='ck.inp'; f2='ck.out'; var a: array[1 max,1 max] of integer; d1,d2,d:array[1 max] of integer; n: integer; procedure nhap; var g:text; i,j,x:integer; begin assign(g,f1); reset(g); readln(g,n); while not seekeof(g) begin readln(g,i,j,x); a[i,j]:=x; a[j,i]:=x; end; 180 (181) close(g); end; procedure timcanh( var i,j:integer); {Tim canh i, j ngan nhat} var x,y,min:integer; begin min:=maxint; for x:=1 to n if d[x]=1 then for y:=1 to n if d[y]=0 then if (a[x,y]>0) and (a[x,y]<min) then begin i:=x; j:=y; min:=a[x,y]; end; end; procedure prim; var i,j,k:integer; begin for i:=1 to n d[i]:=0; d[1]:=1; 181 (182) for k:=1 to n-1 begin timcanh(i,j); d[j]:=1; d1[k]:=i; d2[k]:=j; end; end; procedure ghi; var g:text; i,tong: integer; begin assign(g,f2); rewrite(g); tong:=0; writeln(g,'Cay khung la : '); for i:=1 to n-1 begin writeln(g,d1[i],' ',d2[i]); tong:=tong+a[d1[i],d2[i]]; end; writeln(g,'Tong so: ',tong); close(G); end; begin nhap; prim; ghi; end 182 (183) MỘT SỐ BÀI TOÁN ỨNG DỤNG Bài toán Mạng rút gọn Một hệ thống gồm N máy tính nối thành mạng có M kênh nối, kênh nối hai máy tính mạng, hai máy tính có không quá kênh nối Các máy tính đánh số từ đến N, các kênh nối đánh số từ đến M Việc truyền tin trực tiếp có thể thực hai máy có kênh nối Các kênh nối mạng chia thành ba loại 1, và Ta nói hai máy a và b mạng có đường truyền tin loại k (k=1 k=2) tìm dãy các máy (a=v1, v2, …,vp=b) thoả mãn điều kiện: hai máy vi và vi+1 có kênh nối loại k có kênh nối loại (i=1, 2, , p-1) Yêu cầu: Cần tìm cách loại bỏ khỏi mạng số nhiều kênh nối đảm bảo luôn tìm đường truyền loại lẫn đường truyền tin loại hai máy mạng.Dữ liệu vào từ tệp văn MRG.INP sau: - Dòng đầu tiên chứa hai số N, M (N≤500); M≤10000) - Dòng thứ i M dòng chứa ba số nguyên dương u, v, s cho biết kênh thứ i nối hai máy u và v thuộc loại s Kết ghi tệp văn MRG.OUT gồm: - Dòng đầu tiên ghi số r là số kênh cần loại bỏ - Nếu r=-1 thì có nghĩa là mạng đã cho tồn hai máy không có đường truyền loại loại - r=0 có nghĩa là mạng có đường truyền thoả mãn số kênh loại bỏ - Nếu r>0 thì r dòng tiếp theo, dòng ghi số kênh cần loại bỏ 183 (184) Các số trên cùng dòng các tệp liệu và tệp kết cách ít dấu cách Ví dụ: MRG.INP MRG.OUT 57 123 233 342 532 541 522 151 Cách giải + Xây dựng đồ thị vô hướng với đỉnh là máy tính, có N đỉnh Mỗi cạnh là kênh M kênh, có M cạnh; trọng số trên cạnh là loại kênh (1, 2, 3) + Dùng thuật toán hợp dần các cây (Thuật toán 3) để tìm cây khung Cụ thể sau: - Vì đường loại và theo yêu cầu phải chứa kênh loại nên ta tìm rừng cây gồm cạnh loại 3, gọi là (R3) - Nếu rừng cây đó là cây khung (tức là có n-1 cạnh) thì bài toán có nghiệm và loại bỏ tất các cạnh loại và loại 184 (185) - Nếu rừng cây đó chưa là cây khung thì phải xem xét bổ sung cạnh loại loại vào rừng cây đó để mạng có đường truyền loại loại Tiến hành các việc sau: Cùng với R3, xét thêm các cạnh loại 1, thực lại thuật toán để xem có tạo thành cây khung (chỉ gồm cạnh loại và 3) không Nếu không là cây khung thì vô nghiệm (r=-1); có thì thực tiếp bước 2: Cùng với R3, xét thêm các cạnh loại 2, thực tiếp thuật toán xem có tạo thành cây khung (chỉ gồm cạnh loại và 2) không Nếu không thì vô nghiệm (r=-1); Nếu có cây khung thì bài toán có nghiệm, các cạnh cần loại bỏ là các cạnh loại và không thuộc cây khung tìm thấy trên Để thực việc này cần đánh dấu các cạnh đã nạp vào cây khung Văn chương trình program mang_rut_gon; const fi='MRG.IN2'; fo='MRG.OUT'; MN=500; MM=10000; type canh=record u,v:integer; w:shortint end; var m,n:integer; socanh, lsc: integer; //so canh, luu so canh l, ll:array[1 MN] of integer; //nhan cua dinh, luu nhan ddinh ds:array[1 MM] of canh; //danh sach canh caykhung:boolean; //co la cay khung hay khong // -procedure readf; var i:integer; begin assign(input,fi); reset(input); readln(n,m); for i:=1 to m 185 (186) with ds[i] readln(u,v,w); close(input); for i:=1 to n l[i]:=-1; // moi cay co goc la chinh no end; // function root(u:integer):integer; //tra ve goc cay chua u begin while l[u]>=0 u:=l[u]; exit(u); end; // procedure union(r1,r2:integer); //hop nhat hai cay co goc la r1, r2 var x:integer; begin x:=l[r1]+l[r2]; //nhan cua goc cay hop nhat if l[r1]>l[r2] then begin l[r1]:=r2; l[r2]:=x end else begin l[r2]:=r1; l[r1]:=x; end; end; // function KRUSKAL(k:integer):boolean; //co la cay khung them canh loai k khong var i, r1,r2:integer; begin for i:=1 to m with ds[i] if w=k then begin r1:=root(u); //goc cua cay chua dinh u r2:=root(v); //goc cay chua dinh v if r1<>r2 then begin w:=-w; // danh dau da canh da nap vao cay 186 (187) inc(socanh); if socanh=n-1 then exit(true); //la cay khung union(r1,r2); // chua la cay thi hop nhat hai cay end; end; exit(false); end; // procedure writef; var i:integer; begin assign(output,fo); rewrite(output); if not caykhung then writeln(-1) //khong co duong truyen thoa man else begin writeln(m-n+n-2-lsc); for i:=1 to m if ds[i].w>0 then writeln(i); end; close(output); end; // -begin readf; socanh:=0; caykhung:=KRUSKAL(3); if not caykhung then begin lsc:=socanh; ll:=l; caykhung:=KRUSKAL(1); socanh:=lsc; l:=ll; caykhung:=caykhung and KRUSKAL(2); end; writef; end Bài toán Mạng giao thông 187 (188) Theo thiết kế, mạng giao thông gồm N nút có số hiệu từ đến N (N≤1000) Chi phí để xây dựng đường hai chiều trực tiếp từ nút i đến nút j A[i,j]=A[j,i] Hai tuyến đường khác không cắt các điểm không là đầu mút Hiện đã xây dựng K tuyến đường Bài toán đặt sau: Hệ thống đường đã xây dựng có bảo đảm lại hai nút bát kỳ chưa? Nếu chưa, hãy chọn số tuyến đường cần xây dựng thêm cho: Các tuyến đường xây dựng thêm cùng với K tuyến đường đã xây dựng bảo đảm lại hai nút Tổng kinh phí xây dựng thêm các tuyến đường là ít Dữ liệu vào từ tệp văn MGT.INP sau: - Dòng đầu tiên chứa hai số N, K (N≤500); M≤10000) - Trong K dòng chứa hai số nguyên dương là số hiệu hai nút, đó là các tuyến đường đã xây dựng - Cuối cùng là N dòng, dờng thứ i ghi N số A[i,1], A[i,2], …, A[i,N] Kết ghi tệp văn MGT.OUT gồm: - Dòng đầu tiên ghi số CP là chi phí xây dựng thêm - Nếu CP>0 thì N dòng tiếp theo, dòng ghi hai số là số hiệu hai nút, đó là hai đầu tuyến đường cần xây dựng thêm Các số trên cùng dòng các tệp liệu và tệp kết cách ít dấu cách Ví dụ: MGT.INP MGT.OUT 54 188 (189) 12 34 23 31 45 01111 10111 11011 11101 11110 Cách giải: + Dựa vào mạng giao thông xây dựng đồ thị vô hướng, có trọng số: - Mỗi đỉnh đồ thị là nút giao thông (N đỉnh); - Mỗi cạnh là đoạn đường trực tiếp nối nút; - Trọng số trên cạnh tương ứng với đoạn đường đã xây dựng 0; trên cạnh chưa xây dựng chi phí xây dựng quãng đường tương ứng + Tìm cây khung ngắn trên đồ thị Nếu trọng số cây 0, có nghĩa là K đoạn đường đã xây dựng đã đảm bảo lại hai nút (đồ thị đã liên thông) Ngược lại, trọng số khác 0, thì trên cây có đoạn đường chưa xây dựng (là cạnh có trọng số khác 0) Đó chính là đoạn đường cần xây dựng thêm + Văn chương trình Program MangGiaoThong; Const Fi='MGT.INP'; 189 (190) Fo='MGT.OUT'; nm=100; Var f:text; n:integer; a:array[1 nm,1 nm] of longint; tr:array[1 nm] of integer; vs:array[1 nm] of boolean; res:longint; Procedure Nhap; var i,j:integer; k:longint; begin assign(f,fi); reset(f); readln(f,n,k); fillchar(a,sizeof(a),255); while k>0 begin readln(f,i,j); a[i,j]:=0; a[j,i]:=0; dec(k); end; for i:=1 to n for j:=1 to n begin read(f,k); if a[i,j]=-1 then a[i,j]:=k; end; close(f); end; Procedure Prim; var i,j,sc:integer; min:longint; begin for i:=1 to n tr[i]:=1; fillchar(vs,sizeof(vs),false); vs[1]:=true; res:=0; for sc:=1 to n-1 190 (191) begin min:=High(longint); for i:=1 to n if not vs[i] and (a[tr[i],i]<min) then begin min:=a[tr[i],i]; j:=i; end; vs[j]:=true; res:=res+a[tr[j],j]; for i:=1 to n if not vs[i] and (a[j,i]<a[tr[i],i]) then tr[i]:=j; end; end; Procedure Xuat; var i:integer; begin assign(f,fo); rewrite(f); writeln(f,res); for i:=1 to n if a[tr[i],i]<>0 then writeln(f,tr[i],' ',i); close(f); end; Begin Nhap; Prim; Xuat; End Bài toán Tìm cây khung dài Các thuật toán Kruslal và Prim không đòi hỏi dấu trọng số Vì ta có thể áp dụng với cây có các cạnh có trọng số dấu tuỳ ý Do đó, để tìm cây khung dài ta việc đổi dấu tất các trọng số và áp dụng hai thuật toán trên để tìm cây khung nhỏ trên đồ thị xây dựng Cuối cùng việc đổi dấu 191 (192) số cây khung Tính đúng đắn thuật toán là hiển nhiên vì số lớn thì dẫn đến số đối nó phải nhỏ Bài toán Tìm mạng điện với tin cậy lớn Bài toán: Cho lưới điện có N nút đường dây nối nút i với nút j có độ tin cậy là số thực 0<Pij<1 Độ tin cậy toàn lưới điện tích độ tin cậy trên tất các đường dây Hãy tìm cây khung với độ tin cậy lớn Cách giải: + Xây dựng đồ thị vô hướng, có trọng số với số đỉnh là số nút lưới điện, cạnh (i,j) đồ thị là đoạn đường dây nối hai nút i và j Trọng số trên cạnh (i,j) gán –ln(Pij) + Tìm cây khung nhỏ đồ thị vừa xây dựng hai thuật toán Kruskal Prim + Tính đúng đẵn thuật toán chứng minh nhờ tính chất logarit sau: Ta có công thức toán học: ln(x1.x2 xN)=ln(x1)+ln(x2)+…+ln(xN) Vì nên thực tìm độ tin cậy lớn mạng điện thay vì tính tích các trọng số trên cây khung T, ta đưa tính tổng của các trọng số Khi đó ta có tổng trọng số trên cây khung ngắn T là số âm nhỏ Khi đổi dấu đó là số lớn Bài toán Tìm cây khung ngắn trên đồ thị, sử dụng câu trúc HEAP + Biểu diễn đồ thị ban đầu danh sách kề với trọng số Khi đó có thể cài đặt đồ thị với số đỉnh lớn (10000 đỉnh) 192 (193) + Áp dụng thuật toán Prim tìm cây khung ngắn trên đồ thị vừa xây dựng, với thao tác tìm đỉnh gần nhất, tức là cạnh liên thuộc có trọng số nhỏ cách sử dụng cấu trúc HEAP Vì mặc dù với đồ thị có số đỉnh lớn chương trình đáp ứng yeu cầu thời gian + Cài đặt cụ thể Program CayKhungNhoNhat_Heap; Const Fi='caykhung.INP'; Fo='CK_HEAP.INP'; nm=10000; mm=15000; vc=10001; Var f:text; n,m,nH:longint; ke,t:Array[1 mm*2] of longint; index:array[0 nm] of longint; minc:array[2 nm] of longint; c1,c2,c,info,pos:array[1 mm] of longint; res:longint; Procedure var Nhap; i:longint; begin assign(f,fi); reset(f); 193 (194) readln(f,n,m); for i:=1 to m readln(f,c1[i],c2[i],c[i]); close(f); end; Procedure TaoKe(x,y,c:longint); begin ke[index[x-1]]:=y; t[index[x-1]]:=c; dec(index[x-1]); end; Procedure var Chuyen; {Chuyen tu Ds canh -> Ds ke} i:longint; begin fillchar(index,sizeof(index),0); for i:=1 to m begin inc(index[c1[i]-1]); inc(index[c2[i]-1]); end; for i:=1 to n index[i]:=index[i-1]+index[i]; for i:=1 to m begin TaoKe(c1[i],c2[i],c[i]); 194 (195) TaoKe(c2[i],c1[i],c[i]); end; end; Procedure var Swap(var a,b:longint); tg:longint; begin tg:=a; a:=b; b:=tg; end; Procedure var UpHeap(i:longint); c,r:longint; begin c:=pos[i]; while c>1 begin r:=c div 2; if minc[info[c]]<minc[info[r]] then begin Swap(info[c],info[r]); pos[info[c]]:=c; pos[info[r]]:=r; c:=r; end else break; 195 (196) end; end; Procedure var DownHeap(i:longint); c,r:longint; begin r:=pos[i]; while r*2<=nH begin c:=r*2; if(c<nH)and(minc[info[c]]>minc[info[c+1]])then inc(c); if minc[info[c]]<minc[info[r]] then begin Swap(info[c],info[r]); pos[info[c]]:=c; pos[info[r]]:=r; r:=c; end else break; end; end; Procedure Insert(i:longint); begin inc(nH); info[nH]:=i; 196 (197) pos[i]:=nH; UpHeap(i); end; Procedure Del; begin pos[info[1]]:=0; info[1]:=info[nH]; pos[info[1]]:=1; dec(nH); DownHeap(info[1]); end; Procedure var Prim; i,j,sc:longint; begin for i:=2 to n minc[i]:=vc; for i:=index[0]+1 to index[1] minc[ke[i]]:=t[i]; nH:=0; for i:=2 to n Insert(i); res:=0; for sc:=2 to n begin i:=info[1]; res:=res+minc[i]; 197 (198) Del; for j:=index[i-1]+1 to index[i] if (pos[ke[j]]<>0) and (t[j]<minc[ke[j]]) then begin minc[ke[j]]:=t[j]; UpHeap(ke[j]); end; end; end; Procedure Xuat; begin assign(f,fo); rewrite(f); writeln(f,res); close(f); end; Begin Nhap; Chuyen; Prim; Xuat; End 198 (199) PHÉP DUYỆT MỘT ĐỒ THỊ Các bài toán đồ thị ngày càng quan tâm nghiên cứu, phát triển, ứng dụng khoa học và sống Một cách tiếp cận các bài toán này là Phép duyệt đồ thị Trong phạm vi tham luận mình tôi xin đề cập đến số phép duyệt đồ thị bản, hiệu Như bạn đã biết: Khi biết gốc cây ta có thể thực phép duyệt cây đó để thăm các nút cây theo thứ tự nào Với đồ thị vấn đề đặt tương tự Xét đồ thị không định hướng G(V,E) và đỉnh v V(G), ta cần thăm tất các đỉnh thuộc G mà có thể với tới đỉnh v (nghĩa là thăm nút liên thông với v) Ta có hai cách giải trên đây: Phép tìm kiếm theo chiều sâu (Depth First Search-DFS) và phép tìm nhiếu theo chiều rộng (Breadth First Search-BFS) Tìm kiếm theo chiều sâu Đỉnh xuất phát v thăm, đó đinh w chưa thăm, mà là lân cận v, chọn và phép tìm kiếm theo chiều sâu xuất phát từ w lại thực Khi đỉnh u đã với tới mà đỉnh lân cận nó đã thăm rồi, thì ta quay ngược lên đỉnh cuối cùng vừa thăm (mà còn có đỉnh w lân cận với nó chưa thăm) Và phép tìm kiếm theo chiều sâu xuất phát từ w lại thực Phép tìm kiếm kết thúc không còn nút nào chưa thăm mà có thể với tới từ nút đã thăm Giải thuật phép duyệt này: Procedure DFS(v) Visited(v) :=1; //Visited dùng để đánh dấu các đỉnh đã thăm For đỉnh w lân cận v Do If Visited(w)=0 then Call DFS(w); Return Ta thấy: Trong trường phợp G biểu diễn danh sách lân cận thì đỉnh w lân cận v xác định cách dựa vào danh sách móc nối ứng với v Vì 199 (200) giải thuật DFS xem xét nút danh sách lân cận nhiều lần mà thôi mà lại có 2e nút danh sách (ứng với e cung), nên thời gian để hoàn thành phép tìm kiếm là O(e) Còn G biểu diễn ma trận lân cận thì thời gian để xác định đỉnh lân cận v là O(n) Vì tối đa có n đỉnh thăm, nên thời gian tìm kiếm tổng quát là O(n2) Giải thuật DFS(V1) đảm bảo thăm đỉnh liên thông với V Tất các đỉnh thăm cùng với các cung liên quan tới các đỉnh đó gọi là phận liên thông (vùng liên thông) G Với phép duyệt DFS ta có thể xác định G có liên thông hay không, tìm các phận liên thông G G không liên thông Áp dụng giải thuật tìm kiếm theo chiều sâu DFS để giải các bài toán sau, giúp ta hiểu DFS Bài toán: Bãi cỏ ngon - VBGRASS Bessie dự định ngày nhai cỏ xuân và ngắm nhìn cảnh xuân trên cánh đồng nông dân John, cánh đồng này chia thành các ô vuông nhỏ với R (1 <= R <= 100) hàng và C (1 <= C <= 100) cột Bessie ước gì có thể đếm số khóm cỏ trên cánh đồng Mỗi khóm cỏ trên đồ đánh dấu ký tự ‘#‘ là ký tự ‘#’ nằm kề (trên đường chéo thì không phải) Cho đồ cánh đồng, hãy nói cho Bessie biết có bao nhiêu khóm cỏ trên cánh đồng Ví dụ cánh đồng dây với R=5 và C=6: # # # # ## .# Cánh đồng này có khóm cỏ: Một khóm hàng đầu tiên, khóm tạo hàng thứ và thứ cột thứ 2, khóm là ký tự nằm riêng rẽ hàng 3, khóm tạo cột thứ và thứ hàng 4, và khóm cuối cùng hàng Dữ liệu  Dòng 1: số nguyên cách dấu cách: R và C 200 (201)  Dòng R+1: Dòng i+1 mô tả hàng i cánh đồng với C ký tự, các ký tự là ‘#’ ‘.’ Kết  Dòng 1: Một số nguyên cho biết số lượng khóm cỏ trên cánh đồng Ví dụ Dữ liệu 56 # # # # ## .# Kết Nhận xét: Số lượng các khóm cỏ có thể xem là số vùng liên thông trên đồ thị Trong đó, a[i,j] là cỏ và đỉnh lân cận nó, là cỏ thì tồn đường từ a[i,j] đến đỉnh đó Uses math; Const fi ='VBGRASS.INP'; fo ='VBGRASS.OUT'; MAXN = 200; Var f : array [0 MAXN+1,0 MAXN+1] of boolean; m,n : longint; Res : longint; Procedure Init(); begin Fillchar(f,sizeof(f),false); Res := 0; end; Procedure ReadData(); Procedure Dfs(x,y: longint); const tx : array [1 4] of longint = (1,1,0,0); ty : array [1 4] of longint = (0,0,1,1); var i,u,v: longint; begin f[x,y]:=false; for i:=1 to begin u:=tx[i] + x; v:=ty[i] + y; if f[u,v] then dfs(u,v); end; end; Procedure Solve(); var i,j : longint; 201 (202) var i,j : longint; c : char; begin Readln(m,n); for i:=1 to m begin for j:=1 to n begin read(c); f[i,j] := c = '#'; end; readln; end; end; begin for i:=1 to m for j:=1 to n if f[i,j] then begin dfs(i,j); inc(Res); end; end; BEGIN assign(input,fi); reset(input); assign(output,fo); rewrite(output); Init(); ReadData(); Solve(); Writeln(Res); close(input); close(output); END Bài Toán: V8ORG Ở đất nước nọ, lực lượng an ninh vừa phát tổ chức đối lập Tổ chức đối lập này tổ chức chặt chẽ, bao gồm mạng lưới thành viên và huy các cấp bậc khác Các thành viên tổ chức đánh số từ đến N Tổ chức có huy tối cao, luôn đánh số Mỗi thành viên biết viên huy trực tiếp mình (có viên huy trực tiếp) không biết các huy cấp cao Khi tiến hành việc bắt giữ các thành viên, tổ chức bị phân rã thành các nhóm nhỏ không liên kết với nhau, ví dụ sau bắt giữ thành viên số (hình 1), tổ chức bị phân rã thành nhóm Lực lượng an ninh khẳng định, nhóm chứa ít K thành viên không còn là mối đe dọa cho đất nước Để không làm giảm hình ảnh đất nước trước dư luận quốc tế, các nhà lãnh đạo an ninh muốn bắt giữ số lượng ít phần tử đối lập, cho các nhóm bị phân rã không còn gây nguy hại cho đất nước Cho biết cấu trúc tổ chức đối lập, việc chương trình giúp các nhà lãnh đạo an ninh xác định số lượng phần tử đối lập ít cần bắt giữ 202 (203) Dữ liệu  Dòng đầu tiên chứa số nguyên K (1 ≤ K ≤ 10000)  Dòng thứ hai chứa số nguyên N (1 ≤ N ≤ 10000)  Dòng thứ ba chứa N-1 số nguyên cách khoảng trắng, số huy trực tiếp phần tử tổ chức (trừ huy tối cao): Số đầu tiên cho biết huy phần tử thứ hai, số thứ hai cho biết huy phần tử thứ ba, Kết qủa In số nguyên là số phần tử đối lập ít cần bắt giữ Ví dụ Dữ liệu 14 1122323666747 Kết Mô tả Có thể bắt giữ phần tử 6, 2, và Hình Ý tưởng giải thuật: Gọi s[i] là số lượng phần tử quyền huy phần tử i (bao gồm chính i) , dễ thấy quá trình duyệt Dfs , có phần tử có s[i] >= k thì ta “bắt giữ” phần tử này, tức là cho s[i] = 0, việc tính các s[u] (với u nhận i là huy tính trước tính s[i]); Const Procedure Dfs(x: longint); fi ='V8ORG.INP'; var p: link; fo ='V8ORG.OUT'; begin p:=a[x]; 203 (204) MAXN = 20000; s[x]:=1; while p<>nil begin dfs(p^.v); inc(s[x],s[p^.v]); p:=p^.next; end; if s[x] >= k then begin inc(Res); s[x]:=0; end; type link =^node; node = record v : longint; next: link; end; var a s n,k Res : array [0 MAXN] of link; : array [0 MAXN] of longint; : longint; : longint; Procedure Push(u,v: longint); var p: link; begin new(p); p^.v:=v; p^.next:=a[u]; a[u]:=p; end; end; BEGIN assign(input,fi); reset(input); assign(output,fo); rewrite(output); ReadData(); Dfs(1); Write(Res); close(input); close(output); END Procedure ReadData(); var i: longint; x : longint; begin Readln(k); Readln(n); for i:=1 to n-1 begin 204 (205) read(x); push(x,i+1); end; end; Bài toán: Dạo chơi đồng cỏ Có N bò (1 <= N <= 1,000), để thuận tiện ta đánh số từ 1->N, ăn cỏ trên N đồng cỏ, để thuận tiện ta đánh số các đồng cỏ từ 1->N Biết bò i ăn cỏ trên đồng cỏ i Một vài cặp đồng cỏ nối với N-1 đường chiều mà các bò có thể qua Con đường i nối đồng cỏ A_i và B_i (1 <= A_i <= N; <= B_i <= N) và có độ dài là L_i (1 <= L_i <= 10,000) Các đường thiết kế cho với đồng cỏ có đường chúng Như các đường này đã hình thành cấu trúc cây Các chú bò có tinh thần tập thể và muốn thăm thường xuyên Vì lũ bò muốn bạn giúp chúng tính toán độ dài đường Q (1 <= Q <= 1,000) cặp đồng cỏ (mỗi cặp mô tả là số nguyên p1,p2 (1 <= p1 <= N; <= p2 <= N) DỮ LIỆU  Dòng 1: số nguyên cách dấu cách: N và Q  Dòng N: Dòng i+1 chứa số nguyên cách dấu cách: A_i, B_i, và L_i  Dòng N+1 N+Q: Mỗi dòng chứa số nguyên khác cách dấu cách mô tả yêu cầu tính toán độ dài đồng cỏ mà lũ bò muốn thăm qua lại p1 và p2 KẾT QUẢ  Dòng Q: Dòng i chứa độ dài đường đồng cỏ yêu cầu thứ i VÍ DỤ Dữ liệu 42 212 432 143 205 (206) 12 32 Kết GIẢI THÍCH Yêu cầu 1: Con đường đồng cỏ và có độ dài là Yêu cầu 2: Đi qua đường nối đồng cỏ và 4, tiếp tục qua đường nối và 1, và cuối cùng là đướng nối và 2, độ dài tổng cộng là Ý tưởng giải thuật : Với truy vấn (p1,p2) ta thực Dfs p1, quá trình dfs ta lưu lại f[i] là độ dài trên đường từ i đến p1, kết là f[p2]; 206 (207) Const fi fo MAXN ='PWALK.INP'; ='PWALK.OUT'; = 2000; Procedure Dfs(x: longint); var p : link; v : longint; type begin link =^node; free[x] := false; node =record p:=a[x]; v,w : longint; while p<>nil next:link; begin end; v:=p^.v; if free[v] then begin l[v] := l[x] + var p^.w; a : array [0 MAXN] of link; dfs(v); n,q : longint; end; p1,p2 : longint; p:=p^.next; end; l : array [0 MAXN] of end; longint; free : array [0 MAXN] of boolean; Procedure Solve(); begin Procedure push(u,v,w: longint); Fillchar(l,sizeof(l),0); var p: link; Fillchar(free,sizeof(free),true); begin Dfs(p1); new(p); end; p^.v:=v; p^.w:=w; BEGIN p^.next:=a[u]; a[u]:=p; assign(input,fi); reset(input); 207 (208) end; Procedure ReadData(); var i : longint; u,v,w: longint; begin Readln(n,q); for i:=1 to n-1 begin readln(u,v,w); push(u,v,w); push(v,u,w); end; end; assign(output,fo); rewrite(output); ReadData(); While q>0 begin Readln(p1,p2); Solve(); Writeln(l[p2]); dec(q); end; close(input); close(output); END Bài toán: Bảo vệ nông trang Nông trang có nhiều đồi núi, để bảo vệ nông trang nông dân John muốn đặt người canh gác trên các đồi này Anh ta băn khoăn không biết cần bao nhiêu người canh gác muốn đặt người canh gác trên đỉnh đồi Anh ta có đồ nông trang là ma trận gồm N (1 < N <= 700) hàng và M (1 < M <= 700) cột Mỗi phần tử ma trận là độ cao H_ij so với mặt nước biển (0 <= H_ij <= 10,000) ô (i, j) Hãy giúp xác định số lượng đỉnh đồi trên đồ Đỉnh đồi là nhiều ô nằm kề ma trận có cùng độ cao bao quanh cạnh đồ các ô có độ cao nhỏ Hai ô gọi là kề độ chênh lệch tọa độ X không quá và chênh lệch tọa độ Y không quá Dữ liệu * Dòng 1: Hai số nguyên cách dấu cách: N và M * Dòng N+1: Dòng i+1 mô tả hàng i ma trận với M số nguyên cách dấu cách: H_ij Kết * Dòng 1: Một số nguyên là số lượng đỉnh đồi 208 (209) Ví dụ Dữ liệu: 87 4322101 3332101 2222100 2111100 1100010 0001110 0122110 0111210 Kết quả: Ý tưởng giải thuật : Ta làm bước: Bước : Với đỉnh [i,j] chưa thăm, ta dfs đánh dấu các đỉnh có chiều cao < a[i,j], ta đảm bảo từ đỉnh có chiều cao a[u,v] nào đó, thủ tục dfs1 đánh dấu đỉnh có chiều cao <= a[u,v] lận cận; Như có các đỉnh có chiều cao “đỉnh” còn lại; Bước 2: Dfs để tìm các nhóm đỉnh, công việc này khá dễ dàng, cách làm tương tự với bài VBGRASS 209 (210) Const fi fo ='NKGUARD.INP'; =''; Procedure Dfs1(x,y,s: longint); var i: longint; MAXN = 1000; u,v : longint; begin tx : array [1 8] of longint = for i:=1 to (1,1,1,-1,-1,-1,0,0); begin ty : array [1 8] of longint = (u:=x+tx[i]; 1,0,1,-1,0,1,1,-1); v:=y+ty[i]; if (free[u,v]) and (a[u,v]<=a[x,y]) Var and (a[u,v]<s) then a : array begin [0 MAXN+1,0 MAXN+1] of longint; free[u,v]:=false; m,n : longint; Dfs1(u,v,s); end; Res : longint = 0; end; end; free : array [0 MAXN+1,0 MAXN+1] of boolean; Procedure Dfs2(x,y: longint); var i: longint; Procedure ReadData(); u,v : longint; var i,j: longint; begin begin for i:=1 to Readln(m,n); begin for i:=1 to m u:=x+tx[i]; for j:=1 to n v:=y+ty[i]; read(a[i,j]); if free[u,v] then end; begin 210 (211) Procedure Init(); var i: longint; begin Fillchar(free,sizeof(free),true); for i:=0 to m+1 begin free[i,n+1]:=false; free[i,0]:=false; end; for i:=0 to n+1 begin free[m+1,i]:=false; free[0,i]:=false end; end; free[u,v]:=false; Dfs2(u,v); end; end; end; Procedure Solve(); var i,j : longint; begin for i:=1 to m for j:=1 to n if free[i,j] Dfs1(i,j,a[i,j]); for i:=1 to m for j:=1 to n if free[i,j] then begin Dfs2(i,j); inc(Res); end; end; BEGIN assign(input,fi); reset(input); assign(output,fo); rewrite(output); ReadData(); Init(); Solve(); Writeln(Res); close(input); close(output); END 211 then (212) Bài toán: Leo núi Cho đồ kích thước NxN (2 <= N <= 100), ô mang giá trị là độ cao ô đó (0 <= độ cao <= 110) Bác John và bò Bessie ô trên trái (dòng 1, cột 1) và muốn đến cabin (dòng N, cột N) Họ có thể sang phải, trái, lên trên và xuống không thể theo đường chéo Hãy giúp bác John và bò Bessie tìm đường cho chênh lệch điểm cao và thấp trên đường là nhỏ Dữ liệu  Dòng 1: N  Dòng N+1: Mỗi dòng chứa N số nguyên, số cho biết cao độ ô Kết Một số nguyên là chênh lệch cao độ nhỏ Ví dụ Dữ liệu 11368 12255 44033 80234 43021 Kết Ý tưởng : Do giới hạn chiều cao đỉnh đồi là 200 nên ta thực tìm kiếm nhị phân và Dfs; Bắt đầu với biến hmin là chiều cao nhỏ xét, hmax là chiều cao lớn xét, ta duyệt hmin từ đến 200 và dùng hàm chặt nhị phân tìm hmax nhỏ cho đoạn đường từ (1,1) đến (n,n) có các đỉnh có độ cao nằm đoạn [hmin,hmax] Với cặp hmin, hmax tìm được, ta so sánh hiệu với kết và cập nhật 212 (213) {Thuật toán : DFS + chặt nhị phân} uses Math; Function ok():boolean; var i: longint; begin Const Fillchar(free,sizeof(free),true); fi ='MTWALK.INP'; for i:=1 to n fo ='MTWALK.OUT'; begin free[i,0]:=false; MAXN =200; free[0,i]:=false; INF =99999; free[i,n+1]:=false; var free[n+1,i]:=false; a : array end; [1 MAXN,1 MAXN] of longint; if (a[1,1] >= hmin) and (a[1,1] n : longint; <=hmax) then Dfs(1,1); res : longint = INF; exit(not(free[n,n])); end; free : array [0 MAXN,0 MAXN] of boolean; Function f():longint; hmin,hmax: longint; var u,v,mid: longint; begin Procedure ReadData(); u:=hmin; v:=200; var i,j : longint; while u<v-1 begin begin Readln(n); mid:= (u+v) div 2; for i:=1 to n hmax:=mid; for j:=1 to n if ok() then v:=mid read(a[i,j]); else u:=mid; end; end; hmax:=u; 213 (214) Procedure Dfs(x,y: longint); const tx : array [1 4] of longint = (1,-1,0,0); ty : array [1 4] of longint = (0,0,1,-1); var i,u,v: longint; begin for i:=1 to begin u:=x+tx[i]; v:=y+ty[i]; if free[u,v] and (a[u,v] >= hmin) and (a[u,v] <=hmax) then begin free[u,v]:=false; Dfs(u,v); end; end; end; if ok() then exit(u-hmin); hmax:=v; if ok() then exit(v-hmin); exit(INF); end; BEGIN assign(input,fi); reset(input); assign(output,fo); rewrite(output); ReadData(); For hmin:=0 to 200 Res := min(Res,f()); Writeln(Res); close(input); close(output); END Bài toán: Nước lạnh Mùa hè oi ả Wisconsin đã khiến cho lũ bò phải tìm nước để làm dịu khát Các đường ống dẫn nước nông dân John đã dẫn nước lạnh vào tập N (3 <= N <= 99999; N lẻ) nhánh (đánh số từ N) từ cái bơm đặt chuồng bò Khi nước lạnh chảy qua các ống, sức nóng mùa hè làm nước ấm lên Bessie muốn tìm chỗ có nước lạnh để cô bò có thể tận hưởng mùa hè cách thoải mái Bessie đã vẽ sơ đồ toàn các nhánh ống nước và nhận nó là đồ thị dạng cây với gốc là chuồng bò và các điểm nút ống thì có chính xác nhánh từ nút đó Một điều ngạc nhiên là các nhánh ống này có độ dài là Cho đồ các ống nước, hãy cho biết khoảng cách từ chuồng bò tới tất các nút ống và các phần cuối đường ống 214 (215) “Phần cuối” đường ống, có thể là vào nút ống là bị bịt, gọi theo số thứ tự đường ống Bản đồ có C (1 <= C <= N) nút ống, mô tả số nguyên: là “phần cuối” ống E_i (1 <= E_i <= N) và ống nhánh từ đó là B1_i và B2_i (2 <= B1_i <= N; <= B2_i <= N) Đường ống số nối với chuồng bò; khoảng cách từ phần cuối đường ống này tới chuồng bò là Dữ liệu  Dòng 1: số nguyên cách dấu cách: N và C  Dòng C+1: Dòng i+1 mô tả nút ống i với ba Số nguyên cách dấu cách: E_i, B1_i, và B2_i Kết  Dòng N: Dòng i chứa số nguyên là khoảng cách từ chuồng tới “phần cuối” ống thứ i Ví dụ Dữ liệu 52 354 123 Giải thích: Dữ liệu trên mô tả đồ ống nước sau: + + | Chuồng | + + |1 * 2/\3 * 4/\5 Kết 215 (216) 2 3 Giải thích: Ống luôn cách chuồng đoạn là Ống và nối với ống nên khoảng cách là Ống và nối với ống nên khoảng cách là Ý tưởng thuật toán: Gọi h[i] là độ dài từ ống i đến chuồng, r[i] là ống phải i và l[i] là ống trái, ta có h[r[i]] = h[l[i]] = h[i] + 1; Const fi='VCOLDWAT.INP'; fo=''; mxF=100000; mxT=1000; Type Nut=record t,p:longint; end; Var n:longint; h:array [1 mxF] of longint; a:array [1 mxF] of Nut; Procedure Init; Var c,i,e:longint; Begin assign(input,fi); reset(input); readln(n,c); for i:=1 to c readln(e,a[e].t,a[e].p); close(input); End; procedure DFS(u:longint); begin if u=1 then h[u]:=1; if a[u].t<>0 then begin h[a[u].t]:=h[u]+1; DFS(a[u].t); end; if a[u].p<>0 then begin h[a[u].p]:=h[u]+1; DFS(a[u].p); end; end; procedure GetOut; var i:longint; begin assign(output,fo); rewrite(output); for i:=1 to n DFS(i); for i:=1 to n writeln(h[i]); close(output); end; 216 (217) BEGIN Init; GetOut; END 217 (218) Tìm kiếm theo chiều rộng Trong phép duyệt đồ thị BFS, đỉnh xuất phát v đây thăm đầu tiên, có khác với DFS chỗ là: Sau đó các đỉnh chưa thăm mà là lân cận v thăm nhau, đến các đỉnh chưa thăm là lân cận các đỉnh này và tương tự Sau đây là giải thuật BFS: Procedure BFS(v) Visited(v) :=1; //Visited dùng để đánh dấu các đỉnh đã thăm Khởi tạo queue với v đã nạp vào While Q không rỗng Do Begin Call pop(v,Q); //Lấy đỉnh v khỏi Q For đình w lân cận với v Do if Visited(w)=0 then Begin Callpush(w,Q); Visited(w) :=1; End; End; Return Mỗi đỉnh thăm nạp vào queue lần vị câu lệnh while lặp lại nhiều n lần.Nếu G biểu diễn ma trận lân cận thì câu lệnh For chi phí O(n) thời gian đỉnh, đó thời gian chi phí toàn là O(n 2) Còn trường hợp G biểu diễn với danh sách lân cận thì chi phí tổng quát chung là O(e) Để hiểu rõ BFS ta nghiên cứu các toán sau: Bài toán: Gặm cỏ 218 (219) Bessie yêu bãi cỏ mình và thích thú chạy chuồng bò vào vắt sữa buổi tối Bessie đã chia đồng cỏ mình là vùng hình chữ nhật thành các ô vuông nhỏ với R (1 <= R <= 100) hàng và C (1 <= C <= 100) cột, đồng thời đánh dấu chỗ nào là cỏ và chỗ nào là đá Bessie đứng vị trí R_b,C_b và muốn ăn cỏ theo cách mình, ô vuông và trở chuồng ô 1,1 ; bên cạnh đó đường này phải là ngắn Bessie có thể từ ô vuông sang ô vuông khác kề cạnh Dưới đây là đồ ví dụ [với đá ('*'), cỏ ('.'), chuồng bò ('B'), và Bessie ('C') hàng 5, cột 6] và đồ cho biết hành trình tối ưu Bessie, đường dánh dấu chữ ‘m’ Bản đồ Đường tối ưu <-cột <-cột 1B * 1Bmmm* * *mmm 3.**.* 3.**.*m *** ***m 5* *.C 5* *.m Bessie ăn ô cỏ Cho đồ, hãy tính xem có bao nhiêu ô cỏ mà Bessie ăn trên đường ngắn trở chuồng (tất nhiên chuồng không có cỏ đâu nên đừng có tính nhé) Dữ liệu  Dòng 1: số nguyên cách dấu cách: R và C  Dòng R+1: Dòng i+1 mô tả dòng i với C ký tự (và không có dấu cách) đã nói trên Kết  Dòng 1: Một số nguyên là số ô cỏ mà Bessie ăn trên hành trình ngắn trở chuồng Ví dụ Dữ liệu 56 219 (220) B * * **.* *** * *.C Kết Ý tưởng : Bfs đỉnh B, với bảng f[i,j] là độ dài đường ngắn từ đỉnh (i,j) đến đỉnh B, kết là f[cx,cy]; Const fi ='VMUNCH.inp'; fo =''; MAXN =1500; tx : array [1 4] of longint = (1,0,-1,0); ty : array [1 4] of longint = (0,1,0,-1); Procedure xuly; var bot,top,x,y,i : longint; u : pos; begin bot:=1;top:=1; repeat u:=q[top];inc(top); a[u.x,u.y]:=false; Type for i:=1 to pos=record begin x,y : longint; x:=u.x+tx[i];y:=u.y+ty[i]; end; if a[x,y] then begin Var a[x,y]:=false; a : array [0 MAXN,0 MAXN] inc(bot); of boolean; q[bot].x:=x; d : array [1 MAXN,1 MAXN] q[bot].y:=y; of longint; d[x,y]:=d[u.x,u.y]+1; q : array [1 MAXN*MAXN] end; of pos; end; r,c,cx,cy : longint; until top>bot; end; 220 (221) Procedure nhap; var i,j : longint; t : char; begin assign(input,fi);reset(input); fillchar(a,sizeof(a),false); readln(r,c); for i:=1 to r begin for j:=1 to c begin read(t);a[i,j]:=t='.'; d[i,j]:=1; if t='B' then begin q[1].x:=i; q[1].y:=j; end; if t='C' then begin a[i,j]:=true; cx:=i; cy:=j; end; end; readln; end; close(input); end; Procedure xuat; begin assign(output,fo);rewrite(output); writeln(d[cx,cy]-1); close(output); end; BEGIN NHAP; XULY; XUAT; END Bài toán: VOI06 Quân tượng 221 (222) Xét bàn cờ vuông kích thước n×n Các dòng đánh số từ đến n, từ lên trên Các cột đánh số từ đến n từ trái qua phải Ô nằm trên giao dòng i và cột j 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 ô (ri, ci), i = 1,2, , m Không có hai quân cờ nào trên cùng ô Trong số các ô còn lại bàn cờ, ô (p, q) có quân tượng Mỗi nước đi, từ vị trí đứng quân tượng có thể di chuyển đến ô trên cùng đường chéo với nó mà trên đường 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) ô đí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ì có trường hợp: là không thể tới ô đích, là tới sau không quá nước (hình trái) Khi trên bàn cờ còn có các quân cờ khác, vấn đề không còn đơn giản Yêu cầu: Cho kích thước bàn cờ n, số quân cờ có trên bàn cờ m và vị trí chúng, ô xuất phát và ô đích quân tượng Hãy xác định số nước ít cần thực để đưa quân tượng ô đích đưa số -1 điều này không thể thực Input Dòng đầu tiên chứa số nguyên n, m, p, q, s, t Nếu m > thì dòng thứ i m dòng chứa cặp số nguyên ri , ci xác định vị trí quân thứ i Hai số liên tiếp trên cùng dòng ghi cách ít dấu cách Output Gồm dòng là số nước tìm 222 (223) Example Input: 837214 54 34 47 Output: Hạn chế: Trong tất các test: ≤ n ≤ 200 Có 60% số lượng test với n ≤ 20 Ý tưởng: giống bài VMUNCH, khác cách thêm đỉnh vào queue Const Procedure BFS; fi ='QBBISHOP'; var d,c,i,k : longint; fo =''; u,v : pos; MAXN =300; begin tx : array [1 4] of longint = d:=1;c:=1; (1,1,-1,-1); queue[d].x:=p; ty : array [1 4] of longint = (1,queue[d].y:=q; 1,1,-1); free[p,q]:=false; repeat Type u:=queue[d];inc(d); pos = record for i:=1 to x,y : longint; begin end; k:=1; Var while a : array [0 MAXN,0 MAXN] (tick[u.x+k*tx[i],u.y+k*ty[i]]) of longint; begin free,tick : array if free[u.x+k*tx[i],u.y+k*ty[i]] [0 MAXN,0 MAXN] of boolean; then queue : array begin [1 MAXN*MAXN] of pos; free[u.x+k*tx[i],u.y+k*ty[i]]:=fal n,s,t,p,q : longint; se; a[u.x+k*tx[i],u.y+k*ty[i]]:=a[u.x Procedure nhap; ,u.y]+1; var i,u,v,m : longint; inc(c); begin queue[c].x:=u.x+k*tx[i]; 223 (224) fillchar(free,sizeof(free),true); tick:=free; readln(n,m,p,q,s,t); for i:=0 to n+1 begin tick[0,i]:=false; tick[i,0]:=false; tick[n+1,i]:=false; tick[i,n+1]:=false; end; for i:=1 to m begin readln(u,v); tick[u,v]:=false; end; end; queue[c].y:=u.y+k*ty[i]; end; inc(k); end; end; until d>c; end; Procedure xuat; begin if (s=p) and(t=q) then writeln(0) else if a[s,t]=0 then writeln(-1) else writeln(a[s,t]); end; BEGIN assign(input,fi);reset(input); assign(output,fo);rewrite(output); NHAP; BFS; XUAT; close(input); close(output); END Bài toán: Laser Phones FJ mua hệ thống liên lạc cho đàn bò để chúng có thể trò chuyện với ăn cỏ Đồng cỏ mô tả lưới hình chữ nhậtkích thước WxH (1 <= W <= 100; <= H <= 100) Hiện FJ cung cấp điện thoại cho bò đầu đàn Tuy nhiên vấn đề là liên lạc hai bò thực đường truyền thông tin chúng không bị chắn Ở đây, thông tin truyềntheo các đường thẳng và dừng lại nó bị chắn núi đá, cây to (kí hiệu các kí tự '*') Do đó, FJ phải mua thêm số gương (kí hiệu các kí tự '/' và '\') để đổi hướng đường tia laser Xét ví dụ minh họa đây : 224 (225) Kích thước đồng có là 8x7, H = và W = Hai bò đầu đàn kí hiệu là 'C', đá và cây to kí hiệu là '*': C /-C * |* 4*****.* 4*****|* * *| * *| 1.C * 1.C *| \ -/ 0123456 0123456 Cần xác định M - số lượng gương ít FJ cần mua để có thể đảm bảo liên lạc hai bò nói trên Dữ liệu luôn đảm bảo có ít cách thực INPUT * Dòng 1: Chứa số nguyên W và H cách ít kí tự * Dòng H+1: Mô tả cánh đồng, dòng gồm W kí tự 'C' '*' , và '.' Thông tin không bị chặn qua các kí tự '.' và có chữ 'C' Ví dụ : 78 C * *****.* * * C * OUTPUT * Dòng 1: Một số nguyên ghi giá trị M - số gương ít cần mua Ví dụ : 225 (226) Ý tưởng: Giống bài QBBISHOP, khác cách thêm đỉnh {$R+} Const fi fo MAXN ='';//MLASERP.INP'; =''; =200; Var a,free : array [0 MAXN,0 MAXN] of boolean; f : array [0 MAXN,0 MAXN] of longint; queuex : array [0 MAXN*MAXN] of longint; queuey : array [0 MAXN*MAXN] of longint; cx,cy : array [1 2] of longint; n,m : longint; kq : longint; Procedure Nhap; var i,j,l : longint; c : char; begin Readln(n,m); l:=0; for i:=1 to m begin for j:=1 to n begin Read(c); a[i,j] := c<>'*'; Procedure Xuly; Const tx : array [1 4] of longint = (1,0,-1,0); ty : array [1 4] of longint = (0,1,0,-1); var d,c : longint; u,v,x,y,i : longint; begin d:=1; c:=1; a[cx[1],cy[1]]:=false; queuex[1]:=cx[1]; queuey[1]:=cy[1]; Repeat x:=queuex[d]; y:=queuey[d]; Inc(d); for i:=1 to begin u:=x+tx[i]; v:=y+ty[i]; While a[u,v] begin if free[u,v] then begin f[u,v]:=f[x,y]+1; free[u,v]:=false; Inc(c); QueueX[c]:=u; QueueY[c]:=v; end else begin if f[u,v]>f[x,y]+1 then 226 (227) if upcase(c) = 'C' then begin Inc(l); cx[l]:=i; cy[l]:=j; end; end; Readln; end; end; Procedure KhoiTao; var i : longint; begin for i:=0 to m+1 a[i,0]:=false; for i:=0 to m+1 a[i,n+1]:=false; for i:=0 to n+1 a[0,i]:=false; for i:=0 to n+1 a[m+1,i]:=false; Fillchar(f,sizeof(f),0); Fillchar(free,sizeof(free),true); end; f[u,v]:=f[x,y]+1; end; u:=u+tx[i]; v:=v+ty[i]; end; end; until d>c; Kq:=f[cx[2],cy[2]]-1; if (cx[2] = 0) and (cy[2] = 0) then kq:=0; end; BEGIN assign(input,fi); reset(input); assign(output,fo); rewrite(output); Nhap; KhoiTao; Xuly; Writeln(Kq); close(input); close(output); END 227 (228) CÁC PHƯƠNG PHÁP TÌM KIẾM TRÊN ĐỒ THỊ Một bài toán quan trọng lí thuyết đồ thị là bài toán duyệt tất các đỉnh có thể đến từ đỉnh xuất phát nào đó Vấn đề này đưa bài toán liệt kê mà yêu cầu nó là không bỏ sót hay lặp lại bất kì đỉnh nào Chính vì mà ta phải xây dựng thuật toán cho phép duyệt cách hệ thống các đỉnh, thuật toán gọi là thuật toán tìm kiếm trên đồ thị (graph traversal) Ta quan tâm đến hai thuật toán nhất: 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 Thuật toán tìm kiếm theo chiều sâu : a Thuật toán tìm kiếm theo chiều sâu: Ý tưởng: Tư tưởng thuật toán tìm kiếm theo chiều sâu (Depth-First Search - DFS) có thể trình bày sau: Trước hết, dĩ nhiên đỉnh s đến từ s, tiếp theo, với cung (s, x) đồ thị thì x đến từ s Với đỉnh x đó thì tất nhiên đỉnh y nối từ x đến từ s Điều đó gợi ý cho ta viết thủ tục đệ quy DFSVisit(u) mô tả việc duyệt từ đỉnh u cách thăm đỉnh u và tiếp tục quá trình duyệt DFSVisit(v) với v là đỉnh chưa thăm nối từ u Kĩ thuật đánh dấu sử dụng để tránh việc liệt kê lặp các đỉnh: Khởi tạo avail[v]:=true, vV, lần thăm đỉnh, ta đánh dấu đỉnh đó lại (avail[v]:=false) để các bước duyệt đệ quy không duyệt lại đỉnh đó Thuật toán: procedure DFSVisit(u  V); //Thuật toán tìm kiếm theo chiều sâu từ đỉnh u begin avail[u] := False; //avail[u] = False ⇔ u đã thăm Output ← u; //Liệt kê u 228 (229) for ∀v  V:(u, v) E //Duyệt đỉnh v chưa thăm nối từ u if avail[v] then DFSVisit(v); end; begin //Chương trình chính Input → Đồ thị G for ∀v  V avail[v] := True; //Đánh dấu đỉnh chưa thăm DFSVisit(s); end b Thuật toán tìm đường theo DFS: Bài toán tìm đường đi: Cho đồ thị G=(V,E) và hai đỉnh s, t  V Nhắc lại định nghĩa đường đi: Một dãy các đỉnh: P=<s=p0, p1, …, pk=t> (i: (pi-1, pi)  E) gọi là đường từ s tới t, đường này gồm k+1 đỉnh p0 , p1, …, pk và cạnh (p0, p1), (p1, p2), …,(pk-1, pk) Đỉnh s gọi là đỉnh đầu và đỉnh t gọi là đỉnh cuối đường Nếu tồn đường từ s tới t, ta nói s đến t và t đến từ s: s t Thuật toán: Để lưu lại đường từ đỉnh xuất phát s, thủ tục DFSVisit(u), trước gọi đệ quy DFSVisit(v) với v là đỉnh chưa thăm nối từ u chưa đánh dấu), ta lưu lại vết đường từ u tới v cách đặt trace[v]:=u, tức là trace[v] lưu lại đỉnh liền trước v đường từ s tới v Khi thuật toán DFS kết thúc, đường từ s tới t là: <p1=t  p2=trace[p1]  p3=trace[p2]  s> procedure DFSVisit(uV); //Thuật toán tìm kiếm theo chiều sâu từ đỉnh u 229 (230) begin avail[u] := False; //avail[u] = False ⇔ u đã thăm for ∀ v V:(u, v) E //Duyệt đỉnh v chưa thăm nối từ u if avail[v] then begin trace[v] := u; //Lưu vết đường đi, đỉnh liền trước v trên đường từ s tới v là u DFSVisit(v); //Gọi đệ quy để tìm kiếm theo chiều sâu từ đỉnh v end; end; begin / /Chương trình chính Input → Đồ thị G, đỉnh xuất phát s, đỉnh đích t; for ∀v  V avail[v] := True; //Đánh dấu đỉnh chưa thăm DFSVisit(s); if avail[t] then //s đến t «Truy theo vết từ t để tìm đường từ s tới t»; end Có thể không cần mảng đánh dấu avail[1 … n] mà dùng luôn mảng trace[1 … n] để đánh dấu: Khởi tạo các phần tử mảng trace[1 … n] là: Trace[s]≠0 Trace[v]=0, v≠s Khi đó điều kiện để đỉnh v chưa thăm là trace[v] = 0, từ đỉnh u thăm đỉnh v, phép gán trace[v]= u kiêm luôn công việc đánh dấu v đã thăm (trace[v] ≠ 0) Tính chất BFS 230 (231) Nếu ta xếp danh sách kề đỉnh theo thứ tự tăng dần thì thuật toán DFS luôn trả đường có thứ tự từ điển nhỏ số tất các đường từ s tới tới t c Thuật toán duyệt đồ thị theo DFS Cài đặt trên là ứng dụng thuật toán DFS để liệt kê các đỉnh đến từ đỉnh Thuật toán DFS dùng để duyệt qua các đỉnh và các cạnh đồ thị viết sau: procedure DFSVisit(uV); //Thuật toán tìm kiếm theo chiều sâu từ đỉnh u begin Time := Time + 1; d[u] := Time; //Thời điểm duyệt đến u Output ← u; //Liệt kê u for ∀vV:(u, v) E //Duyệt đỉnh v nối từ u if d[v] = then DFSVisit(v); //Nếu v chưa thăm, gọi đệ quy để tìm // kiếm theo chiều sâu từ đỉnh v Time := Time + 1; f[u] := Time; //Thời điểm duyệt xong u end; begin //Chương trình chính Input → Đồ thị G for ∀vV d[v] := 0; //Mọi đỉnh chưa duyệt đến Time := 0; for ∀vV if d[v] = then DFSVisit(v); end 231 (232) Thời gian thực giải thuật DFS có thể đánh giá số lần gọi thủ tục DFSVisit (|V| lần) cộng với số lần thực vòng lặp for bên thủ tục DFSVisit Chính vì vậy: • Nếu đồ thị biểu diễn danh sách kề danh sách liên thuộc, vòng lặp for bên thủ tục DFSVisit (xét tổng thể chương trình) duyệt qua tất các cạnh đồ thị (mỗi cạnh hai lần là đồ thị vô hướng, cạnh lần là đồ thị có hướng) Trong trường hợp này, thời gian thực giải thuật DFS là Θ(|V| + |E|) • Nếu đồ thị biểu diễn ma trận kề, vòng lặp for bên thủ tục DFSVisit phải duyệt qua tất các đỉnh … n Trong trường hợp này thời gian thực giải thuật DFS là Θ(|V| + |V|2) = Θ(|V|2) Nếu đồ thị biểu diễn danh sách cạnh, vòng lặp for bên thủ tục DFSVisit phải duyệt qua tất danh sách cạnh lần thực thủ tục Trong trường hợp này thời gian thực giải thuật DFS là Θ(|V||E|) Thuật toán tìm kiếm theo chiều rộng: a Thuật toán tìm kiếm theo chiều rộng Ý tưởng: • s u u v v … … Thăm trước tất các đỉnh v Thăm sau tất các đỉnh u Tư tưởng thuật toán tìm kiếm theo chiều rộng (Breadth-First Search – BFS) là “lập lịch” duyệt các đỉnh Việc thăm đỉnh lên lịch duyệt các đỉnh nối từ nó cho thứ tự duyệt là ưu tiên chiều rộng (đỉnh nào gần đỉnh xuất phát s duyệt trước) Đầu tiên ta thăm đỉnh s Việc thăm đỉnh s phát sinh thứ tự thăm đỉnh u1, u2, … nối từ s (những đỉnh gần s nhất) Tiếp theo ta thăm đỉnh u1, thăm đỉnh u1 lại phát sinh yêu cầu thăm đỉnh r1, r2, … nối từ u1 Nhưng rõ ràng các đỉnh r này “xa” s đỉnh u nên chúng thăm tất đỉnh u đã thăm Tức là thứ tự duyệt đỉnh là: s, u1, u2, … , r1, r2, … Thuật toán tìm kiếm theo chiều rộng sử dụng danh sách để chứa đỉnh “chờ” thăm Tại bước, ta thăm đỉnh đầu danh sách, loại nó khỏi danh sách và cho đỉnh chưa “xếp hàng” kề với nó xếp hàng thêm vào cuối danh sách Thuật toán kết thúc danh sách rỗng 232 (233) Vì nguyên tắc vào trước trước, danh sách chứa đỉnh chờ thăm tổ chức dạng hàng đợi (Queue): Nếu ta có Queue là hàng đợi với thủ tục Push(r) để đẩy đỉnh r vào hàng đợi và hàm Pop trả đỉnh lấy từ hàng đợi thì thuật toán BFS có thể viết sau: Thuật toán: Queue := (s); //Khởi tạo hàng đợi gồm đỉnh s for ∀vV avail[v] := True; avail[s] := False; //Đánh dấu có đỉnh s xếp hàng repeat //Lặp tới hàng đợi rỗng u := Pop; //Lấy từ hàng đợi đỉnh u Output ← u; //Liệt kê u for ∀vV:avail[v] and (u, v)  E //Xét đỉnh v kề u chưa //đẩy vào hàng đợi begin Push(v); //Đẩy v vào hàng đợi avail[v] := False; //Đánh dấu v đã xếp hàng end; until Queue = Ø; Thuật toán tìm đường theo BFS: Queue := (s); //Khởi tạo hàng đợi gồm đỉnh s for ∀vV avail[v] := True; avail[s] := False; //Đánh dấu có đỉnh s xếp hàng repeat //Lặp tới hàng đợi rỗng u := Pop; //Lấy từ hàng đợi đỉnh u for ∀vV:avail[v] and (u, v)  E //Xét đỉnh v kề u chưa //đẩy vào hàng đợi 233 (234) begin trace[v] := u; //Lưu vết đường Push(v); //Đẩy v vào hàng đợi avail[v] := False; //Đánh dấu v đã xếp hàng end; until Queue = Ø; if avail[t] then //s tới t «Truy theo vết từ t để tìm đường từ s tới t»; Tương tự thuật toán tìm kiếm theo chiều sâu, ta có thể dùng mảng Trace[1 … n] kiêm luôn chức đánh dấu Tính chất BFS Thuật toán BFS luôn trả đường qua ít cạnh số tất các đường từ s tới t Nếu ta xếp các danh sách kề đỉnh theo thứ tự tăng dần và có nhiều đường từ s tới t qua ít cạnh thì thuật toán BFS trả đường có thứ tự từ điển nhỏ số đường đó c Thuật toán duyệt đồ thị theo BFS Tương tự thuật toán DFS, trên thực tế, thuật toán BFS dùng để xác định thứ tự trên các đỉnh đồ thị và viết theo mô hình sau: procedure BFSVisit(sV); begin Queue := (s); //Khởi tạo hàng đợi gồm đỉnh s Time := Time + 1; d[s] := Time; //Duyệt đến đỉnh s repeat //Lặp tới hàng đợi rỗng u := Pop; //Lấy từ hàng đợi đỉnh u Time := Time+1; F[u]:=Time; //Ghi nhận thời điểm duyệt xong đỉnh u 234 (235) Output ← u; //Liệt kê u for ∀vV:(u, v) E //Xét đỉnh v kề u if d[v] = then //Nếu v chưa duyệt đến begin Push(v); //Đẩy v vào hàng đợi Time := Time + 1; d[v] := Time; //Ghi nhận thời điểm duyệt đến đỉnh v end; until Queue = Ø; end; begin //Chương trình chính Input → Đồ thị G; for ∀vV d[v] := 0; //Mọi đỉnh chưa duyệt đến Time := 0; for ∀vV if d[v]=0 then BFSVisit(v); end Thời gian thực giải thuật BFS tương tự DFS, Θ(|V| + | E|) đồ thị biểu diễn danh sách kề danh sách liên thuộc, Θ(|V|2) đồ thị biểu diễn ma trận kề, và Θ(|V||E|) đồ thị biểu diễn danh sách cạnh Bài tập: Bài 1: Mê cung hình chữ nhật kích thước mn gồm các ô vuông đơn vị (m, n ≤ 1000) Trên ô ghi ba kí tự: 235 (236) O: Nếu ô đó an toàn X: Nếu ô đó có cạm bẫy E: Nếu là ô có nhà thám hiểm đứng Duy có ô ghi chữ E Nhà thám hiểm có thể từ ô sang số các ô chung cạnh với ô đứng Một cách thoát khỏi mê cung là hành trình qua các ô an toàn ô biên Hãy giúp cho nhà thám hiểm hành trình thoát khỏi mê cung qua ít ô Dữ liệu vào từ tệp văn MECUNG.INP  Dòng 1: Ghi m, n (1<m, n≤1000)  M dòng thể bảng kích thước mn, mô tả trạng thái mê cung theo thứ tự từ trên xuống dưới, dòng n ký tự theo thứ tự từ trái qua phải Kết ghi file MECUNG.OUT • • •  Dòng 1: Ghi số bước tìm hành trình tìm  Dòng 2: Ghi xâu ký tự S mô tả hành trình tìm (xâu ký tự S gồm các chữ cái in hoa E, W, S, N mà ký tự xâu S thể việc sang ô chung cạnh theo hướng mô tả ký tự đó Ví dụ: E: sang ô chung cạnh theo hướng Đông, W: sang ô chung cạnh theo hướng Tây, S: sang ô chung cạnh theo hướng Nam, N: sang ô chung cạnh theo hướng Bắc) Ví dụ: MECUNG.INP MECUNG.OUT 45 XXXOX NEEN XOOOX XEXOO XXXOO Chương trình {$MODE OBJFPC} Const NMax = 1000; Fi = 'MECUNG.INP'; 236 (237) Fo = 'MECUNG.OUT'; dd: Array[1 4] of integer = ( 0,-1, 0, 1); dc: Array[1 4] of integer = (-1, 0, 1, 0); h: array[1 4] of char=('W','N', 'E', 'S'); Var a, tr: Array[1 NMax,1 NMax] of integer; queue : Array[1 NMax*NMax] of Record d,c : integer; End; N, M, dau, cuoi, x0, y0, x1, y1: integer; ok: boolean; Procedure DocF; Var i,j : integer; s: string; Begin Assign(Input,Fi); Reset(Input); Readln(M,N); For i:=1 to M begin readln(s); For j:=1 to N case s[j] of 237 (238) 'O': a[i,j]:=0; 'X': a[i,j]:=1; 'E': begin a[i,j]:=1; x0:=i; y0:=j; end; end; end; Close(Input); End; Procedure BFS(i,j : integer); Var k,dong,cot,u,v : integer; Begin ok:=false; if (x0=1) or (x0=m) or (y0=1) or (y0=n) then begin x1:=x0; y1:=y0; ok:=true; exit; end; 238 (239) Dau:=1; Cuoi:=1; queue[cuoi].d := i; queue[cuoi].c := j; tr[i,j] := 1; While dau<=cuoi Begin dong := queue[dau].d; cot := queue[dau].c; inc(dau); For k:=1 to Begin u := dong + Dd[k]; v := cot + Dc[k]; If (u>0) and (u<=M) and (v>0) and (v<=N) then If (a[u, v]=0) and (tr[u,v]=0) then Begin Inc(cuoi); queue[cuoi].d := u; queue[cuoi].c := v; tr[u,v] := k; if (u=1) or (u=m) or (v=1) or (v=n) then begin 239 (240) x1:=u; y1:=v; ok:=true; exit; end; End; End; End; End; Procedure Inkq; Var i, x, y: integer; s:string; Begin Assign(OutPut,fo); Rewrite(OutPut); if not ok then writeln(-1) else begin s:=''; while (x1<>x0) or (y1<>y0) begin s:=h[tr[x1,y1]]+s; 240 (241) x:=x1; y:=y1; x1:=x-dd[tr[x,y]]; y1:=y-dc[tr[x,y]]; end; writeln(length(s)); writeln(s); end; Close(Output); End; BEGIN DocF; BFS(x0,y0); Inkq; END Bài 2: Trên bàn cờ mn (1  m, n  1000) ô vuông có k quân mã đứng ô nào đó (1  k  1000) Trong quá trình di chuyển, quân mã có thể nhảy đến ô đã có quân mã khác đứng Hãy tìm cách di chuyển k quân mã đến vị trí ô [x0, y0] cho trước cho tổng bước các quân mã là nhỏ Dữ liệu vào tệp văn HORSES.INP:  Dòng chứa số nguyên dương m , n, x0, x0, k 241 (242)  k dòng dòng ghi số nguyên là tọa độ quân mã Kết ghi vào tệp văn HORSES.OUT:  Ghi số là tổng số bước các quân mã Trong trường hợp không di chuyển quân mã nào đó vị trí [x0, y0] thì ghi -1 Ví dụ: HORSES.INP 88883 11 22 33 HORSE.OUT 14 Phân tích: Loang từ điểm (x0, y0) hết bảng Trong bảng len[1 n, n], ô ghi số bước quân mã di chuyển từ ô [x0, y0] đến ô đó Nếu ô có quân mã không có giá trị thì không có cách di chuyển quân mã đó đến ô [x0, y0] ghi -1, ngược lại ta tính tổng số các số ghi các ô có quân mã đứng, tổng số đó là đáp số bài toán Chương trình {$MODE OBJFPC} Const NMax = 1000; Fi = 'HORSES.INP'; Fo = 'HORSES.OUT'; dd: Array[1 8] of integer = (-1,-2,-2,-1, 1, 2, 2, 1); dc: Array[1 8] of integer = (-2,-1, 1, 2, 2, 1,-1,-2); Var len: Array[1 NMax,1 NMax] of integer; queue : Array[1 NMax*NMax] of Record d,c : integer; 242 (243) End; N, M, x0, y0, q, dau, cuoi: integer; x, y: array[1 NMax] of integer; Procedure ReadFile; Var i, j : integer; Begin Assign(Input,Fi); Reset(Input); Readln(M,N, x0, y0, q); For i:=1 to q readln(x[i],y[i]); Close(Input); End; Procedure BFS(i,j : integer); Var k,dong,cot,u,v,t : integer; Begin Dau:=1; Cuoi:=1; queue[cuoi].d:=i; queue[cuoi].c:=j; fillchar(len, sizeof(len),0) len[i,j] := 1; 243 (244) While dau<=cuoi Begin dong := queue[dau].d; cot := queue[dau].c; inc(dau); For k:=1 to Begin u := dong + Dd[k]; v := cot + Dc[k]; If (u>0) and (u<=M) and (v>0) and (v<=N) then If len[u,v]=0 then Begin Inc(cuoi); queue[cuoi].d := u; queue[cuoi].c := v; len[u,v] := len[dong,cot]+1; End; End; End; End; Procedure PrintResult; Var i, s: integer; 244 (245) Begin Assign(OutPut,fo); Rewrite(OutPut); s:=0; for i:=1 to q if len[x[i],y[i]]=0 then begin s:=-1; break; end else s:=s+len[x[i],y[i]]-1; writeln(s); close(Output); End; BEGIN ReadFile; BFS(x0,y0); PrintResult; END Bài 3: 245 (246) Cho đồ thị vô hướng có N đỉnh đánh số từ đến N Hãy tìm các vùng liên thông đồ thị Dữ liệu vào từ file văn SVLT.INP  Dòng 1: Ghi n, m là số đỉnh và số cạnh đồ thị (1< n≤100)  M dòng tiếp theo: dòng ghi hai đỉnh đầu cạnh Kết ghi file SVLT.OUT  Dòng 1: Ghi số K là số vùng liên thông  K dòng tiếp theo: dòng ghi các đỉnh thuộc cùng vùng liên thông Ví dụ : SVLT.INP 11 10 12 34 36 45 46 57 67 68 78 10 11 SVLT.OUT 12 345678 10 11 Const Max = 100; Fi = 'SVLT.INP'; Fo = 'SVLT.OUT'; Var A: Array[1 Max,1 Max] of boolean; D: Array[1 Max] of integer; queue : Array[1 Max*Max] of Integer; N, dau, cuoi, sv: integer; 246 (247) Procedure ReadFile; Var i, u, v, m : integer; Begin Assign(Input,Fi); Reset(Input); Readln(N, m); fillchar(a, sizeof(a), false); For i:=1 to M begin Read(u,v); a[u, v]:=true; a[v, u]:=true; end; Close(Input); End; Procedure BFS(u : integer); Var v : integer; Begin Dau:=1; Cuoi:=1; queue[cuoi] := u; 247 (248) D[u] := sv; While dau<=cuoi Begin u := queue[dau]; inc(dau); For v:=1 to n If A[u,v] and (D[v]=0) then Begin Inc(cuoi); queue[cuoi] := v; D[v] := sv; End; End; End; Procedure Timsvlt; var i: integer; Begin Sv := 0; fillchar(D, sizeof(d), 0); Fillchar(D,sizeof(D),0); for i:=1 to n if D[i]=0 then 248 (249) begin inc(sv); BFS(i); end; End; Procedure Inkq; Var i, j: integer; Begin Assign(OutPut,fo); Rewrite(OutPut); writeln(sv); For i:=1 to sv Begin For j:=1 to N If D[j]=i then Write(j,' '); Writeln; end; Close(Output); End; BEGIN ReadFile; 249 (250) Timsvlt; Inkq; END Bài 4: Cho bảng hình chữ nhật chia thành m×n ô vuông đơn vị, ô vuông có ghi số Một miền bảng là tập hợp các ô chung đỉnh chứa số Hãy tính số miền bảng và diện tích miền Dữ liệu vào từ file văn MIEN0.INP  Dòng 1: Ghi m, n (1<m, n≤100)  M dòng thể bảng số theo thứ tự từ trên xuống dưới, dòng n số theo thứ tự từ trái qua phải Kết ghi file MIEN0.OUT  Dòng 1: Ghi số lượng miền  Dòng 2: ghi diện tích các miền Ví dụ : MIEN0.INP 10 0100000010 1100000010 0001100010 1110110010 0011000010 0001111110 1101000101 0001001010 MIEN0.OUT 25 14 Const Max = 100; Fi = 'MIEN0.INP'; Fo = 'MIEN0.OUT'; 250 (251) dc: Array[1 8] of integer = ( 0, 1, 1, 1, 0,-1,-1,-1); dd: Array[1 8] of integer = (-1,-1, 0, 1, 1, 1, 0,-1); Var A, D: Array[1 Max,1 Max] of integer; QUEUE : Array[1 Max*Max] of Record d,c : integer; End; DT : Array[1 Max*Max] of Integer; N, M , dau, cuoi, sv : integer; Procedure DocF; Var i,j : integer; Begin Assign(Input,Fi); Reset(Input); Readln(M,N); For i:=1 to M For j:=1 to N Read(A[i,j]); Close(Input); End; Procedure BFS(i,j : integer); Var k,dong,cot,u,v : integer; Begin 251 (252) Dau:=1; Cuoi:=1; QUEUE[cuoi].d := i; QUEUE[cuoi].c := j; D[i,j] := sv; DT[SV]:=1; While dau<=cuoi Begin dong := QUEUE[dau].d; cot := QUEUE[dau].c; inc(dau); For k:=1 to Begin u := dong + Dd[k]; v := cot + Dc[k]; If (u>0) and (u<=M) and (v>0) and (v<=N) then If (A[u,v]=0) and (D[u,v]=0) then Begin Inc(cuoi); QUEUE[cuoi].d := u; QUEUE[cuoi].c := v; D[u,v] := sv; Inc(DT[sv]); 252 (253) End; End; End; End; Procedure Timsvlt; var i, j: integer; Begin Sv := 0; fillchar(D, sizeof(d), 0); fillchar(DT, sizeof(DT), 0); Fillchar(D,sizeof(D),0); for i:=1 to m for j:=1 to n if (a[i,j]=0) and (D[i,j]=0) then begin inc(sv); BFS(i,j); end; End; Procedure Inkq; Var i: integer; 253 (254) Begin Assign(OutPut,fo); Rewrite(OutPut); writeln(sv); For i:=1 to sv Write(DT[i],' '); Close(Output); End; BEGIN DocF; Timsvlt; Inkq; END Bài 5: Một lâu đài chia thành m×n modul vuông (1<m, n<=50) Mỗi modul vuông có từ đến tường Hãy viết chương trình tính : - Lâu đài có bao nhiêu phòng ? - Diện tích phòng lớn là bao nhiêu ? - Bức tường nào cần loại bỏ để phòng càng rộng càng tốt ? Dữ liệu vào từ tệp văn LAUDAI.INP Dòng 1: ghi số lượng các môdul theo hướng Bắc-Nam và số lượng các modul theo hướng Đông Tây Trong các dòng tiếp theo, modul mô tả số (0 p15) Số đó là tổng : (= tường phía Tây ), (=tường phía Bắc ) ,4 (=tường phía Đông ) , ( = tường phía Nam) 254 (255) Các tường bên xác định hai lần ; tường phía Nam modul (1,1) đồng thời là tường phía Bắc modul (2,1) Kết ghi tệp văn LAUDAI.OUT Dòng 1: ghi số lượng phòng Dòng 2: ghi diện tích phòng lớn (tính theo số modul ) Dòng 3: ghi tường cần loại bỏ (trước tiên là hàng sau đó là cột modul có tường đó ) và dòng cuối cùng là hướng tường Ví dụ: N (Bắc) W (Tây)  S (Nam) Mũi tên tường cần loại bỏ theo kết ví dụ Const NMax = 50; Fi = 'LAUDAI.INP'; Fo = 'LAUDAI.OUT'; dd: Array[0 3] of integer = ( 0,-1, 0, 1); dc: Array[0 3] of integer = (-1, 0, 1, 0); 255 E (Đông) (256) h: array[0 3] of char=('W','N', 'E', 'S'); Var A, D: Array[1 NMax,1 NMax] of integer; queue : Array[1 NMax*NMax] of Record d,c : integer; End; DT : Array[1 NMax*NMax] of Integer; N, M, dau, cuoi, sp, MaxDT, i0, j0, k0: integer; Procedure DocF; Var i,j : integer; Begin Assign(Input,Fi); Reset(Input); Readln(M,N); For i:=1 to M For j:=1 to N Read(A[i,j]); Close(Input); End; Procedure BFS(i,j : integer); Var k,dong,cot,u,v : integer; Begin Dau:=1; 256 (257) Cuoi:=1; queue[cuoi].d := i; queue[cuoi].c := j; D[i,j] := sp; DT[sp]:=1; While dau<=cuoi Begin dong := queue[dau].d; cot := queue[dau].c; inc(dau); For k:=0 to Begin u := dong + Dd[k]; v := cot + Dc[k]; If (u>0) and (u<=M) and (v>0) and (v<=N) then If ((A[dong,cot] shr k) and =0) and (D[u,v]=0) then Begin Inc(cuoi); queue[cuoi].d := u; queue[cuoi].c := v; D[u,v] := sp; Inc(DT[sp]); End; 257 (258) End; End; End; procedure TimDtMax; var i: integer; begin MaxDT:=0; for i:=1 to sp if MaxDT<DT[i] then MaxDT:=DT[i]; end; procedure TimTuong; var i, j, k, max, u, v: integer; begin max:=0; for i:=1 to m-1 for j:=1 to n-1 for k:=2 to begin u:=i+dd[k]; v:=j+dc[k]; if ((a[i,j] shr k) and k =1) and (D[i,j]<>D[u,v]) then 258 (259) if max < DT[D[i,j]]+DT[D[u,v]] then begin max:=DT[D[i,j]]+DT[D[u,v]]; i0:=i; j0:=j; k0:=k; end; end; end; Procedure Timsvlt; var i, j: integer; Begin sp := 0; fillchar(DT, sizeof(DT), 0); Fillchar(D,sizeof(D),0); for i:=1 to m for j:=1 to n if D[i,j]=0 then begin inc(sp); BFS(i,j); end; 259 (260) End; Procedure Inkq; Var i: integer; Begin Assign(OutPut,fo); Rewrite(OutPut); writeln(sp); Writeln(MaxDT); writeln(i0,' ', j0, ' ', h[k0]); Close(Output); End; BEGIN DocF; Timsvlt; TimDtMax; TimTuong; Inkq; END Bài 6: Cho lưới hình chữ nhật kích thước mn gồm các ô vuông đơn vị, ô tô màu ký hiệu màu , màu 2… màu Giả thiết màu ô trái trên và phải là khác Hai ô chung cạnh cùng thuộc miền cùng màu Người A đứng miền có chứa ô góc trái trên, người B đứng miền có chứa ô phải Hai người chơi lần lượt, đến lượt mình người chơi có thể tô lại màu 260 (261) miền mà mình đứng Trò chơi kết thúc hai người đứng hai miền cạnh (chung ít cạnh ô vuông) Tính số lượt ít để trò chơi đó kết thúc Giới hạn: ≤ m, n ≤ 100 Số lượng miền ≤ 100 Dữ liệu vào từ tệp văn DOIMAU.INP:  Dòng đầu: ghi hai số m , n  M dòng tiếp theo, số thứ j dòng j ghi số hiệu màu ô [i, j] Kết ghi tệp văn DOIMAU.OUT: ghi số là số lượt ít để trò chơi kết thúc Ví dụ: DOIMAU.INP 43 DOIMAU.OUT 122 221 143 132 Phân tích: + Loang từ ô [1, 1] để tìm số miền (sm) 2 2 + Xây dựng véc tơ V màu miền V= 261 (262) 1 3 + Xây dựng đồ thị gồm sm đỉnh, xem miền là đỉnh đồ thị Giữa hai đỉnh có cạnh nối hai miền đó có chung ít cạnh ô vuông + Tìm đường ngắn từ đỉnh đến đỉnh sm Trong thủ tục BFS, bước, ta thăm đỉnh đầu danh sách (giả sử đỉnh đó là đỉnh u), loại nó khỏi danh sách và cho đỉnh v, chưa “xếp hàng” kề với u xếp hàng thêm vào cuối danh sách, tô màu đỉnh v giống màu đỉnh u, đồng thời cho các đỉnh kề với đỉnh v có màu giống với đỉnh u, chưa xếp “xếp hàng” thêm vào cuối danh sách {$MODE OBJFPC} Const Max = 100; Fi = 'DOIMAU.INP'; Fo = 'DOIMAU.OUT'; dd: Array[1 4] of integer = ( 0,-1, 0, 1); dc: Array[1 4] of integer = (-1, 0, 1, 0); 262 (263) Var A, B, D: Array[1 Max,1 Max] of integer; Queue : Array[1 Max*Max] of record d, c: integer; end; len: array[1 max] of integer; mau: array[1 max] of integer; N, M, sv : integer; Procedure DocF; Var i,j : integer; Begin Assign(Input,Fi); Reset(Input); Readln(M,N); For i:=1 to M For j:=1 to N Read(A[i,j]); fillchar(b, sizeof(b),0); fillchar(mau, sizeof(mau),0); Close(Input); End; Procedure BFS(i,j : integer); Var k,dong,cot,u,v, dau, cuoi: integer; Queue : Array[1 Max*Max] of record d, c: integer; end; Begin Dau:=1; Cuoi:=1; Queue[cuoi].d := i; Queue[cuoi].c := j; D[i,j] := sv; mau[sv]:=a[i,j]; While dau<=cuoi Begin dong := Queue[dau].d; cot := Queue[dau].c; inc(dau); For k:=1 to 263 (264) Begin u := dong + Dd[k]; v := cot + Dc[k]; If (u>0) and (u<=M) and (v>0) and (v<=N) then begin If (a[u,v]=a[i,j])and(D[u,v]=0) then Begin Inc(cuoi); Queue[cuoi].d := u; Queue[cuoi].c := v; D[u,v] := sv; End; if (a[u,v]<>a[i,j]) and (D[u,v]<>0) then begin b[d[u,v],sv]:=1; b[sv,d[u,v]]:=1; end; end; End; End; End; Procedure Timsvlt; var i, j: integer; Begin Sv := 0; fillchar(D, sizeof(d), 0); for i:=1 to m for j:=1 to n if D[i,j]=0 then begin inc(sv); BFS(i,j); end; End; procedure BFS1; Var k, u, v, dau, cuoi : integer; queue: array[1 max] of integer; 264 (265) Begin Dau:=1; Cuoi:=1; Queue[cuoi]:=1; len[1]:=1; While dau<=cuoi Begin u:=queue[dau]; inc(dau); For v:=1 to sv if (b[u,v]=1) and (len[v]=0) then Begin Inc(cuoi); Queue[cuoi]:=v; len[v]:=len[u]+1; mau[v]:=mau[u]; for k:=1 to sv if (b[v,k]=1)and(mau[k]=mau[u])and(len[k]=0) then begin inc(cuoi); queue[cuoi]:=k; len[k]:=len[v]; end; End; End; End; Procedure Inkq; Var i, j: integer; Begin Assign(OutPut,fo); Rewrite(OutPut); {writeln(sv); For i:=1 to m begin for j:=1 to n Write(D[i,j],' '); writeln; end;} write(len[sv]-1); Close(Output); 265 (266) End; BEGIN DocF; Timsvlt; BFS1; Inkq; END 266 (267)

Ngày đăng: 09/09/2021, 18:57

w