Bàn về một bài toán hay trong Pascal
Trang 1Bàn về một bài toán hay
Nguyễn Hiển
Các bạn thân mến! Việc nghiên cứu thuật toán và rút ra kinh nghiệm từ các bài toán hay luôn là một công việc thích thú với các bạn đam mê lập trình Trong bài viết này, tôi muốn giới thiệu với các bạn các cách giải quyết khác nhau cho một bài toán tưởng như đơn giản
A - BÀI TOĂN
Hôm nay, bạn có nhiệm vụ tân trang lại ô tô của mình Tất nhiên, có quá nhiều việc phải làm, vì vậy, bạn muốn thuê các xưởng để sửa chữa làm các công việc đó Không may, các xưởng đó lại rất chuyên môn hoá, cho nên bạn phải thuê các xưởng khác nhau cho các công việc khác nhau Hơn nữa, họ có xu hướng đòi nhiều tiền hơn với các xe ở tình trạng tốt hơn Chẳng hạn, một người thợ sơn có thể đòi tiền nhiều hơn khi sửa 1 ô tô mà toàn bộ nội thất đã làm bằng da, anh ta sẽ không đòi nhiều tiền cho xe chưa bọc da Do
đó, tiền trả thêm phụ thuộc vào công việc nào được làm và các công việc làm trước đó Tất nhiên, bạn luôn muốn tiết kiệm tiền cho mình bằng một thứ tự tốt nhất cho các công việc
Các công việc được đánh số từ 1 tới n Cho giá gốc p của mỗi công việc và tiền trả thêm s
cần tính tổng giá tiền nhỏ nhất để hoàn thành các công việc này.Dữ liệu vào: file
INP.DATDòng đầu tiên của file vào chứa số nguyên n (1 ≤ n ≤ 14) Tiếp theo có n dòng, mỗi dòng chứa đúng n số nguyên: số nguyên thứ i trên dòng này là giá gốc của công việc
nếu công việc j được làm trước đó Các giá tiền là các số nguyên không âm, không vượt quá 100000
Dữ liệu ra: file OUT.DAT
Ghi ra file 1 số nguyên duy nhất là tổng số tiền nhỏ nhất để hoàn thành các công việc.Ví dụ:
B - CĂCH GIẢI QUYẾT
Đây là bài toán có nhiều cách giải, với mỗi cách tiếp cận và suy nghĩ về bài toán, chúng
ta có các cách giải quyết khác nhau:
Trang 2I CĂCH GIẢI QUYẾT THỨ NHẤT: Duyệt
Dễ thấy, yêu cầu chính của bài toán là sắp xếp n công việc cho trước theo 1 thứ tự xác định để có cực tiểu chi phí Vì thế, cách giải quyết đơn giản nhất là duyệt mọi hoán vị của các công việc và tính ra chi phí nhỏ nhất Rất dễ tính toán ra độ phức tạp của thuật toán là: n! Với cách giải quyết này, theo thử nghiệm của tôi có thể qua được khoảng 8/14 test (một con số không nhỏ dù không tìm được thuật toán tối ưu!)
II CĂCH GIẢI QUYẾT THỨ 2: Dùng lý thuyết đồ thị
Nếu xem xét mỗi công việc như 1 đỉnh của đồ thị, số tiền trả thêm cho công việc i nếu công việc j được làm trước đó là trọng số của cung (có hướng) từ đỉnh j đến đỉnh i Như vậy, bài toán quy về việc xác định 1 đường đi Hamilton có chi phí cực tiểu Dễ dàng cài đặt thuật toán tìm đường đi Hamilton trên đồ thị có hướng, nhưng thực tế thuật toán tìm đường đi Hamilton cũng là thuật toán duyệt, độ phức tạp không khác phương pháp 1
III CĂCH GIẢI QUYẾT THỨ 3: Quy hoạch động
Cách giải quyết đúng đắn nhất cho bài toán này chính là thuật toán tôi muốn đưa ra để bàn luận với các bạn: Thuật toán Quy hoạch động trên tập hợp
Chúng ta có một cách tiếp cận khác về bài toán như sau:
- Xét 1 tập hợp a công việc (có thứ tự) đã được làm (ta đánh thứ tự là 1 a), giả sử chi phí
trên thì chi phí mới sẽ là:
Với một cách tổ chức dữ liệu hợp lí, ta có thể thực hiện bài toán qui hoạch động khá dễ dàng
- Xét mỗi số nguyên h, coi như 1 tập hợp các công việc: Bit thứ i của số nguyên h sẽ là trạng thái của công việc i, cụ thể: Bit thứ i của số nguyên h mang giá trị 1 nếu công việc i
đã được chọn làm và ngược lại, mang giá trị 0 nếu công việc i chưa được làm Như vậy,
- Xây dựng mảng best, với ý nghĩa: best[h] = số tiền cực tiểu để thực hiện các công việc trong tập h
Như vậy, ta có công thức quy hoạch động để tính giá trị cực tiểu khi kết nạp thêm công việc i:
+ h: tập các công việc của h0 và kết nạp thêm công việc h được làm cuối cùng Dễ thấy,
Chương trình có thể viết đơn giản:
Trang 3Nếu đã xác định được thuật toán thì việc viết chương trình trên trở nên đơn giản, trong khuôn khổ bài báo, tôi không muốn đưa ra phần cài đặt của chương trình, bạn nào có nhu cầu, xin liên hệ trực tiếp qua email
C - BÀN LUẬN THÊM
- Với thuật toán trên, chương trình chạy rất nhanh với n = 14, thậm chí là n = 20
- Nếu bạn cấp phát 2, 3 mảng động trong TP, bạn hoàn toàn có thể chạy với n = 18
- Nếu viết bằng Free Pascal, do bộ nhớ không hạn chế nên chương trình có thể chạy với n
= 20, thậm chí lớn hơn nữa
Kết luậnNhư vậy, với 1 bài toán tưởng như đơn giản, nhưng với cách tiếp cận khác nhau,
ta có các phương án giải quyết khác nhau, do đó thu được các kết quả rất khác nhau Chỉ
có đào sâu suy nghĩ, so sánh, cân đối phương pháp và quá trình thực hiện mới giúp ta tìm
ra thuật toán tối ưu cho chương trình của mình