sáng tạo trong thuật toán và lập trình với ngôn ngữ pascal và c#: tập 1 do nguyễn xuân huy biên soạn cung cấp cho các bạn những kiến thức về giải một bài toán tin; sinh dữ liệu vào và ra; bàn phím và màn hình; tổ chức dữ liệu và một số kiến thức khác.
TỦ SÁCH TRI THỨC DUY TÂN NGUYỄN XUÂN HUY SÁNG TẠO TRONG THUẬT TỐN VÀ LẬP TRÌNH với ngơn ngữ Pascal C# Tập Tuyển toán Tin nâng cao cho học sinh sinh viên giỏi Sáng tạo Thuật tốn Lập trình Tập I MỤC LỤC Chƣơng I Bài 1.1 Lời nói đầu i GIẢI MỘT BÀI TOÁN TIN Số thân thiện Bài 1.2 Số cấp cộng Bài 1.3 Số cấp nhân 11 Bài 1.4 Mảng ngẫu nhiên 13 Bài 1.5 Chia mảng tỉ lệ 1:1 16 Bài 1.6 Chia mảng tỉ lệ 1:k 21 Chƣơng II Bài 2.1 SINH DỮ LIỆU VÀO VÀ RA Sinh ngẫu nhiên theo khoảng 27 27 Bài 2.2 Sinh ngẫu nhiên tăng 29 Bài 2.3 Sinh hoán vị ngẫu nhiên 31 Bài 2.4 Sinh ngẫu nhiên 33 Bài 2.5 Sinh ngẫu nhiên tỉ lệ 36 Bài 2.6 Sinh ngẫu nhiên tệp tăng 40 Bài 2.7 Sinh ngẫu nhiên tệp cấp số cộng 42 Bài 2.8 Sinh ngẫu nhiên mảng đối xứng 43 Bài 2.9 Số độ cao h 46 Bài 2.10 Tệp hoán vị 49 Bài 2.11 Đọc liệu từ tệp vào mảng biết hai kích thước 53 Bài 2.12 Đọc liệu từ tệp vào mảng biết kích thước 56 Bài 2.13 Đọc liệu từ tệp vào mảng đối xứng 60 Bài 2.14 Đếm tàu 62 Bài 2.15 Sắp đoạn 65 Chƣơng III Bài 3.1 BÀN PHÍM VÀ MÀN HÌNH Bảng mã ASCII 79 79 Bài 3.2 Bộ Tú lơ khơ 80 Bài 3.3 Hàm GetKey 88 Bài 3.4 Trò chơi 15 90 Bài 3.5 Bảng nhảy 95 Chƣơng IV Bài 4.1 TỔ CHỨC DỮ LIỆU Cụm 107 107 Bài 4.2 Bài gộp 112 Bài 4.3 Chuỗi hạt 120 Sáng tạo Thuật tốn Lập trình Tập I Bài 4.4 Sắp mảng ghi tệp 129 Bài 4.5 abc - theo dẫn 133 Bài 4.6 Xâu mẫu 141 Chƣơng V Bài 5.1 PHƢƠNG PHÁP THAM LAM Băng nhạc 153 153 Bài 5.2 Xếp việc 158 Bài 5.3 Xếp ba lô 165 Bài 5.4 Cây bao trùm ngắn 170 Bài 5.5 Trộn hai tệp 177 Chƣơng VI Bài 6.1 PHƢƠNG PHÁP QUAY LUI Tám Hậu 193 195 Bài 6.2 Từ chuẩn 207 Bài 6.3 Tìm đường mê cung 216 Chƣơng VII Bài 7.1 Bài Bài 7.3 Bài 7.4 QUY HOẠCH ĐỘNG Chia thưởng Palindrome Cắm hoa Tìm đường ngắn 227 228 235 243 253 Chƣơng VIII Bài 8.1 Bài 8.2 Bài 8.3 Bài 8.4 Bài 8.5 Bài 8.6 Bài 8.7 Bài 8.8 Bài 8.9 SUY NGẪM Lát Chữ số cuối khác Hình chữ nhật tối đại ma trận 0/1 Ma phương Tháp Hà Nội cổ Tháp Hà Nội xuôi Tháp Hà Nội ngược Tháp Hà Nội thẳng Tháp Hà Nội sắc màu (Hà Nội Cầu vồng) 267 267 276 281 291 308 311 316 321 325 Sáng tạo Thuật tốn Lập trình Tập I Lời nói đầu Thể theo u cầu đơng đảo bạn đọc, biên soạn lại Sáng tạo Thuật tốn Lập trình với Tốn Tin nâng cao cho học sinh sinh viên nhằm cung cấp kĩ thuật lập trình để giải tốn khó máy tính Một tốn tin hiểu khó ta sử dụng thuật giải nảy sinh đầu vừa biết nội dung tốn ta thu kết sai lời giải thu khơng hữu hiệu theo nghĩa chương trình địi hỏi q nhiều nhớ hoặc/và chạy lâu Những thuật giải nảy sinh đầu thường gọi thuật giải tự nhiên Dĩ nhiên, khái niệm tương đối Nếu bạn nắm vững nhiều dạng thuật giải thử sức với nhiều tốn khó đến lúc thuật giải tự nhiên bạn đáng tin cậy Đó mục đích học tập rèn luyện ước mơ người viết tập sách Để đọc sách khơng địi hỏi bạn phải có tri thức đặc biệt Để tiếp thu tốt đóng góp cho việc hiệu chỉnh cải tiến nội dung sách cần bạn biết sử dụng ngơn ngữ lập trình: Pascal môi trường Turbo Free Pascal C# Các kĩ thuật lập trình minh hoạ qua tốn cụ thể tương đương với trình độ nâng cao học sinh sinh viên Hình thức phát biểu tốn suy cho khơng quan trọng Các kĩ thuật lập trình phương pháp xây dựng thuật giải cho toán thường dùng rộng rãi trình thiết kế cài đặt phần mềm ứng dụng thực tiễn, việc sớm làm chủ tri thức thật cần thiết Chính mà chúng tơi cho nội dung sách phù hợp với bạn học sinh, sinh viên trường đại học bạn đọc muốn tự hoàn thiện tri thức lĩnh vực giải thuật lập trình Thiết nghĩ sách dùng làm tài liệu tham khảo để dạy lớp chuyên tin trường phổ thông Nội dung sách gồm hai phần Phần thứ giới thiệu vắn tắt chất phương pháp kĩ thuật lập trình đề toán để bạn thử sức Phần thứ hai trình bày phân tích chi tiết lời giải với bình luận xuất xứ tốn Trong tập sách cung cấp tồn văn chương trình viết ngơn ngữ lập trình Pascal C# để bạn đọc tiện so sánh với lời giải Cả hai phần đề cập đến nội dung tám chương sau Chương thứ trình bày sơ đồ chung để giải tốn tin Các tập chương hầu hết thuộc loại dễ giải Chương thứ hai giới thiệu kĩ thuật sinh liệu cách tự động nhằm phục vụ cho việc kiểm thử (test) chương trình Chương thứ ba trình bày kĩ thuật quản lí bàn phím hình Chương thứ tư đề cập đến cách thức tổ chức liệu cho toán tin Ba chương giới thiệu ba số phương pháp phổ biến thường vận dụng thiết kế thuật giải Đó phương pháp tham lam, phương pháp quay lui quy hoạch động Các phương pháp không vạn theo nghĩa dùng chúng để giải toán tin Trong thực Sáng tạo Thuật tốn Lập trình Tập I tế, phương pháp vạn khơng hữu hiệu Tuỳ theo nội dung tốn mà ta chọn phương pháp phù hợp Đó điểm khó, địi hỏi bạn đọc q trình tìm tịi tích luỹ kinh nghiệm Riêng chương cuối sách, chương thứ tám giới thiệu số toán tin để bạn đọc tự phát phương pháp giải Những nội dung tập sách tập hợp chỉnh lí từ giảng thuật tốn lập trình, từ sách Tìm đường mê cung, Bắn tàu biển từ viết tác giả đăng tạp chí Tin học nhà trường số lời giải hay bạn học sinh Lần xuất chúng tơi trình bày thêm giải viết môi trường ngôn ngữ C# để bạn sinh viên tham khảo Hi vọng dịp khác cung cấp thêm phương án giải với bạn đọc Tuy nhiên, suy cho cùng, môi trường lập trình mang tính minh hoạ Khi biết thuật tốn, việc thể thuật tốn mơi trường lập trình cụ thể chắn việc làm quen thuộc bạn đọc Xin chân thành cảm ơn em học sinh, sinh viên, thầy cô giáo, bạn bè đồng nghiệp chia sẻ kinh nghiệm trợ giúp tài liệu, nhận xét bình luận để hình thành nội dung sách Chúng hi vọng tiếp tục nhận ý kiến phê bình bạn đọc nội dung, chất lượng hình thức trình bày để định hướng cho tập Hà Nội, Lễ Hội Đạp Thanh - 2008 N.X.H Sáng tạo Thuật tốn Lập trình Tập I CHƢƠNG GIẢI MỘT BÀI TOÁN TIN Phần giới thiệu số bước thường vận dụng trình giải toán tin Bước bước quan trọng hiểu rõ nội dung toán Đây yêu cầu quen thuộc người làm toán Để hiểu toán theo cách tiếp cận tin học ta phải gắng xây dựng số thí dụ phản ánh yêu cầu đề đầu thử giải thí dụ để hình thành dần hướng thuật tốn Bước thứ hai dùng ngơn ngữ quen thuộc, tốt ngơn ngữ tốn học đặc tả đối tượng cần xử lí mức độ trừu tượng, lập tương quan, xây dựng hệ thức thể quan hệ đại lượng cần xử lí Bước thứ ba xác định cấu trúc liệu để biểu diễn đối tượng cần xử lí cho phù hợp với thao tác thuật toán Trong bước ta tiếp tục làm mịn dần đặc tả theo trình tự từ xuống, từ trừu tượng đến cụ thể, từ đại thể đến chi tiết Bước cuối sử dụng ngơn ngữ lập trình chọn để viết chương trình hồn chỉnh Ở bước ta tiến hành theo kĩ thuật từ lên, từ thao tác nhỏ đến thao tác tổ hợp Sau nhận chương trình ta cho chương trình chạy thử với liệu lấy từ thí dụ xây dựng bước Điều quan trọng xây dựng thủ tục cách khoa học có chủ đích nhằm kiểm tra tính tin cậy chương trình thu thực số cải tiến Chúng ta vận dụng cách tiếp cận để giải số toán cụ thể Những phần trình bày sử dụng vài kí pháp quen thuộc tin học, thí dụ: x = abc số tự nhiên x tạo ba chữ số a, b c a, b = hai số a b nhận giá trị từ đến Sáng tạo Thuật tốn Lập trình Tập I Sở dĩ ta khơng sử dụng kí hiệu tốn học bàn phím máy tính khơng có kí hiệu Chọn kí hiệu có sẵn ngơn ngữ lập trình giúp viết thích chương trình Bài 1.1 Số thân thiện Tìm tất số tự nhiên hai chữ số mà đảo trật tự hai chữ số thu số nguyên tố với số cho Hiểu đầu Ta kí hiệu (a, b) ước chung lớn (ucln) hai số tự nhiên a b Hai số tự nhiên a b gọi nguyên tố (a, b) = Khi đó, chẳng hạn: a (23, 32) = 1, 23 số cần tìm Theo tính chất đối xứng, ta có 32 số cần tìm b (12, 21) = 3, 12 đồng thời 21 khơng phải số cần tìm Đặc tả: Gọi hai chữ số số tự nhiên cần tìm x a b, ta có: (1) x = ab (2) a, b = (a b biến thiên khoảng 9) (3) a > x số có hai chữ số (4) (ab, ba) = Ta kí hiệu x' số đối xứng số x theo nghĩa đầu bài, ta có đặc tả sau: (5) x = 10 99 (x biến thiên từ 10 đến 99, x số có hai chữ số) (6) (x, x') = Nếu x = ab x' = ba Ta tính giá trị x' theo cơng thức: x' = (chữ số hàng đơn vị x) * 10 + (chữ số hàng chục x) Kí hiệu Đơn(x) toán tử lấy chữ số hàng đơn vị số tự nhiên x kí hiệu Chục(x) toán tử lấy chữ số hàng chục x, ta có: x' = Đơn(x)*10 + Chục(x) Tổng hợp lại ta có đặc tả: Số cần tìm x phải thoả tính chất sau:x = 10 99 (x nằm khoảng từ 10 đến 99) (7) x' = Đơn(x)*10 + Chục(x) (8) (x, x') = (ước chung lớn x x' 1) Đặc tả thể qua ngơn ngữ trình tựa Pascal sau: (9) for x:=10 to 99 if ucln(x, đơn(x)*10+Chục(x))=1 then Lấy(x); đó, ucln(a,b)là hàm cho ước chung lớn hai số tự nhiên a b; Lấy(x) tốn tử hiển thị x lên hình ghi x vào mảng với mục đích sử dụng lại, cần Ta làm mịn đặc tả (10): ucln(a, b): Thuật toán Euclid chia liên tiếp, thay số thứ dư chia cho số thứ hai hoán vị hai số (* Tim uoc chung lon nhat cua hai so a va b Thuat toan Euclid *) function Ucln(a,b: integer): integer; Sáng tạo Thuật toán Lập trình Tập I var r: integer; begin while b > begin r:= a mod b; a:= b; b:= r; end; Ucln:= a; end; Đơn(x) = (x mod 10): số dư phép chia nguyên x cho 10, thí dụ: Đơn(19) = 19 mod 10 = Chục(x) = (x div 10): thương nguyên phép chia x cho 10, thí dụ: Chục(19) = 19 div 10 = Lấy(x): write(x) nạp giá trị x vào mảng s theo thao tác sau: n := n + 1; s[n] := x; n đếm số phần tử nạp mảng s Biểu diễn liệu Ta dùng mảng s để lưu số tìm Dễ thấy s phải mảng nguyên chứa tối đa 90 phần tử số cần khảo sát nằm khoảng từ 10 đến 99 var s: array[1 90] of integer; Phương án chương trình hoạt động theo hai bước sau: n := Tim; Xem(n); Bước Tìm ghi vào mảng s số thoả điều kiện đầu bài, n số lượng số tìm Bước Hiển thị phần tử mảng s[1 n] chứa số tìm Tốn tử x' viết dạng hàm cho ta số tạo chữ số x theo trật tự ngược lại Ta đặt tên cho hàm SoDao (số đảo) Hàm nhận giá trị vào số tự nhiên có nhiều chữ số Để tạo số đảo y số x cho trước, hàm SoDao lấy dần chữ số hàng đơn vị x để ghép vào bên phải số y: y := y*10 + (x mod 10) Sau bước, chữ số hàng đơn vị lấy loại hẳn khỏi x toán tử: x := x div 10 Chỉ thị {$B-} chương trình NTCN (nguyên tố nhau) đặt chế độ kiểm tra biểu thức lôgic vừa đủ Khi xác định giá trị chân lí cần thiết khơng tiến hành tính tiếp giá trị biểu thức Thí dụ, với lệnh x := 1; y := 5; if (x > 5) and (x + y < 7)then y := y + else y := y-1; chế độ {$B-}, sau tính giá trị chân lí (x > 5) = false, chương trình bỏ qua nhân tử logic (x + y < 7), tích lôgic false với giá trị tuỳ ý cho ta false Trong trường hợp lệnh y := y - thực Ngược lại, ta đặt thị {$B+} chương trình, sau tính (x > 5) = false tiếp tục tính giá trị (x + y < 7) lấy tích hai giá trị tìm (false and true = false) làm giá trị biểu thức điều kiện cấu trúc rẽ nhánh nói Sáng tạo Thuật tốn Lập trình Tập I Cuối tốn tử y := y - thực giống trường hợp khối lượng tính tốn lại nhiều (* Pascal *) (* -So than thien (xy,yx) = *) program SoThanThien; {$B-} uses Crt; const MN = 90; var s: array[1 MN] of integer; function Ucln(a,b: integer): integer; tự viết function SoDao(x: integer): integer; var y: integer; begin y := 0; repeat { ghep chu so hang don cua x vao ben phai y } y := 10*y + (x mod 10); x := x div 10; { loai chu so hang don } until (x = 0); SoDao := y; end; (* -Tim cac so thoa dieu kien dau bai ghi vao mang s Output: so luong cac so tim duoc *) function Tim: integer; var x,d: integer; begin d := 0; {So luong cac so can tim } for x := 10 to 99 if Ucln(x,SoDao(x)) = then begin d := d + 1; s[d]:= x; end; Tim := d; end; (* -Hien thi mang s[1 n] tren man hinh *) procedure Xem(n: integer); var i: integer; begin writeln; for i := to n write(s[i]:4); writeln; end; BEGIN n := Tim; Xem(n); writeln; Sáng tạo Thuật tốn Lập trình Tập I 10 write(' Tong cong ',n,' so'); readln; END // C# using System; namespace SangTao1 { /*********************************** So Than Thien: (xy, yx) = **********************************/ class SoThanThien { static int mn = 90; static int [] s = new int[mn]; static void Main(string[] args) { Run(); Console.ReadLine(); } static void Run() { int n = Find(); for (int i=0;i b Trường hợp a = b ta khơng xét x' = x Ucln(x, x) = x 10 Nếu b = ta có x = 10a x' = a Ta thấy Ucln(10a, a) = a = a = Do ta xét riêng trường hợp Khi ab = 10 ta có (10, 1) = Vậy 10 số cần tìm số 268 Sáng tạo Thuật tốn Lập trình Tập I Hà Nội xi Hà Nội ngược b = (a mod 3)+1 a b kề a b không kề a = (b mod 3)+1 a b không kề a b kề Quan hệ kề hai tốn tháp Hà Nội xi ngược // C# using System; namespace SangTao1 { /* -* Thap Ha Noi Nguoc * -*/ class ThapHaNoiNguoc { static int d = 0; static void Main() { Console.WriteLine("\n Ha Noi Nguoc "); HaNoiNguoc(3, 1, 2); Console.WriteLine("\n Total: " + d + " steps"); Console.ReadLine(); } // Main static void HaNoiNguoc(int n, int a, int b) { if (n == 0) return; if (a == (b % 3) + 1) { HaNoiNguoc(n - 1, a, - a - b); Console.WriteLine((++d) + " " + a + " -> " + b); HaNoiNguoc(n - 1, - a - b, b); } else // b c a, c = 6-a-b { HaNoiNguoc(n - 1, a, b); Console.WriteLine((++d)+" "+a+ " -> "+(6-a-b)); HaNoiNguoc(n - 1, b, a); Console.WriteLine((++d)+" "+ (6-a-b)+" -> "+b); HaNoiNguoc(n - 1, a, b); } } } // ThapHaNoiNguoc } // SangTao1 Bài 8.8 Tháp Hà Nội thẳng 269 Sáng tạo Thuật tốn Lập trình Tập I Nội dung giống toán tháp Hà Nội cổ sửa lại quy tắc (2) sau: (2) Mỗi lần chuyển tầng tháp từ cọc sang cọc kề nó, khơng vịng từ sang hay sang Điều kiện quy định bốn phép chuyển tầng tháp sau: , , , hoặc, theo cách biểu diễn khác: tức chuyển qua lại hai cọc kề Giả thiết cọc thành hàng sau: Hãy tìm cách giải tốn với số lần chuyển Bài giải Giống phân tích tốn Hà Nội trước, ta có: - Hình trạng xuất phát: (a:[1 n], b:[ ], c:[ ]) - … - Hình trạng kết thúc: (a:[ ], b:[1 n], c:[ ]) - Hình trạng buộc phải có: (a:[n], b:[ ], c:[1 n – 1]) Ta phân biệt hai trường hợp: - Hai cọc a b đứng kề đường thẳng - Hai cọc a b cách qua c Trường hợp thứ Nếu vị trí cọc bố trí sau a b có bốn tình huống, cụ thể là: Tình a b b a a b b a Tháp Hà Nội thẳng Đặc tả a b kề abs(a b) = Trường hợp đặc tả abs(a-b) = Hình trạng (a: [1 n], b: [ ], c: [ ]) Ý nghĩa Hình trạng ban đầu với vị trí b kề vị trí a đường thẳng abs(a b) = Lệnh 270 Sáng tạo Thuật tốn Lập trình Tập I Để chuyển n tầng từ a sang b theo đường thẳng ta phải (a: [n], b: [ ], c: [1 n 1]) Chuyển (tạm) n tầng từ a qua c = a b Hnt(n 1, a, a b); (a: [ ], b: [n], c: [1 n 1]) sau chuyển tầng lại a qua b ab Cuối chuyển n tầng từ c = – a – b qua b hoàn tất Hnt(n 1, a b, b); (a: [ ], b: [1 n], c: [ ]) Trường hợp thứ hai a b cách qua c đường thẳng Ta có c = có hai tình cho a b sau: Hình trạng a c b b c a Ý nghĩa (a: [n], c: [ ], b: [1 n 1] (a: [ ], c: [n], b: [1 n 1]) (a: [1 n-1], c: [n], b: [ ]) (a: [1 n-1], c: [ ], b: [n]) (a: [ ], c: [ ], b: [1 n]) Pascal Lệnh Hình trạng ban đầu với vị trí b khơng kề với vị trí a đường thẳng abs(a b) Ta có c = Để chuyển n tầng từ a sang b cách qua vị trí c = ta phải (a: [1 n], c: [ ], b: [ ]) (* Tình Chuyển (tạm) n tầng từ a qua b sau chuyển (tạm) tầng cuối a qua c = Hnt(n 1, a, b) Rồi lại chuyển (tạm) n tầng từ b qua a nhằm giải phóng vị trí đích b để chuyển tầng lớn n từ c = qua b Cuối chuyển n tầng từ a qua b hoàn tất Hnt(n 1, b, a) *) (**************************** HNT.PAS – Ha Noi thang Chuyen n tang thap tu coc a sang coc b theo duong thang 1->2, 2->1 hoac 2->3, 3->2 ****************************) a2 2b Hnt(n 1, a, b) Sáng tạo Thuật tốn Lập trình Tập I uses crt; var d: longint; f: text; procedure Hnt(n,a,b: byte); begin if n = then exit; if abs(a-b) = then begin Hnt(n-1,a,6-a-b); inc(d); writeln(f,d,' ',a,' -> ',b); Hnt(n-1,6-a-b,b); end else { abs(a-b)=2 tuc la a = 1, b = hoac a = 3, b = 1, do c=2 } begin Hnt(n-1,a,b); inc(d); writeln(f,d,' ',a,' -> 2'); Hnt(n-1,b,a); inc(d); writeln(f,d,' -> ',b); Hnt(n-1,a,b); end; end; procedure runHnt(n: byte); begin d := 0; assign(f,'hnt.out'); rewrite(f); writeln(' -'); Hnt(n,1,2); writeln(f,'Total: ',d,' step(s)'); close(f); readln; end; BEGIN runHnt(3); END Kết 1 -> 2 -> 3 -> -> -> -> -> -> -> 10 -> 271 Sáng tạo Thuật tốn Lập trình Tập I 272 11 -> 12 -> 13 -> Total: 13 step(s) // C# using System; namespace SangTao1 { /* -* Thap Ha Noi Thang * -*/ class ThapHaNoiThang { static int d = 0; static void Main() { Console.WriteLine("\n Ha Noi Thang"); HaNoiThang(3, 1, 2); Console.WriteLine("\n Total: " + d + " steps"); Console.ReadLine(); } // Main /* -Ha Noi Thang * -*/ static void HaNoiThang(int n, int a, int b) { if (n == 0) return; if (Math.Abs(a - b) == 1) { HaNoiThang(n - 1, a, - a - b); Console.WriteLine((++d)+ " "+a+" -> "+b); HaNoiThang(n - 1, - a - b, b); } else // a c b, b c a, c = 6-a-b { HaNoiThang(n - 1, a, b); Console.WriteLine((++d)+ " "+a+" -> "+(6-a-b)); HaNoiThang(n - 1, b, a); Console.WriteLine((++d)+ " "+(6-a-b)+" -> "+b); HaNoiThang(n - 1, a, b); } } } // ThapHaNoiThang } // SangTao1 Bài 8.9 Tháp Hà Nội sắc màu (Hà Nội Cầu vồng) Người ta sơn tầng tháp màu quy định luật chuyển cho loại tầng theo màu mô tả bảng sau 273 Sáng tạo Thuật tốn Lập trình Tập I Kí hiệu x n t h Màu xanh nâu trắng hồng Ý nghĩa chuyển tầng xuôi chiều kim đồng hồ ngược chiều kim đồng hồ thẳng tự (hà nội kinh điển) Quy tắc Ngoài ra, dĩ nhiên phải theo quy định tầng to không đặt lên tầng nhỏ Hãy tìm cách giải tốn với số lần chuyển Thí dụ, với tầng tháp tô màu từ (tầng nhỏ nhất) xuống (tầng lớn nhất) là: xanh nâu xanh hồng trắng Hà Nội sắc màu tầng xnxht cần chuyển tháp từ cọc sang cọc phải thực tối thiểu 31 lần chuyển tầng sau: 1 -> 2 -> 3 -> -> -> -> -> -> -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 -> 17 -> 18 -> 19 -> 20 -> 21 -> 22 -> 23 -> 24 -> 25 -> 26 -> 27 -> 28 -> 29 -> 30 -> 31 -> Total: 31 step(s) Bài giải Điều lí thú thủ tục Hà Nội sắc màu tổng qt ta dùng để gọi cho toán tháp Hà Nội xét Bảng cho biết cách sử dụng thủ tục Hnm cho trường hợp riêng Muốn gọi Hn(n,a,b) Hnx(n,a,b) Thì gọi Chú thích s := 'hh…h'; Hnm(length(s),a,b); s chứa n kí tự 'h' s := 'xx…x'; Hnm(length(s),a,b); s chứa n kí tự 'x' 274 Sáng tạo Thuật tốn Lập trình Tập I Hnn(n,a,b) s := 'nn…n'; Hnm(length(s),a,b); s chứa n kí tự 'n' Hnt(n,a,b) s := 'tt…t'; Hnm(length(s),a,b); s chứa n kí tự 't' Ta quy ước liệu ban đầu mô tả biến tổng thể kiểu xâu kí tự s với khai báo sau: var s: string Trong thí dụ trên, s gán trị s := 'xnxht'; Khi khai báo thủ tục tháp Hà Nội sắc màu sau: procedure Hnm(n: byte; a,b: byte); n số tầng tháp, a b hai cọc khác cho trước nhận giá trị 1, Ta viết thêm thủ tục runHnm(Thap: string) để tổ chức lời gọi thuận tiện cho toán Hà Nội sắc màu sau Tham biến Thap kiểu string chứa mô tả cụ thể cho tầng tháp Thí dụ, lời gọi runHnm('xnxht'); xử lí tháp tầng, tính từ xuống, tức từ tầng nhỏ có mã số đến tầng đáy, tầng lớn mang mã số sau: Tầng (i) Màu (Thap[i]) 'x' 'n' 'x' 'h' 't' Gọi thủ tục cho Hà Nội Sắc màu runHnm('xnxht') Cách mã số ngược với quy tắc gọi tầng đời thường Người ta thường mã số tầng nhà từ lên 1, 2,… Với lời gọi runHnm(Thap:string); giá trị tham trị Thap truyền cho biến tổng thể s: s := Thap; Sau lời gọi cụ thể Hnm theo số tầng tháp length(s), cọc nguồn (nơi đặt tầng tháp ban đầu) a = cọc đích, nơi cần chuyển tháp đến, b = Hnm(length(s),1,2); Sở dĩ phải dùng biến tổng thể s để lưu lại cấu hình tháp ta muốn hạn chế tốn phát sinh lời gọi đệ quy thủ tục Hnm 275 Sáng tạo Thuật tốn Lập trình Tập I Nếu khai báo Hnm với bốn tham biến sau: procedure Hnm(Thap: string; n,a,b: byte); rõ ràng tốn Biến tổng thể d khởi trị dùng để đếm số bước chuyển tầng tháp procedure runHnm(Thap:string); begin s := Thap; d := 0; assign(f,'hnm.out'); rewrite(f); writeln(' -'); Hnm(length(s),1,2); writeln(f,'Total: ',d,' step(s)'); close(f); readln; end; BEGIN runHnm('txhxn'); END Mỗi xử lí tầng tháp i, ta xem màu s[i] tầng lựa chọn định bảng sau Ta nhận xét rằng, tình huống, tức với màu khác tầng tháp, hai cọc a b kề ta cần thực thao tác nhau, cụ thể là: Nếu s[i] có màu 'x' Thì xử lí thủ tục hnx 'n' hnn 't' hnt 'h' hn Phương thức xử lí cho tốn Hà Nội sắc màu Vậy ta cần đặc tả tính chất kề Gọi hai cọc b xong thủ atục Ý nghĩa Ta thấy, dựa theo luật chuyển, với tháp Hà Nội cổ, hai cọc a b khác tùy ý Hnm(n 1, a, a b) Chuyển n tầng từ a xem kề Với qua thápcHà Nội xuôi, cọc b phải đứng sát cọc a theo chiều kim =6ab đồng hồ: ab Chuyển tầng cuối từ a sang b Hnm(n 1, a b, b) b = (a mod 3)+1 Chuyển n tầng tháp từ c = a b qua b Hà Nội sắc màu Chuyển tháp trường hợp a b kề Với tháp Hà Nội ngược, b phải đứng sát a theo chiều quay ngược kim đồng hồ Nói cách khác, a phải đứng sát b theo chiều kim đồng hồ: a = (b mod 3)+1 Với tháp Hà Nội thẳng, ta biết, giá trị a b phải lệch đơn vị: 276 Sáng tạo Thuật toán Lập trình Tập I abs(a-b) = Bảng mơ tả trường hợp Bài tốn Kí hiệu Hà Nội Cổ h Điều kiện a kề b (a b) Luôn (True) Hà Nội Xuôi x b = (a mod 3) + Hà Nội Ngược n a = (b mod 3) + Hà Nội Thẳng t abs(a b) = Minh hoạ ,,, , , , , , , , , Hà Nội sắc màu Các điều kiện kề hai cọc a b a, b = 1, 2, 3; a b Hàm Ke (kề) mô tả sau function Ke(n,a,b: byte): Boolean; begin case s[n] of 'x': ke := (b = (a mod 3)+1); 'n': ke := (a = (b mod 3)+1); 't': Ke := (abs(a-b) = 1); 'h': Ke := True; end {case}; end; Tương tự, hai cọc a b không kề ta thực thao tác Biết hàm Ke, ta dựa vào thuật toán riêng cho trường hợp để viết phương án cho toán Hà Nội sắc màu sau: (* -Ha Noi sac mau x: xanh - xuoi chieu kim dong ho n: nau - nguoc chieu kim dong ho t: trang - chuyen thang h: hong - chuyen tu -*) procedure Hnm(n: byte; a,b: byte); begin if n = then exit; if Ke(n,a,b) then begin Hnm(n-1,a,6-a-b); inc(d); writeln(f,d,' ',a,' -> ',b); Hnm(n-1,6-a-b,b); end else begin Sáng tạo Thuật toán Lập trình Tập I Hnm(n-1,a,b); inc(d); writeln(f,d,' ',a,' -> ',6-a-b); Hnm(n-1,b,a); inc(d); writeln(f,d,' ',6-a-b,' -> ',b); Hnm(n-1,a,b); end; end; (* Pascal *) (************************************** HNM.PAS – Thỏp Hà Nội màu x – xanh: Xuụi chiều kim đồng hồ n – nõu: Ngược chiều kim đồng hồ t – Trắng: thẳng theo hàng ngang h – Hà Nội cổ: kinh điển ****************************************) uses crt; var d: longint; {dem so buoc chuyen} f: text; {output file} s: string; {cau hinh thap} { -Kiem tra tinh chat ke giua coc a va b -} function Ke(n,a,b: byte): Boolean; begin case s[n] of 'x': ke := (b = (a mod 3)+1); 'n': ke := (a = (b mod 3)+1); 't': Ke := (abs(a-b) = 1); 'h': Ke := True; end {case}; end; (* -Ha Noi sac mau x: xanh - xuoi chieu kim dong ho n: nau - nguoc chieu kim dong ho t: trang - chuyen thang h: hong - chuyen tu -*) procedure Hnm(n: byte; a,b: byte); tự viết { To chuc goi thu tuc Hnm } procedure runHnm(Thap:string); begin s := Thap; d := 0; assign(f,'hnm.out'); rewrite(f); writeln(' -'); 277 Sáng tạo Thuật tốn Lập trình Tập I 278 Hnm(length(s),1,2); writeln(f,'Total: ',d,' step(s)'); close(f); readln; end; BEGIN runHnm('txhxn'); END // C# using System; namespace SangTao1 { /* -* Thap Ha Noi * -*/ class ThapHaNoi { static int d = 0; static char[] s = new char[64]; static void Main() { Console.WriteLine("\n Ha Noi Sac Mau"); RunHaNoiSacMau("xnxht", 1, 2); Console.WriteLine("\n Total: " + d + " steps"); Console.ReadLine(); } // Main static bool Ke(char KieuThap, int a, int b) { switch (KieuThap) { case 'x': return (b == (a % 3) + 1); case 'n': return (a == (b % 3) + 1); case 't': return (Math.Abs(a - b) == 1); } return true; } /* Ha Noi sac mau x: xanh - xuoi chieu kim dong ho n: nau - nguoc chieu kim dong ho t: trang - chuyen thang h: hong - chuyen tu -*/ void HaNoiSacMau(int n, int a, int b) { if (n == 0) return; if (Ke(s[n], a, b)) { HaNoiSacMau(n - 1, a, - a - b); Sáng tạo Thuật tốn Lập trình Tập I 279 Console.WriteLine((++d)+ " ("+s[n]+") "+a+" -> "+b); HaNoiSacMau(n - 1, - a - b, b); } else { HaNoiSacMau(n - 1, a, b); Console.WriteLine((++d)+ " ("+s[n]+") " +a+" -> "+(6-a-b)); HaNoiSacMau(n - 1, b, a); Console.WriteLine((++d)+ " ("+s[n]+")" +(6-a-b) +" -> "+b); HaNoiSacMau(n - 1, a, b); } } static void RunHaNoiSacMau(string w, int a, int b) { d = 0; w.CopyTo(0, s, 1, w.Length); HaNoiSacMau(w.Length, a, b); } } // ThapHaNoi } // SangTao1 Hƣớng dẫn kiểm thử Ta dùng kĩ thuật đối sánh để kiểm thử trường hợp Ta chọn cố định giá trị n, a b Chẳng hạn, n = 4, a = 1, b = Khi ta có: RunHn(n) RunHnm('hhhh') cho kết RunHnx(n) RunHnm('xxxx') cho kết RunHnn(n) RunHnm('nnnn') cho kết RunHnt(n) RunHnm('tttt') cho kết Nếu ghi kết vào tệp tương ứng, sau gọi thủ tục đối sánh cặp hai tệp tương ứng kiểm định số trường hợp Để xây dựng tình kiểm thử khác cịn lại bạn để ý đến tính chất thuận nghịch thủ tục khác Thí dụ, phân tích phần trên, lời gọi Hnx(3,1,2) Hnn(3,3,1) phát sinh số lần chuyển Bạn thử phát thêm tương đồng lời gọi Hnm với tham trị khác Sáng tạo Thuật tốn Lập trình Tập I 280 Đọc thêm Lƣơc sử Một số toán tháp Hà Nội đưa vào kì thi Olympic Tin học số quốc gia Chúng ta thử tìm hiểu cội nguồn tốn thuộc loại Năm 1883, tờ báo Paris có đăng mơ tả trị chơi tốn học giáo sư Claus với tên Tháp Hà Nội Nội dung trò chơi người say mê làm thử tốn Tháp Hà Nội cổ Thời thủ Paris dân chúng đổ xơ mua đồ chơi suốt ngày ngồi chuyển tháp Trong lịch sử trị chơi thơng minh có sốt Tính trung bình kỉ có vài sốt trị chơi Thế kỉ thứ XX có sốt Rubic, kỉ XIX trò chơi 15 tháp Hà Nội Bài toán tiếng đến mức trở thành kinh điển giáo trình thuật giải đệ quy trình bày thơng báo thức phiên chuẩn ngữ trình ALGOL-60, ALGOL-68, Pascal, Delphy, C, C++, Ada, muốn nhấn mạnh khả đệ quy ngôn ngữ Theo nhà nghiên cứu Henri De Parville cơng bố vào năm 1884 tác giả trị chơi tháp Hà Nội có tên thật nhà tốn học Eduard Lucas, người có nhiều đóng góp lĩnh vực số luận Mỗi viết đề tài giải trí ơng đổi tên Claus Bạn có để ý Claus hoán vị chữ từ Lucas De Parville cịn kể tốn tháp Hà Nội bắt nguồn từ tích truyền kì Ấn Độ Một nhóm cao tăng Ấn Độ giáo giao trọng trách chuyển dần 64 đĩa vàng ba cọc kim cương theo điều kiện nói tốn Tháp Hà Nội cổ Khi hồn tất cơng việc, tức chuyển xong tồ tháp vàng 64 tầng từ vị trí ban đầu sang vị trí kết thúc thời điểm tận Sự việc có xảy hay khơng ta xét tập 8.10 Lời giải công bố cho toán tháp Hà Nội Allardice Frase, năm 1884 Năm 1994 David G Poole Đại học Trent, Canada viết khảo cứu cho tờ Mathematics Magazine số tháng 12 nhan đề "Về tháp tam giác giáo sư Claus" với phụ đề "Pascal biết Hà Nội" Poole liệt kê 65 cơng trình khảo cứu tốn tháp Hà Nội đăng tạp chí tốn-tin khoảng mười năm Tác giả liên quan cơng thức tính số lần chuyển tầng tháp phương pháp quen biết n dùng để tính hệ số dạng khai triển nhị thức Newton (a + b) Phương pháp gọi Tam giác Pascal, mang tên nhà toán học kiêm vật lí học Pháp Blaise Pascal (1623-1662), người chế tạo máy tính quay tay giới Một số nhà nghiên cứu nước có bàn luận địa danh Hà Nội Theo tơi vấn đề cịn ngỏ Hầu hết viết xoay quanh đề tài chuyển tháp nói dùng thuật ngữ toán tháp Hà Nội Khi giới thiệu toán Hà Nội nhiều tháp Dudeney đặt tên toán đố Reve (The Reve's Puzzle) Tuy nhiên, nhiều nhà nghiên cứu cho tốt nên đặt tên phân loại theo tên nguyên thuỷ toán, nghĩa Tháp Hà Nội Ngoài dạng Tháp Hà Nội liệt kê phần số tác giả đề xuất dạng kì lạ, chẳng hạn tốn sau 281 Sáng tạo Thuật toán Lập trình Tập I Hà Nội nhiều tháp Trong trị chơi người ta làm thêm cọc, chẳng hạn thay ba ta dùng bốn cọc bố trí tháp nhiều cọc Ý kiến H.E Dudeney, tác giả hàng đầu toán học giải trí người Anh đưa vào năm 1908 Đã có nhiều đăng lời giải cho tốn này, có xuất gần vào năm 1988 1989 Dù chưa chứng minh rõ ràng số lần chuyển giải tối thiểu làm với dạng tháp Hà Nội khác Bài tập Bạn thử lập công thức tính số lần chuyển tầng tối thiểu cho toán sau: Tháp Hà Nội, Tháp Hà Nội Xuôi, Tháp Hà Nội Ngược Tháp Hà Nội Thẳng Lời cảm ơn Các tư liệu số tư liệu khác trích dẫn từ viết giáo sư viện sĩ Nguyễn Xuân Vinh, Khoa Kỹ thuật không gian, Đại học Michigan, cộng tác viên NASA, Hoa Kỳ Tác giả xin chân thành cám ơn giáo sư cho phép trích dẫn giáo phương pháp truyền thụ tri thức khoa học cho giới trẻ NXH 8/4/2008 Sửa ngày 4/4/09 282 Sáng tạo Thuật tốn Lập trình Tập I Nguyn Xuân Huy sáng tạo thuật toán lập trình với C#, Pascal tuyển toán tin nâng cao cho học sinh sinh viên giỏi Tập Lời giới thiệu Sách trình bày có hệ thống ph-ơng pháp thiết kế thuật toán minh họa qua toán thi học sinh giỏi Olimpic học sinh sinh viên n-ớc, khu vực quốc tế Các toán đ-ợc phân tích đầy đủ kèm theo thuật toán toàn văn ch-ơng trình viết ngôn ngữ C# Pascal Sách tài liệu bổ ích cho học sinh trung học, giáo viên tr-ờng phổ thông cao đẳng sinh viên tr-ờng đại học muốn hoàn thiện kiến thức để tham dự kỳ thi Olimpic Tin học quốc gia quốc tế Các ch-ơng trình song ngữ Pascal C# giúp cho bạn đọc chuyển đổi nhanh chóng sang môi tr-ờng lập trình tiên tiến ...2 Sáng tạo Thuật tốn Lập trình Tập I MỤC LỤC Chƣơng I Bài 1. 1 Lời nói đầu i GIẢI MỘT BÀI TOÁN TIN Số thân thiện Bài 1. 2 Số cấp cộng Bài 1. 3 Số cấp nhân 11 Bài 1. 4 Mảng ngẫu nhiên 13 Bài 1. 5... LIỆU Cụm 10 7 10 7 Bài 4.2 Bài gộp 11 2 Bài 4.3 Chuỗi hạt 12 0 Sáng tạo Thuật tốn Lập trình Tập I Bài 4.4 Sắp mảng ghi tệp 12 9 Bài 4.5 abc - theo dẫn 13 3 Bài 4.6 Xâu mẫu 14 1 Chƣơng V Bài 5 .1 PHƢƠNG... = x 10 Nếu b = ta có x = 10 a x' = a Ta thấy Ucln (10 a, a) = a = a = Do ta xét riêng trường hợp Khi ab = 10 ta có (10 , 1) = Vậy 10 số cần tìm số Sáng tạo Thuật toán Lập trình Tập I 11 Mỗi