+ Giải tất cả các bài toán cơ sở (thông thường rất dễ) , lưu các lời giải vào bảng phương án. + Dùng công thức truy hồi phối hợp những lời giải của các bài toán nhỏ đã lưu trong bảng [r]
(1)TRẠI HÈ PHƯƠNG NAM LẦN IV - 2017
KỶ YẾU HỘI THẢO MÔN: TIN HỌC
TRƯỜNG THPT CHUYÊN PHAN NGỌC HIỂN
(2)2 MỤC LỤC
STT CHUYÊN ĐỀ Trang
1 Thuật toán tham lam
THPT Chuyên Thủ Khoa Nghĩa, An Giang
2 Quy Hoạch Động
Nguyễn Chánh Tín, THPT Chuyên Bạc Liêu 17
3
Thuật toán xác định hướng điểm so với vector ứng dụng việc giải tốn hình học
Đồn Minh Đức THPT Chuyên Bến Tre
47
4 Xác định thuật toán FLOYD –WARSHALL cho đồ thị dày
THPT Chuyên Vị Thanh, Hậu Giang 55
5
Cấu trúc Heap
Thi Thị Thanh Tuyền THPT Chuyên Nguyễn Thiện Thành, Trà Vinh
63
6 Cấu trúc liệu Heap
THPT Chuyên Long An
112
7
Cấu trúc liệu tập rời rạc
Nguyễn Hoàng Phú THPT Chuyên Lý Tự Trọng, Cần Thơ
161
8 Cây khung nhỏ
THPT Chuyên Huỳnh Mẫn Đạt, Kiên Giang 168
9
Cây IT – SEGMENT TREE
Phan Thành Tâm THPT Chuyên Thoại Ngọc Hầu, An Giang
203
10
TWO POINTERS
Lương Quí Hiệp THPT Chuyên Lương Thế Vinh, Đồng Nai
215
11
Dãy chung dài
Nguyễn Tấn Phát THPT Chuyên Phan Ngọc Hiển, Cà Mau
(3)3
12 Chuyên đề Hình Học
THPT Chuyên Nguyễn Bỉnh Khiêm, Vĩnh Long 245
13 BINARY INDEX TREE BIT
THPT Chuyên Hùng Vương, Bình Dương 267
14
BINARY HEAP
Phạm Ngọc Bách THPT Chuyên Hoàng Lê Kha, Tây Ninh
272
15
Vận dụng thuật tốn tìm kiếm nhịphân để giải toán thi học sinh giỏi
Nguyễn Trọng Nghĩa THPT Chuyên Tiền Giang
278
16
Hình học tính tốn
Lâm Thanh Phương THPT Chuyên Nguyễn Thị Minh Khai, Sóc Trăng
293
17
Thuật tốn TARJAN ứng dụng
Huỳnh Tấn Thông THPT Chuyên Nguyễn Quang Diêu, Đồng Tháp
316
242
264
269
275
290
(4)4
PHƯƠNG PHÁP THAM LAM
Trường THPT Chuyên Thủ Khoa Nghĩa – An Giang
I Ý tưởng thuật toán tham lam:
Trong khía cạnh sống hàng ngày, cơng việc nghiên cứu, người phải đối mặt với tốn để lựa chọn phương án chấp nhận Trong số tốn đó, có tốn đơn giản cần có giải pháp có tốn khắt khe hơn, địi hỏi khơng giải pháp mà giải pháp cịn phải giải pháp tốt (nhanh nhất, đơn giản nhất, hiệu ) gọi tốn tối ưu Các toán tối ưu thường số lớn nghiệm, việc tìm nghiệm tối ưu địi hỏi nhiều thời gian
(5)5
theo kỹ thuật tham lam cho ta nghiệm tối ưu, chẳng hạn thuật tốn Dijkstra tìm đường ngắn từ đỉnh tới đỉnh lại đồ thị định hướng, thuật tốn Prim Kruskal tìm bao trùm ngắn đồ thị vô hướng
Lược đồ tổng quát thuật toán tham lam:
procedure Greedy; begin
X:=; i:=0;
while (Chua_xay_dung_het_cac_thanh_phan_cua_nghiem) begin
i:=i+1;
<Xac_dinh_Si>;
X Select(Si);
end; end;
II Một số toán giải phương pháp tham lam: 1 Bài toán ba lơ
a Phát biểu tốn
Có n vật, vật i, i∈{1, ,n} đặc trưng
trọng lượng wi giá trị vi Có ba lơ có khả mang trọng lượng m Giả sử wi, vi, m ∈N* ∀i∈{1, ,n} Hãy chọn vật xếp vào ba lô cho ba lơ thu có giá trị
Dữ liệu: Vào từ file BAG.INP có dạng:
- Dịng gồm n m cách dấu cách
- n dòng tiếp theo, dòng thứ i cho biết trọng lượng giá trị vật thứ i.
Kết quả: Ghi vào file BAG.OUT có dạng:
- Dịng ghi giá trị tối ưu toán
(6)6
- n dòng tiếp theo, dòng cho biết số thứ tự đồ vật số lượng lấy đồ vật thứ i
Ví dụ:
BAG.INP BAG.OUT
Test 11 3 10 15 1 2 Test 37 15 30 10 25 2 83 3 Test 5 1
b Phân tích thiết kế thuật tốn
Ý tưởng: Ở đây, ta cần quan tâm đến yếu tố “đơn giá” loại đồ vật, tức
tỉ lệ giá trị/trọng lượng, đơn giá cao đồ quý Từ ta có kỹ thuật tham lam áp dụng cho toán là:
- Bước 1: Tính đơn giá cho loại đồ vật
- Bước 2: Sắp xếp loại đồ vật theo đơn giá từ lớn đến nhỏ
- Bước 3: Xét đồ vật theo thứ tự xếp, với đồ vật xét lấy số lượng tối đa mà trọng lượng lại ba lô cho phép
- Bước 4: Xác định trọng lượng cịn lại ba lơ quay lại bước xét hết đồ vật trọng lượng cịn lại ba lơ
Minh họa với liệu test 1:
(7)7
Loại đồ vật Trọng lượng w Giá trị v Đơn giá c
1 3 3/3 =
2 6/4 = 1.5
3 4/5 = 0.8
4 10 10/9 = 1.1
5 2/4 = 0.5
Bước 2: Sắp xếp lại đồ vật theo thứ tự từ lớn đến bé, ta có bảng sau:
Loại đồ vật Trọng lượng w Giá trị v Đơn giá c
2 6/4 = 1.5
4 10 10/9 = 1.1
1 3 3/3 =
3 4/5 = 0.8
5 2/4 = 0.5
Bước 4:
+ Với m = 11, xét đồ vật 2, ta lấy số lượng Khi m tính lại
m = 11 – =
+ Với m = 3, xét đồ vật 4, ta lấy đồ vật vượt trọng lượng lại ba lô
+ Với m = 3, xét đồ vật 1, ta lấy số lượng Khi m tính lại m
= – =
+ Do trọng lượng cịn lại ba lơ nên dừng thuật toán
Vậy ta lấy đồ vật 2, với số lượng tương ứng 2, với giá trị tối ưu 15
Nhận xét:
(8)8
nghiệm đủ tốt mà thời gian thực nhanh, lý thuật tốn tham lam người quan tâm
Với test cho, ta tìm nghiệm tối ưu test đầu, test thứ không cho nghiệm tối ưu Muốn tìm nghiệm tối ưu, ta dùng phương pháp quy hoạch động cho toán
Độ phức tạp thuật tốn: Q trình xếp làm theo QuickSort
O(nlog(n)), trình lặp n đồ vật O(n) Vậy tổng độ phức tạp thuật toán
O(nlog(n))
2 Bài toán Chọn hoạt động a Phát biểu toán
Chúng ta có nhiều nhiệm vụ ngày Giả sử có 𝑛 nhiệm vụ nhiệm vụ thứ 𝑖 phải bắt đầu sau thời điểm 𝑠𝑖, thực liên tục kết thúc thời điểm fi Có thể coi nhiệm vụ tương ứng với khoảng thời gian thực hiện(𝑠𝑖, fi] Hãy chọn nhiều nhiệm vụ để làm, cho khơng có thời điểm phải làm hai nhiệm vụ lúc, hay nói cách khác, khoảng thời gian thực hai nhiệm vụ không giao
Dữ liệu: Vào từ file AS.INP có dạng:
- Dòng số
- dòng tiếp theo, dòng thứ cho biết chứa hai số nguyên
( )
Kết quả: Ghi vào file AS.OUT có dạng:
- Dòng ghi số hoạt động chọn - Ḍng cho biết hoạt động chọn
Ví dụ:
AS.INP AS.OUT
Test
5
(9)9
0
Test
11 12 14 13 10 11 12
4
2 10
b Phân tích thiết kế thuật tốn Ý tưởng:
Trước tiên ta có nhận xét phương án tối ưu, chắn có phương án mà nhiệm vụ chọn nhiệm vụ kết thúc sớm
Gọi i1 nhiệm vụ kết thúc sớm Với phương án tối ưu bất kỳ, giả sử thứ tự nhiệm vụ cần thực phương án tối ưu (j1, j2, …, jk) Do i1 nhiệm vụ kết thúc sớm nên chắn khơng thể kết thúc muộn j1, việc thay j1 i1 phương án không gây xung đột thời gian thực nhiệm vụ Sự thay không làm giảm bớt số lượng nhiệm vụ thực phương án tối ưu, nên (i1, j2, …, jk) phương án tối ưu Yêu cầu toán cần đưa phương án tối ưu, ta phương án tối ưu có nhiệm vụ nhiệm vụ kết thúc sớm số 𝑛 nhiệm vụ Điều có nghĩa không cần thử 𝑛 khả chọn nhiệm vụ đầu tiên, giải toán con, đánh giá chúng để đưa định cuối Chúng ta đưa định tức thời: chọn nhiệm vụ kết thúc sớm i1 làm nhiệm vụ
(10)10
Và chọn tiếp nhiệm vụ i3, i4… Cứ khơng cịn nhiệm vụ chọn
Đến ta thiết kế thuật toán tham lam sau:
Sắp xếp nhiệm vụ theo thứ tự không giảm thời điểm kết thúc
Khởi tạo thời điểm 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒∶=
Duyệt nhiệm vụ theo danh sách xếp (nhiệm vụ kết thúc sớm xét trước nhiệm vụ kết thúc muộn), xét đến nhiệm vụ 𝑖 có [𝑖] ≥ 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 chọn nhiệm vụ 𝑖 vào phương án tối ưu cập nhật 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 thành thời điểm kết thúc nhiệm vụ 𝑖: 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒≔𝑓[𝑖]
Minh họa với liệu test 1:
Ta có bảng liệu sau xếp là:
Công việc i
s[i]
f[i]
FinishTime =
Với bảng trên, ta chọn công việc trước, cập nhật lại FinishTime= Tiếp tục duyệt cơng việc cịn lại, cơng việc khơng chọn
s[4]< FinishTime
Ta chọn công việc s[5] FinishTime, cập nhật lại FinishTime = Công việc không chọn s[4]< FinishTime.
Ta chọn công việc s[1] FinishTime, cập nhật lại FinishTime = Dừng thuật toán lại xét hết cơng việc
Hình 0.1. Minh họa toán Chọn hoạt động
(11)11
Độ phức tạp thuật toán: Dễ thấy thủ tục GreedySelection thực
hiện thời gian O(n) Thời gian chủ yếu nằm thuật tốn xếp cơng việc theo thời điểm kết thúc Như đoạn code trên, ta dùng QuickSort, độ phức tạp thuật tốn O(nlog(n))
3 Bài toán máy rút tiền tự động ATM a Phát biểu toán
Một máy ATM có n (n < 20) tờ tiền có mệnh giá t1, t2, , tn Hãy tìm cách trả tờ với số tiền s
Dữ liệu: Vào từ file ATM.INP có dạng:
- Dòng gồm n s cách dấu cách - Dòng thứ gồm n số t1, t2, …, tn.
Kết quả: Ghi vào file ATM.OUT, khơng có phương án ghi -1, ngược lại
thì:
- Dịng ghi số tờ tiền - Dòng ghi cách trả tiền
Ví dụ:
ATM.INP ATM.OUT
Test 10 390
200 10 20 20 50 50 50 50 100 100
5
200 100 50 20 20
Test 11 100
50 20 20 20 20 20 2 2
8
50 20 20 2 2
Test
6 100
50 20 20 20 20 20
-1
b Phân tích thiết kế thuật tốn
Ý tưởng: Như phân tích ví dụ 1.2, ta thiết kế thuật toán tham lam sau:
tại bước ta chọn tờ tiền lớn cịn lại khơng vượt q lượng tiền cịn phải trả, cụ thể:
(12)12
Bước 2: Lần lượt xét tờ tiền từ giá trị lớn đến giá trị nhỏ, chưa lấy đủ s tờ tiền xét có giá trị nhỏ s lấy ln tờ tiền
Minh họa với liệu test 1:
Bước 1: Sau xếp tờ tiền giảm dần theo giá trị, ta có:
200 100 100 50 50 50 50 20 20 10
Bước 2: Với tiền cần rút 390, ta chọn phần tử dãy, số tiền cần rút cịn lại 90 Do ta khơng chọn tờ 100, mà xét tiếp phần tử Ta chọn tờ 50, tiền cần rút cịn lại 40 Vì ta không chọn tờ 50 Xét tiếp ta chọn hai tờ 20, đủ số tiền cần rút, dừng thuật toán Kết ta nhận 200 100 50 20 20
Nhận xét:
Với test 1, thuật toán tham lam cho nghiệm tối ưu Tuy nhiên với test 2, thuật tốn tham lam khơng cho nghiệm tối ưu test thuật tốn tham lam khơng tìm nghiệm có nghiệm
Độ phức tạp thuật toán: Thuật toán tham lam qua lần duyệt, có độ phức tạp
là O(n) Tồn trình thời gian nhiều trình xếp, ta xếp theo phương pháp chọn, có độ phức tạp O(n2). Vậy độ phức tạp thuật toán O(n2).
4 Bài toán băng nhạc a Phát biểu toán
(13)13
Dữ liệu: Vào từ file CASSETTE.INP có dạng:
- Dịng số tự nhiên n cho biết số lượng hát
- Dòng thứ gồm n số nguyên dương thể dung lượng tính theo phút bài, số cách dấu cách
Kết quả: Dữ liệu ghi vào file CASSETTE.OUT có dạng:
- n dòng thể trật tự ghi hát băng, dòng gồm hai số nguyên dương j d cách dấu cách, j số thứ tự hát cần ghi, d thời gian tìm phát theo trật tự
- Dịng thứ n+1 ghi tổng số thời gian quay băng hát phát lần ngày
Ví dụ:
CASSETTE.INP CASSETTE.OUT
3
2 12 19
b Phân tích thiết kế thuật tốn
Ý tưởng:
Giả sử ta có ba hát với số phút sau:
Mã số hát
Thời gian phát
Ta xét vài tình ghi băng để rút kết luận cần thiết:
Trật tự ghi băng (x,y,z)
Thời gian phát
t(x) + t(y) + t(z); t(i): thời gian tìm phát i
(1,2,3) (7) + (7 + 2) + (7 + + 3) = 28 phút (1,3,2) (7) + (7 + 3) + (7 + + 2) = 29 phút (2,1,3) (2) + (2 + 7) + (2 + + 3) = 23 phút
(14)14
(3,1,2) (3) + (3 + 7) + (3 + + 2) = 25 phút (3,2,1) (3) + (3 + 2) + (3 + + 7) = 20 phút
Vậy phương án tối ưu (2,3,1): ghi đến 3, cuối ghi Tổng thời gian theo phương án 19 phút
Để có phương án tối ưu, ta cần ghi băng theo trật tự tăng dần thời lượng Bài toán cho với giả thiết băng đủ lớn để ghi toàn [3]
Minh họa với liệu cho:
Rất đơn giản, ta cần xếp theo thứ tự không giảm thời gian phát nhạc Với liệu cho ta có thứ tự 2, 3, thời gian phát toàn tính + (2 + 3) + (2 + + 7) = 19 phút
Nhận xét:
Thuật toán tham lam trường hợp thời gian phát nhạc tính theo trật tự tăng dần Lời giải tìm lời giải tối ưu
Độ phức tạp thuật toán: Thuật toán tham lam qua lần duyệt, có độ phức tạp
là O(n) Tồn trình thời gian nhiều trình xếp, ta xếp theo QuickSort, có độ phức tạp O(nlog(n)) Vậy độ phức tạp thuật toán
O(nlog(n))
5 Bài toán nối điểm đen trắng a Phát biểu toán
Trên trục số thực cho n điểm đen n điểm trắng hoàn toàn phân biệt Các điểm đen có tọa độ ngun a1, a2, …, an cịn điểm trắng có tọa độ nguyên b1, b2, …, bn Người ta muốn chọn k điểm đen k điểm trắng để nối điểm đen với điểm trắng cho k đoạn thẳng tạo đơi khơng có điểm chung
u cầu: Cho tọa độ n điểm đen a1, a2, …, anvà tọa độ điểm trắng b1, b2,
…, bn Hãy tìm giá trị k lớn thỏa mãn yêu cầu [7]
Dữ liệu: Vào từ file BAW.INP có dạng:
- Dịng thứ chứa số nguyên dương n (0 < n ≤ 105)
(15)15
- Dòng thứ ba chứa số b1, b2, …, bn (|bi| <= 109, i = 1, 2,…, n) Các số dòng ghi cách dấu cách
Kết quả: Ghi số nguyên số k lớn tìm
Ví dụ:
BAW.INP BAW.OUT
3 -3 -1
2
b Phân tích thiết kế thuật tốn
Hình 0.2. Minh họa liệu toán nối điểm đen trắng
Ý tưởng:
Trộn hai màu lại thành danh sách trục tọa độ xếp lại theo thứ tự tăng dần Dùng ý tưởng tham lam duyệt thứ tự từ trái sang phải, thấy hai cặp liền khác màu tăng biến đếm lên 1, nhận thấy cần chọn cặp điểm kề khác màu trục nhiều cặp điểm không giao Ta ý tăng bước nhảy để tránh trường hợp điểm đen (trắng) kẹp điểm trắng (đen) tính thành đoạn đề nói điểm đen trắng phân biệt
Minh họa với liệu cho:
Theo đề ta có biến giá trị sau:
Biến Giá trị
N
A
B -3 -1
(16)16
Thêm tất giá trị mảng b thêm sau mảng a, đồng thời vị trí thêm
đó mảng color ta đánh dấu (để phân biệt với phần tử mảng a) Ta có bảng sau:
Biến
a -3 -1
color 0 1
Sắp xếp lại mảng a theo thứ tự tăng dần, đổi chỗ hai phần tử mảng a ta đồng thời đổi mảng color Ta có liệu sau sau xếp lại:
Biến
a -3 -1
color 1 0 0 1
Ta duyệt mảng color từ trái sang phải, thấy hai cặp liền khác giá trị tăng biến res lên Sau duyệt, biến res = kết tối ưu toán
Nhận xét: Thuật toán tham lam thể chỗ sau xếp liệu, ta
cần chọn cặp điểm liền kề khác màu trục kết tối ưu
Độ phức tạp thuật toán: Thuật toán tham lam qua lần duyệt n phần tử có
O(n), xếp dùng QuickSort có O(nlog(n)) Vậy độ phức tạp thuật toán
(17)17
QUY HOẠCH ĐỘNG
Nguyễn Chánh Tín - THPT Chuyên Bạc Liêu
Phần 1: CƠ SỞ LÝ THUYẾT I- CƠ SỞ LÝ THUYẾT:
1) Phương pháp quy hoạch động:
Nhà toán học Richard Bellman phát minh phương pháp Quy hoạch động vào năm 1953 Ngành thành lập chủ đề kỹ nghệ phân tích hệ thống tổ chức IEEE (Viện kỹ nghệ Điện Điện tử quốc tế) thừa nhận
Phương pháp quy hoạch động dùng để giải tốn tối ưu có tính chất đệ quy, tức việc tìm phương án tối ưu cho tốn đưa tìm phương án tối ưu số hữu hạn toán Đối với nhiều thuật toán đệ quy, nguyên lý chia để trị (devide and conquer) thường đóng vai trị chủ đạo việc thiết kế thuật toán Để giải tốn lớn, ta chia thành nhiều tốn dạng với để giải độc lập Trong phương pháp quy hoạch động, nguyên lý thể rõ: Khi cần phải giải toán nào, ta giải tất toán lưu trữ lời giải hay đáp số chúng với mục đích sử dụng lại theo phối hợp để giải tốn tổng qt Đó điểm khác Quy hoạch động phép đệ quy và nội dung Phương pháp quy hoạch động:
+ Đệ quy toán lớn phân rã thành nhiều toán giải
bài tốn Việc giải tốn lại đưa phép phân rã tiếp thành nhiều toán nhỏ lại giải toán nhỏ giải hay chưa
+ Quy hoạch động việc giải tất toán nhỏ (bài toán
sở) để từ bước giải toán lớn hơn, giải toán lớn (bài toán ban đầu)
Sau ta xét ví dụ đơn giản quen thuộc:
Ví dụ: Dãy Fibonacci dãy số nguyên dương định nghĩa sau: F[1]=F[2]=1;
(18)18
Hãy tính F[N]
Xét hai cách cài đặt chương trình: *Cách 1: Dùng đệ quy
function F (i: longint): longint; begin
if i<=2 then exit(1)
else exit(F(i-1)+F(i-2)); end;
*Cách 2: Dùng quy hoạch động F[1]:=1; F[2]:=1;
For i:=3 to MaxN F[i]:=F[i-1]+F[i-2] Nhận xét:
Trong cách 1, ta viết hàm đệ quy F(i) để tính số Fibonacci thứ i Giả sử ta cần tính F[6]: Chương trình gọi F(6), gọi tiếp F(5) F(4) để tính … Q trình tính tốn vẽ Ta nhận thấy để tính F(6) phải tính lần F(5), lần F(4), lần F(3), lần F(2), lần F(1)
Cách khơng Trước hết tính sẵn F[1] F[2], từ tính tiếp F[3], lại tính tiếp F[4], F[5], F[6] Ðảm bào giá trị Fibonnaci phải tính lần
(Cách cịn cải tiến thêm nữa, cần dùng biến tính lại giá trị lẫn nhau).
(19)19
+ Bài toán lớn phải phân rã thành nhiều toán con, mà phối hợp lời giải tốn cho ta lời giải tốn lớn
+ Vì quy hoạch động giải tất toán con, nên không đủ không gian vật lý lưu trữ lời giải (bộ nhớ, đĩa,…) để phối hợp chúng phương pháp quy hoạch động thực
+ Q trình từ tốn sở tìm lời giải toán ban đầu phải qua hữu hạn bước
2) Các khái niệm:
+ Bài toán giải theo phương pháp quy hoạch động gọi bài toán quy hoạch
động
+ Cơng thức phối hợp nghiệm tốn để có nghiệm tốn lớn gọi cơng thức truy hồi quy hoạch động
+ Tập tốn nhỏ nhất có lời giải để từ giải tốn lớn gọi cơ sở quy hoạch động
+ Không gian lưu trữ lời giải tốn để tìm cách phối hợp chúng gọi bảng phương án của quy hoạch động
3) Các bước cài đặt chương trình sử dụng quy hoạch động:
+ Giải tất tốn sở (thơng thường dễ), lưu lời giải vào bảng phương án
+ Dùng công thức truy hồi phối hợp lời giải toán nhỏ lưu bảng phương án để tìm lời giải tốn lớn lưu chúng vào bảng phương án Cho tới tốn ban đầu tìm lời giải
+ Dựa vào bảng phương án, truy vết tìm nghiệm tối ưu
Cho đến nay, chưa có định lý cho biết cách xác tốn giải hiệu quy hoạch động Tuy nhiên để biết tốn giải quy hoạch động hay khơng, ta tự đặt câu hỏi : “Một nghiệm tối ưu tốn lớn có phải phối hợp nghiệm tối ưu bài tốn hay khơng?” “Liệu lưu trữ nghiệm toán
(20)20
Cuối trước khảo sát số toán quy hoạch động, ta nhắc lại: Phương pháp tốt để giải toán tin học biết sử dụng phối hợp uyển chuyển nhiều thuật tốn, khơng lạm dụng hay coi thường phương pháp
II- MỘT SỐ BÀI TỐN VÍ DỤ ĐIỂN HÌNH:
1) Bài 1: Trước tiên xét tốn thật đơn giản quen thuộc
tìm giá trị lớn n số a1, a2, , an Giải toán này, ta xây dựng
các cấu hình tối ưu cách tìm số lớn k số với k
chạy từ đến n:
K=1: max1:=a1;
K=2: max2:=max(max1,a2); K=3: max3:=max(max2,a3); K=n: maxn:=max(maxn-1,an);
Như k đạt tới n maxn giá trị lớn n số chọ Việc
cài đặt chương trình đơn giản sau: Uses crt;
Var a: array[1 100] of integer; n,k,max: integer;
Begin
Write('Cho so luong phan tu: ');readln(n); For i:=1 to n
begin
write('a[',i,']= '); readln(a[i]); end;
Max:=a[1]; For k:=2 to n
If a[k]>max then max:=a[k];
Write('Gia tri lon nhat cua day cac so da cho la: ',max); Readln
(21)21
Bây xét đến tốn có phần hấp dẫn Đây tốn kinh điển cho giải thuật qui hoạch động:
2) Bài 2: BÀI TỐN XẾP BA LƠ (một số sách ghi bài toán túi, tương tự
như bài toán xếp vali) tốn tối ưu hóa tổ hợp Bài toán đặt tên từ vấn
đề chọn quan trọng nhét vừa vào túi (với giới hạn khối lượng) để mang theo chuyến
Một số cách phát biểu nội dung toán:
Một kẻ trộm đột nhập vào cửa hiệu tìm thấy có n mặt hàng có trọng lượng giá trị khác nhau, hắn mang theo túi có sức chứa trọng lượng tối đa M Vậy kẻ trộm nên bỏ vào ba lô số lượng để đạt giá trị cao khả mà hắn mang được.
Một hành khách mang theo vali có khối
lượng hàng hố tối đa M Hành khách chuẩn bị N dồ vật đánh số từ đến N để chuẩn bị mang theo Đồ vật thứ i có trọng lượng giá trị sử dụng
ci (i = 1, 2, N) Yêu cầu: Chỉ đồ vật mà hành khách cần mang theo cho
tổng giá trị sử dụng lớn nhất? * Bài tốn:
Trong siêu thị có n gói hàng (n ≤ 100), gói hàng thứ i có trọng lượng Wi ≤ 100
và trị giá Vi ≤ 100 Một tên trộm đột nhập vào siêu thị, tên trộm mang theo túi
có thể mang tối đa trọng lượng M ( M ≤ 100) Hỏi tên trộm lấy gói hàng để tổng giá trị lớn
Input: file văn BAG.INP
• Dịng 1: Chứa hai số n, M cách dấu cách
• n dịng tiếp theo, dịng thứ i chứa hai số nguyên dương Wi, Vi cách dấu cách
Output: file văn BAG.OUT
(22)22
BAG.INP BAG.OUT
5 11
3
4
5
9 10
4
11
* Ý týởng giải thuật:
Nếu gọi F[i, j] giá trị lớn có cách chọn gói {1, 2, …, i} với giới hạn trọng lượng j Thì giá trị lớn chọn số n gói với giới hạn trọng lượng M F[n, M]
Cơng thức truy hồi tính F[i, j].
Với giới hạn trọng lượng j, việc chọn tối ưu số gói {1, 2, …,i – 1, i} để có giá trị lớn có hai khả năng:
• Nếu khơng chọn gói thứ i F[i, j] giá trị lớn cách chọn số gói {1, 2, …, i – 1} với giới hạn trọng lượng j Tức
F[i, j] = F[i - 1, j]
• Nếu có chọn gói thứ i (tất nhiên xét tới trường hợp mà Wi ≤ j) F[i, j] giá trị gói thứ i Vi cộng với giá trị lớn có cách chọn số gói {1, 2, …, i – 1} với giới hạn trọng lượng j – Wi Tức mặt giá trị thu được:
F[i, j] = Vi + F[i - 1, j - Wi]
Vì theo cách xây dựng F[i, j] giá trị lớn có thể, nên F[i, j] max giá trị thu
Cơ sở quy hoạch ðộng:
Dễ thấy F[0, j] = giá trị lớn cách chọn số gói =
(23)23
Bảng phương án F gồm n + dòng, M + cột, trước tiên điền sở quy hoạch động: Dịng gồm tồn số Sử dụng cơng thức truy hồi, dùng dịng tính dịng 1, dùng dịng tính dịng 2, v.v… đến tính hết dịng n
Truy vết:
Tính xong bảng phương án ta quan tâm đến F[n, M] giá trị lớn thu chọn n gói với giới hạn trọng lượng M Nếu F[n, M] = F[n - 1, M] tức khơng chọn gói thứ n, ta truy tiếp F[n - 1, M] Còn F[n, M] ≠ F[n - 1, M] ta thơng báo phép chọn tối ưu có chọn gói thứ n truy tiếp F[n - 1, M - Wn] Cứ tiếp tục truy lên tới hàng bảng phương án
Chương trình:
const max = 100; var
w, v: array[1 max] of integer; f: array[0 max, max] of integer; n, m: integer;
procedure enter; var i: integer; begin
readln(n, m);
for i := to n readln(w[i], v[i]); end;
procedure optimize; {tinh bang phuong an} var i, j: integer;
begin
(24)24
for j := to m begin {tinh f[i, j]}
f[i, j] := f[i - 1, j]; {Neu khong chon goi thu i} if (j>= w[i]) and (f[i, j] < f[i-1, j-w[i]]+v[i]) then f[i, j] := f[i-1, j-w[i]]+v[i];
end; end;
procedure trace; {truy vet tim nghiem toi uu} begin
writeln(f[n, m]); {in gia tri lon nhat co the tim duoc} while n <> {truy vet tu hang n len hang 0}
begin
if f[n, m] <> f[n - 1, m] then {neu co chon goi thu n} begin
write(n, ' '); m := m - w[n]; end;
dec(n); end; end; Begin
assign(input, 'bag.inp'); reset(input); assign(output, 'bag.out'); rewrite(output); enter;
optimize; trace;
(25)25
3) Bài 3: DÃY CON ĐƠN ĐIỆU TĂNG DÀI NHẤT
Bài toán: Cho dãy số nguyên A = a1, a2, …, an (n ≤ 10000, -10000 ≤ ≤
10000) Một dãy A cách chọn A số phần tử giữ nguyên thứ tự Như A có 2n dãy
Yêu cầu: Tìm dãy đơn điệu tăng A có độ dài lớn
Ví dụ: A = (1, 2, 3, 4, 9, 10, 5, 6, 7, 8) Dãy đơn điệu tăng dài là: (1, 2, 3, 4, 5, 6, 7, 8)
* Dữ liệu (Input) vào từ file văn INCSEQ.INP • Dịng 1: Chứa số n
• Dịng 2: Chứa n số a1, a2, …, an cách dấu cách
* Kết (Output) ghi file văn INCSEQ.OUT
• Dịng 1: Ghi độ dài dãy tìm
• Các dịng tiếp: ghi dãy tìm số phần tử chọn vào dãy
INCSEQ.INP INCSEQ.OUT
11
1 20 10
8
a[1] = a[2] = a[3] = a[6] = a[7] = a[8] = a[10] = a[11] = 10
* Ý týởng giải thuật:
Bổ sung vào A hai phần tử: a0 = -∞ an+1 = +∞ Khi dãy đơn điệu tăng
(26)26
Với i: ≤ i ≤ n + Ta tính L[i] = độ dài dãy đơn điệu tăng dài bắt đầu
Cơ sở quy hoạch ðộng (bài toán nhỏ nhất):
L[n+1] = Độ dài dãy đơn điệu tăng dài bắt đầu an+1 = +∞ Dãy gồm phần tử (+∞) nên L[n+1]=1
Công thức truy hồi: Giả sử với i từ n đến 0, ta cần tính L[i]: độ dài dãy tăng
dài bắt đầu L[i] tính điều kiện L[i + 1], L[i + 2], …, L[n + 1] biết:
Dãy đơn điệu tăng dài thành lập cách lấy ghép vào đầu số dãy đơn điệu tăng dài bắt đầu vị trí aj đứng sau Ta chọn dãy để ghép vào đầu? Tất nhiên ghép vào đầu dãy bắt đầu aj lớn (để đảm bảo tính tăng) dĩ nhiên ta chọn dãy dài để ghép vào đầu (để đảm bảo tính dài nhất) Vậy L[i] tính sau: Xét tất số j khoảng từ i + ðến n + mà aj >ai, chọn ra số jmax có L[jmax] lớn Ðặt L[i] := L[jmax] + 1.
Truy vết: Tại bước xây dựng dãy L, tính L[i] = L[jmax] + 1, ta đặt T[i]
= jmax Để lưu lại rằng: Dãy dài bắt đầu có phần tử thứ hai
ajmax
Sau tính xong hay dãy L T, ta T[0] phần tử chọn,
T[T[0]] phần tử thứ hai chọn,
T[T[T[0]]] phần tử thứ ba chọn …Q trình truy vết diễn tả sau:
Ví dụ: với A = (5, 2, 3, 4, 9, 10, 5, 6, 7, 8) Hai dãy L T sau tính là:
Chương trình:
(27)27
fi='INCSEQ.INP'; fo='INCSEQ.OUT';
var a, L, T: array[0 max + 1] of Integer; n: Word;
procedure Enter; var i: Word; begin
ReadLn(n);
for i := to n Read(a[i]); end;
procedure Optimize; {QHD} var i, j, jmax: Word;
begin
a[0] := -32768; a[n + 1] := 32767;
L[n + 1] := 1; {Dien co so quy hoach dong} for i := n downto {Tinh bang phuong an} begin
jmax := n + 1;
for j := i + to n +
if (a[j] > a[i]) and (L[j] > L[jmax]) then jmax := j; L[i] := L[jmax] + 1;
T[i] := jmax; end;
WriteLn(L[0] - 2); i := T[0]; {Truy vet} while i <> n + begin
WriteLn('a[', i, '] = ', a[i]); i := T[i];
(28)28
end;
Begin
Assign(Input, fi); Reset(Input); Assign(Output,fo); Rewrite(Output); Enter;
Optimize;
Close(Input); Close(Output); End
4) Bài 4: BÀI TỐN TÌM XÂU CON CHUNG DÀI NHẤT
Bài toán: Cho hai xâu X =x1x2…xm Y=y1y2…yn Tìm xâu Z = z1z2…zk xâu chung dài X Y Một xâu xâu A cách chọn A số phần tử giữ nguyên thứ tự
* Ý tưởng giải thuật: Bảng phương án:
Ta dùng mảng hai chiều B[0 m, n] làm bảng phương án, B[i,j] độ dài xâu chung dài xâu Xi (phần đầu X) Yj (phần đầu Y)
Cơ sở: Rõ ràng hai xâu X Y xâu rỗng (j=0
i=0) thì B[i,j]=0.
Cơng thức truy hồi: Dễ dàng có nhận xét sau:
- Nếu i,j > xi=yi B[i,j] = B[i-1,j-1] +
- Nếu i,j > xi<>yj thì B[i,j] = max ( B[i,j-1], B[i-1,j] )
Truy vết :
Như B[n,m] cho biết độ dài xâu chung dài Để chi tường minh xâu chung dài ta cần xây dựng bảng T[1 m, n] để ghi nhận truy vết đánh dấu B[i,j] tính từ B[i-1,j-1] hay B[i,j-1] hay B[i-1,j]
Chương trình:
(29)29
b,t:array[0 100,0 100] of integer; m,n,len:integer;
procedure getinput; var f:text;
begin
assign(f,fi); reset(f); readln(f,s1); readln(f,s2); close(f);
m:=length(s1); n:=length(s2); end;
procedure optimize; var i,j:integer; begin
for i:=0 to m b[i][0]:=0; for j:=0 to n b[0][j]:=0; for i:=1 to m
for j:=1 to n if s1[i]=s2[j] then begin
b[i,j]:=b[i-1,j-1]+1; t[i,j]:=0;
end else
if b[i-1,j]>b[i,j-1] then begin
b[i,j]:=b[i-1,j]; t[i,j]:=1; end
(30)30
begin
b[i,j]:=b[i,j-1]; t[i,j]:=-1; end;
end;
procedure trace; begin
len:=b[m,n]; s:='';
while (m>0) and (n>0) begin
if t[m,n]=0 then begin
s:=s1[m]+s; m:=m-1; n:=n-1; end else
if t[m,n]=1 then m:=m-1 else n:=n-1; end;
end;
procedure putoutput; var f:text;
i:integer; begin
(31)31
getinput; optimize; trace; putoutput; End
5) Bài 5: BÀI TOÁN XÂU CON ĐỐI XỨNG DÀI NHẤT
Bài toán: Một xâu gọi đối xứng (palindrome) đọc xâu
từ phải sang trái thu xâu ban đầu
Yêu cầu: tìm xâu đối xứng dài xâu s cho trước Xâu xâu thu xóa số ký tự từ xâu ban đầu
* Dữ liệu vào: Gồm dòng chứa xâu S, gồm chữ in
thường
* Kết quả: Gồm dòng xâu đối xứng dài xâu S
Nếu có nhiều kết quả, cần in kết
Giới hạn: Xâu S có độ dài khơng vượt q 2000 Ví dụ: Dữ liệu vào: lmevxeyzl Kết quả: level 2) Ý tưởng giải thuật:
Ta chuyển toán toán quy hoạch động là: Bài tốn tìm xâu chung dài nhất.
Với liệu vào S1
Ta tạo xâu S2 xâu ngược S1bằng cách chép phần tử xâu S1 vào xâu S2 theo thứ tự ngược lại
Sau ta tìm xâu chung dài S1 S2
Ta cần tìm xâu chung dài phần S1 nghịch đảo phần lại, tức ta xét phần bảng phương án với i+j<=chiều dài S1
Ví dụ:
S1 = lmevxeyzl
(32)32
Khi xâu chung dài
le của S14=“lmev” S24=”lzye” (hoặcS13=”lme” S25=”lzyex”)
Khi ta truy vết để tìm xâu chung ta kiểm tra xem xâu đối xứng lẻ hay chẵn (số kí tự)
+ Nếu i+j=chiều dài S1, tức kí tự đối xứng đứng liên tiếp S1, xâu đối xứng chẵn
+ Ngược lại, tức mọi i+j<chiều dài S1, S1, có kí tự xen hai kí tự đối xứng, nên xâu đối xứng lẻ Trong ví dụ có hai kí tự v x xen xâu đối xứng
Với xâu đối xứng chẵn ta việc chép lại nửa sau dựa vào nửa đầu
Còn xâu lẻ ta chọn thêm kí tự xen Theo ví dụ trên, chọn v x Như xâu đối xứng dài level lexel
* Chương trình:
const fi='xaucon.inp'; fo='xaucon.oup'; var s1,s2,s: ansistring;
b,t:array[0 2000,0 2000] of integer; m,n,len:integer;
procedure getinput; var f:text; i: longint; begin
assign(f,fi); reset(f); readln(f,s1); close(f);
m:=length(s1);
for i:=m downto s2:=s2+s1[i]; n:=length(s2);
end;
(33)33
var i,j:integer; begin
for i:=0 to m b[i][0]:=0; for j:=0 to n b[0][j]:=0; for i:=1 to m
for j:=1 to n if s1[i]=s2[j] then begin
b[i,j]:=b[i-1,j-1]+1; t[i,j]:=0;
end else
if b[i-1,j]>b[i,j-1] then begin
b[i,j]:=b[i-1,j]; t[i,j]:=1; end
else begin
b[i,j]:=b[i,j-1]; t[i,j]:=-1; end;
end;
procedure trace; begin
len:=b[m,n]; s:='';
while (m>0) and (n>0) begin
if t[m,n]=0 then begin
(34)34
m:=m-1; n:=n-1; end else
if t[m,n]=1 then m:=m-1 else n:=n-1; end;
end;
procedure putoutput; var f:text;
i:integer; begin
assign(f,fo); rewrite(f); writeln(f,len); write(f,s); close(f); end; Begin getinput; optimize; trace; putoutput; End
6) Bài 6: BÀI TOÁN DI CHUYỂN TỪ TÂY SANG ĐÔNG
(35)35
Dữ liệu vào: Từ file vãn dongtay.inp gồm: - Dòng ðầu ghi hai số m, n (2<= m, n <=1000)
- Dòng thứ i m dòng dòng ghi n số số bảng ( |A[I,j]|<=1000)
Kết quả: file vãn dongtay.out gồm: - Dòng ðầu ghi tổng số lớn (nhỏ nhất)
- Dòng thứ hai ghi n số số số dòng býớc ði Dongtay.inp Dongtay.out
1 7 4 7
9
1 3
2) Ý tưởng giải thuật: Gọi B[i, j] số điểm lớn (nhỏ nhất) có
khi tới A[i, j] Rõ ràng cột B[i, 1] = A[i, 1]:
Với ô (i, j) cột khác Vì (i, j – 1), (i – 1, j – 1), (i + 1, j – 1) sang ô (i, j), sang ô (i, j) số điểm cộng thêm A[i, j] Chúng ta cần B[i, j] số điểm lớn nên B[i, j] = max(B[i, j - 1], B[i - 1, j - 1], B[i + 1, j - 1]) + A[i, j] (Hoặc min) Ta dùng công thức truy hồi tính tất B[i, j] Cuối chọn B[i, n] phần tử lớn cột n bảng B từ truy vết tìm đường nhiều điểm
* Chương trình:
(36)36
fi='taydong.inp'; fo='taydong.out';
var a,b,t:array[0 101,1 100] of longint; tong:longint;
m,n,dongcuoi:integer; procedure nhap;
var i,j:integer; begin
readln(m,n); for i:=1 to m begin
for j:=1 to n read(a[i,j]); readln;
end; end;
function min(i,j:integer):integer; var m:integer;
begin m:=i-1;
if b[i,j-1] < b[m,j-1] then m:=i; if b[i+1,j-1] < b[m,j-1] then m:=i+1; min:=m;
end;
procedure taobang; var i,j,d:integer; begin
for j:=1 to n begin
b[0,j]:=max; b[m+1,j]:=max; end;
(37)37
for j:=2 to n for i:=1 to m begin
d:=min(i,j);
b[i,j]:=b[d,j-1]+a[i,j]; t[i,j]:=d;
end; tong:=b[1,n]; dongcuoi:=1; for i:=2 to m if tong>b[i,n] then begin
tong:=b[i,n]; dongcuoi:=i; end;
end;
procedure truyvet(dong,cot:integer); begin
if cot=0 then writeln(tong) else
begin
truyvet(t[dong,cot],cot-1); write(dong,' ');
end; end; Begin
assign(input,fi); reset(input); assign(output,fo); rewrite(output); nhap;
taobang;
(38)38
close(input); close(output); End
7) Bài 7: BÀI TOÁN CHIA KẸO:
Có N gói kẹo, gói thứ i có Ai kẹo Khơng bóc gói kẹo nào,
cần chia N gói kẹo thành hai phần cho độ chênh lệch số kẹo hai gói Dữ liệu vào file “chiakeo.inp” có dạng :
Dòng số N (N<=100);
Dòng thứ hai N số Ai (i=1, 2, , N; Ai <=100)
Kết file “chiakeo.out” có dạng:
Dịng đầu độ chênh lệch nhỏ hai phần
Dịng hai dãy N số, si =1 gói thứ i thuộc phần 1, si =2 gói
thứ i thuộc phần
* Lời giải:
- Với số M bất kì, ta biết có tồn cách chọn gói kẹo để tổng số kẹo gói chọn M khơng, tốn giải Vì đơn giản ta cần chọn số M cho M gần với S/2 (với i =1,2, ,N) Sau xếp gói kẹo để tổng M vào phần một, phần thứ hai gồm gói kẹo cịn lại
- Để kiểm tra điều ta xây dựng tất tổng có N gói kẹo cách: ban đầu chưa có tổng sinh
- Làm với gói kẹo từ đến N, với gói kẹo thứ i, ta kiểm tra xem có tổng sinh ra, giả sử tổng x1, x2, , xt đến bước
này sinh tổng x1, x2, , xt Ai x1+Ai,x2+Ai, ,xt+Ai
- Với N gói kẹo, mà gói có khơng q 100 kẹo tổng số kẹo không vượt N*100 ≤ 10000 kẹo Dùng mảng đánh dấu D, sinh tổng k D[k] = ngược lại D[k] =
* Chương trình:
(39)39
var a,s : array[1 max]of integer;
d1,d2,tr : array[0 max*max]of integer; n,m,sum : integer;
Procedure docf; var f: text; k : integer; begin
assign(f,fi); reset(f); readln(f,n);
sum:=0;
for k:=1 to n begin
read(f,a[k]); sum:=sum+a[k]; end;
close(f); end;
Procedure lam; var i,j : integer; Begin
fillchar(d1,sizeof(d1),0); fillchar(tr,sizeof(tr),0); d1[0]:=1;
d2:=d1;
for i:=1 to n begin
for j:=0 to sum-a[i]
if (d1[j]=1)and(d2[j+a[i]]=0) then begin
(40)40
d1:=d2; end; end;
Procedure ghif; var m,k : integer; f :text;
Begin
fillchar(s,sizeof(s),0); m:=sum div 2;
while d2[m]=0 dec(m);
assign(f,fo); rewrite(f); writeln(f,sum-2*m); while tr[m]>0 begin
s[tr[m]]:=1; m:=m-a[tr[m]]; end;
for k:=1 to n write(f,k+1,#32); close(f);
end;
BEGIN {main} docf; lam; ghif; END
8) Bài 8: DÃY CON CÓ TỔNG CHIA HẾT CHO K
Cho dãy số nguyên A1, A2, , AN số k Hãy tìm dãy (khơng
(41)41
Dịng gồm số N k (N<=1000; k<=50)
Các dòng chứa số mảng A
Kết file “dayso.out” gồm dòng ghi số phần tử lớn tìm * Thuật tốn: Tìm dãy nhiều phần tử tương đương với việc loại khỏi dãy phần tử để phần tử cịn lại có tổng chia hết cho k Ta gọi d số dư tổng tất phần tử dãy A chia cho k Nếu ta chọn phần tử (thậm chí không chọn) để tổng số chọn chia k dư d phần tử cịn lại dãy cần tìm
Với ta sinh tổng có, song ta xếp tổng có số dư chia cho k làm lớp Ta cần quan tâm tới lớp mà không cần quan tâm tới tổng Sẽ có k lớp, lớp gồm tổng chia cho k dư 1, lớp gồm tổng chia cho k dư 2,…, lớp k-1 gồm tổng chia cho k dư (k-1), lớp k gồm tổng chia hết cho k
Bài toán khác với tốn chia kẹo phải địi hỏi tìm phần tử Để làm điều ta dùng thêm mảng nhãn
Như theo thuật tốn ta làm với liệu lớn so với đề
* Chương trình: Chương trình sau giải với N <=100000 k <= 100 uses crt;
const maxK = 100; fi='dayso.inp'; fo='dayso.out';
var L1,L2 : array[0 maxK]of longint; n,k,p : longint;
Procedure lam; var f : text; i,j,x : longint; Begin
fillchar(L1,sizeof(L1),0); L2:=L1;
(42)42
p:=0;
for i:=1 to n begin read(f,x); x:=x mod k+k; p:=(p+x)mod k; for j:=0 to k-1 if (L1[j]>0) then if (L2[(x+j)mod k]=0)
or(L2[(x+j)mod k]>L1[j]+1) then L2[(x+j)mod k]:=L1[j]+1; L2[x mod k]:=1;
end; close(f); end;
Procedure ghif; var f :text; Begin
assign(f,fo); rewrite(f); write(f,n-L2[p]);
close(f); End;
BEGIN lam; ghif; END
9) Bài 9: HÌNH VNG
(43)43
u cầu: Hãy tìm hình vng gồm ô bảng thoả mãn điều kiện sau:
1 – Hình vng đồng nhất: tức thuộc hình vng phải ghi số giống (0 1)
2 – Cạnh hình vng song song với cạnh bảng – Kích thước hình vng lớn
Input : + Dòng 1: Ghi hai số m, n
+ M dòng tiếp theo, dòng thứ i ghi N số mà số thứ j số ghi ô (i, j) bảng
Output: Gồm dòng ghi kích thước cạnh hình vng tìm
Example Input:
11 13
0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0 1 0 0 1 0 0 0 0 1
Output: 7
* Lời giải: Đây quy hoạch động hay
- Gọi f[i,j] kích thước hình vng có góc đáy ô [i,j]
(44)44
- Bây ta xét đến công thức quy hoạch động ô xét Đương nhiên ta lấy giá trị nhỏ f ô để chắn tạo thành ô vuông: f[i,j] := min(f[i-1,j],f[i,j-1],f[i-1,j-1]) +
-Cơ sở quy hoạch động tốn cạnh bên trái có f = có góc nó kích thước
* Chương trình:
uses math;
const fi = 'HV.INP'; fo = 'HV.OUT';
var m,n,i,j,maxf : integer;
a : array[0 1001,0 1001] of 1; f : array[0 1001,0 1001] of integer;
begin
assign(input,fi); reset(input); assign(output,fo); rewrite(output);
readln(m,n); for i := to m for j := to n read(a[i,j]);
for j := to n+1 f[1,j] := 1; for i := to m+1 f[i,1] := 1; for i := to m
for j := to n
if (a[i,j] = a[i-1,j]) and (a[i,j] = a[i,j-1]) and (a[i,j] = a[i-1,j-1]) then
(45)45
f[i-1,j-1])) + else
f[i,j] := 1; maxf := 1;
for i := to m for j := to n
maxf := max(maxf,f[i,j]); writeln(maxf);
close(input); close(output); end
10) Bài 10: CHIA XÂU THÀNH ÍT NHẤT CÁC PALINDROME
Bài tốn: Tìm cách chia xâu thành Palindrome độ dài ≤1000 * Lời giải
-Sử dụng thuật toán quy hoạch động
-Gọi F[i] số palindrome mà đoạn 1→ j chia thành
⇒ Ta có cơng thức: F[i] = max(F[j] + 1); j < i thỏa mãn: đoạn j+1→ i palindrome
-Đoạn chương trình sau: F[0] := 0;
for i := to n begin
for j := i-1 downto
if (đoạn j+1 i palindrome) then F[i] := max( F[i], F[j]+1 ); end;
- Hai vòng for lồng O(N2), phần kiểm tra đoạn j+1→ i palindrome
hay không O(N), độ phức tạp thuật tốn O(N3) Sẽ khơng khả thi
N = 1000
(46)46
mảng L[i, j] N2 Tổng cộng O(N2) lần kiểm tra O(1) Có thể cải
tiến cách dùng hai mảng chiều L[i] C[i] có ý nghĩa:
- L[i] độ dài lớn palindrome độ dài lẻ nhận s[i] làm tâm;
- C[i] độ dài lớn palindrome độ dài chẵn nhận s[i] s[i+1] làm tâm; L[i] C[i] tính cách Xâu liên tiếp đối xứng dài nhất O(N2) Phần kiểm tra ta viết lại sau:
Function is_palindrome(i, j : integer) : boolean; var t : integer;
Begin
t := j-i+1; if odd (t) then
is_palindrome := (L[(i+j) div 2] >= n) else
is_palindrome := (C[(i+j) div 2] >= n) end;
Vậy thuật tốn có độ phức tạp tính tốn O(N2), chi phí nhớ O(N)
TÀI LIỆU THAM KHẢO
1 Sách giáo khoa Chuyên Tin – NXB Giáo dục – Chủ biên: Hồ Sĩ Đàm Toán rời rạc – Nguyễn Đức Nghĩa, Nguyễn Tô Thành NXB ĐHQG Hà Nội Thuật tốn thơng dụng (Dành cho bồi dưỡng HSG Tin học) – Trần Đỗ Hùng Một số vấn đề chọn lọc tin học – Tập – Nguyễn Xuân My
(47)47
THUẬT TOÁN XÁC ĐỊNH HƯỚNG CỦA MỘT ĐIỂM SO VỚI MỘT VECTOR VÀ NHỮNG ỨNG DỤNG TRONG VIỆC GIẢI BÀI TỐN
HÌNH HỌC
Đoàn Minh Đức
Trường THPT Chuyên Bến Tre
I. Đặt vấn đề
- Chuyên đề hình học chuyên tương đối “Khó chịu” với học sinh giáo viên việc bồi dưỡng Ngoài việc phải nắm số kiến thức tốn hình học, phải vận dụng đặc thù thuật tốn hình học để xử lí - Trong phần nhỏ đóng góp cho trại hè phương nam tơi xin giới thiệu thuật
tốn liên quan tới hướng điểm so với vector việc vận dụng việc giải số tốn hình học
II. Nội dung
Tổ chức liệu cho thuật toán
Type
//Điểm:
Point = record x, y: integer;
end; //Đường thẳng:
Line = Record p1, p2: point;
end; //Đa giác:
Polygon = array[1 n] of point;
1. Thuật toán xác định hướng điểm C so với Vector (phương pháp vector)
(48)48
- Xét điểm A, B, C mặt phẳng tọa độ, vị trí tương đối C so với A, B có khả xãy ra:
b. Thuật toán
function CCW(C, A, B: Point): integer; var
k: integer; begin
if equal(k,0) then CCW := // C thẳng hàng AB else
if k > then CCW := // C bên trái AB else CCW := -1; // C bên phải AB end;
c Nhận xét :
- Bất chấp vị trí điểm A,B,C mặt phẳng, thông qua hệ số k cho phép xác định hướng C so với vector
- Chi phí thuật tốn (1)
A
B C
A
B C
A
B
(49)49
2. Vận dụng giải số toán chuyên đề hình học
2.1Một số toán liên quan điểm, đoạn thẳng đường thẳng
+ Xác định điểm M có thuộc đoạn thẳng nối điểm A,B
Điểm M thuộc đoạn thẳng AB M nằm đường thẳng qua điểm AB tọa chiếu trục tọa độ thuộc tọa độ chiếu xuống trục tọa độ điểm A B
function Inside(M, A, B: Point): boolean; begin
exit(CCW(M,A,B)and(M.x>=min(A.x,B.x))and(M.x<=max(A.x,B.x))an d(M.y>=min(A.y,B.y))and(M.y<=max(A.y,B.y)))
end;
+ Xác định vị trí tương đối điểm M,N so với đường thẳng qua điểm A, B
- điểm M,N nằm phía khác phía so với đường thẳng qua điểm A,B Nếu điểm M N thuộc đường thẳng ta xem M N nằm phía với
function Pos2PWithLine(M, N, A, B: Point): boolean; begin
exit(CCW(M,A,B)*CCW(N,A,B)>=0) end;
+ Xác định đoạn thẳng AB CD có cắt nhau? a. Cách
2 đoạn thẳng giao thỏa điều kiện:
- Hai đường thẳng qua điểm phải cắt I - Và I thuộc đoạn thẳng
(50)50
- Trường hợp cắt nhau:
o Đầu mút đoạn thẳng nằm đoạn thẳng
o điểm đoạn thẳng nằm khác phía với đường thẳng
function Intersect(A,B,C,D: Point): boolean; Begin
if (inside(C,A,B)) or (inside(D,A,B)) or (inside(A,C,D)) or (inside(B,C,D)) then exit(true);
Exit(CCW(M,A,B)* CCW(N,A,B)* CCW(A,M,N)* CCW(B,M,N)>=0); End;
c. Đánh giá:
- Việc giải toán cách khó cài đặt phương pháp vector Hơn phải giải vấn đề số thực việc cài đặt phải xác định điểm có thuộc đoạn thẳng
2.2 Một số thuật toán đa giác lồi + Kiểm tra đa giác lồi
* Định nghĩa
Một đa giác lồi gọi lồi ta vẽ đường thẳng qua cạnh đa giác điểm cịn lại ln nằm phía so với đường thẳng
a Cách 1: Dựa theo định nghĩa chi phí thực 0(N2)
b Theo phương pháp vector
xét theo chiều (cùng chiều ngược chiều kim đồng hồ), xét với điểm liên tiếp chiều đỉnh thứ ln nằm phía (trái phải) so với đỉnh trước
Function Convex (P:poly;n:index):boolean; Vari:index;
Begin
(51)51
For i:=3 to n
If CCW(p[i-1],p[i],p[i+1])<>Dir then exit(false); Convex:=true;
End;
c Đánh giá
- Chi phí thuật tốn theo cách 0(n2) chi phí theo phương
pháp vector 0(n)
- Việc cài đặt theo cách phức tạp
+ Bài tốn vị trí tương đối điểm so với đa giác lồi
Điểm thuộc đa giác điểm nằm cạnh thuộc miền đa giác a Cách 1:
- Vẽ trục song với trục tung với tọa độ x=max{các hoành độ}+1
- Vẽ đoạn thẳng song song với trục hoàng cắt trục vẽ bên Ta nhận xét số giao điểm với đa giác số lẽ điểm thuộc đa giác Cịn ngược lại điểm nằm ngồi đa giác
- Các trường hợp cắt sau ta tính cắt giao điểm:
b Phương pháp vector:
- Ta thấy xét cạnh đa giác theo chiều điểm M thuộc đa giác nằm bên (trái phải) với vector cạnh cuả đa giác
Function InsideP (P:polygon;n:index;M:point):boolean; Var i:index; VT, VT1:integer;
M N
M
M N
N P[i]
P[i+1] P[i+2]
P[i-1]
P[i] P[i+1] P[i+2]
P[i]
(52)52
Begin
If Inside(M,P[1], P[2]) then exit(true); Dir:=CCW(P[1],P[2],M);
For i:=2 to n Begin
If Inside(M,P[i], P[i+1]) then exit(true); If CCW(P[i],P[i+1],M)<>Dir then exit(false); end;
exit(true); End;
c Đánh giá
- Việc giải tốn cách khó cài đặt phương pháp vector Hơn phải giải vấn đề số thực việc cài đặt phải xác định điểm có thuộc đoạn thẳng
+ Bài tốn tìm bao lồi đa giác a. Thuật tốn bọc gói
* Điểm có tung độ nhỏ ln thuộc bao lồi - pomin(trung độ);
- W={po}; - Lặp lại
Từ po Quét ngược theo chiều kim đồng hồ gặp đỉnh v
W=W v;
pov - Until v=đỉnh
b. Grahamscan(phương pháp vector)
- Chọn điểm có tung độ nhỏ Nếu có tung độ chọn điểm có hồnh độ lớn Điểm thuộc bao lồi
(53)53
o Nếu điểm chọn không tạo thành đa giác lồi với điểm chọn bỏ đỉnh chọn lập thành đa giác lồi thơi
o Bỏ điểm vào bao lồi
Thuật toán Grahamscan
function Grahamscan:integer; var M,min,i:integer; t:point; begin
min:=1; // find min(p.y) and max(p.x) for i:=2 to N
if (p[i].y<p[min].y) or ((p[i].y=p[min].y) and (p[i].x>p[min].x)) then min:=i;
t:=p[1];p[1]:=p[min];p[min]:=t;// điểm bọc gói pa[1]:=0.0;
for i:=2 to N pa[i]:=theta1(p[1],p[i]); // tinh goc cac dinh so voi dinh quicksort;
p[0]:=p[n];
M:=3; \\ tam giác đa giác lồi for i:=4 to N
begin
while (ccw(p[M-1],p[M],p[i])<>-1) dec(M); inc(M);
t:=p[M];p[M]:=p[i];p[i]:=t;// bỏ điểm vào bao lồi
end;
grahamscan:=M; end;
Thuật toán Grahamscan
c Đánh giá
(54)54
so với tổng số Nhưng trường hợp xấu (tất điểm nằm bao) chi phí thuật tốn lên tới O(N2)
Chi phí cho thủ tục tỷ lệ thuận với N Đúng vậy, vịng lặp có vịng lặp, ta để ý không điểm bị loại lần nên vịng lặp hoạt động khơng đến N lần.Như chi phí cho thuật tốn O(NlogN) ta dùng phương pháp xếp tốt (như Quick Sort chẳng hạn)
III. KẾT LUẬN
Thơng qua thuật tốn CCW cho ta phương pháp giải số toán dựa vào đặc thù hướng điểm mặt phẳng Dựa phương pháp ta thấy hiệu số phương pháp khác đề cập bên Việc giải tốn hình học kinh nghiệm nên sử dụng cách thức vẽ hình nhận biết mối liên hệ để sử dụng phương pháp cho hiệu
Tài liệu tham khảo
(55)55
XÂY DỰNG THUẬT TOÁN FLOYD-WARSHALL CHO ĐỒ THỊ DÀY
Trường THPT Chun Vị Thanh – Hậu Giang
Thuật tốn tìm đường ngắn cặp đỉnh đồ thị G(V,E) (có hướng, có trọng số khơng có chu trình âm) Áp dụng quy hoạch động để giải toán thời gian O(n3) nhớ O(n2) Một vài cách phát triển thuật toán quy
hoạch động khác nhau, cách hi vọng mang đến cách nhìn khác tốn Cách phát biểu cuối đưa đến thuật tốn tiếng Floyd-Warshall Tài liệu tham khảo notes Jeff Erickson1 Đối với học sinh nên xem lại cách
phát triển thuật toán quy hoạch động cho toán đường ngắn từ đỉnh tới đỉnh từ thuật toán Bellman-Ford
Ghi chú: Đồ thị G biểu diễn ma trận kề W, i.e,
W[u,v]=w(u→v) tồn cung từ u tới v với trọng số w(u→v)
Vấn đề: Cho đồ thị có hướng, có trọng số khơng có chu trình âm G(V,E), tìm
khoảng cách ngắn cặp đỉnh đồ thị
Nếu trọng số đồ thị G khơng âm, ta áp dụng thuật toán Dijkstra V lần, lần áp dụng cho đỉnh đồ thị Do đó, ta có:
Định lý: Ta tìm khoảng cách ngắn cặp đỉnh đồ thị
khơng có trọng số âm thời gian O(V2logV) sử dụng O(V2) nhớ
Tuy nhiên đồ thị có trọng số âm, toán trở nên phức tạp nhiều Như ta biết, thuật tốn Bellman-Ford tìm đường ngắn từ đỉnh tới đỉnh khác đồ thị thời gian O(VE) nhớ O(V) Do đó, áp dụng thuật tốn Bellman-Ford cho đỉnh đồ thị, ta thu thuật toán với thời gian O(V2E) nhớ O(V2) Với đồ thị dầy E=O(V2), thời gian thuật toán
O(V4)
(56)
56
Trong giả mã đây, ta quy ước w(u→u)=0 w(u→v)=+∞ cung u→v không tồn đồ thị G
Thuật toán quy hoạch động phong cách Brute-force
Ta xây dựng bảng bảng quay hoạch động tương tự bảng quy hoạch động thuật toán Bellman-Ford
Gọi D[1,…,V][1,…,V][0,…,V−1] mảng chiều ô D[u,v,i] tương ứng với khoách cách ngắn từ u tới v qua tối đa i đỉnh, từ suy cơng thức truy hồi:
(1)
Ý nghĩa công thức truy hồi sau: đường ngắn từ u tới v phải qua hàng xóm x v mà (x→v)∈E
Nếu đường ngắn từ u tới v qua tối đa i đỉnh đường từ u tới x qua không q i−1 đỉnh Do D[u,v,i]=D[u,x,i−1]+w(x→v) Do ta khơng biết x, ta duyệt qua tất hàng xóm v lấy giá trị nhỏ để tìm D[u,v,i]
Khoảng cách ngắn từ u tới v D[u,v,V−2] đường ngắn từ u tới v qua tối đa V−2 đỉnh
(57)57
Phân tích thời gian: Bước khởi tạo thời gian O(V3) để khởi tạo
các phần tử bảng D thành +∞ Bước khởi tạo thứ hai (2 vòng lặp for) thời gian O(V2) Do ta tổng thời gian O(V3) để khởi tạo Tiếp theo ta phân tích vịng for cuối Vịng for duyệt qua tất cảc đỉnh x mà có cung x→v∈E; đó, vịng for có d+(v) vịng lặp d+(v) bậc tới v Tổng số bước lặp
của hai vòng for là: ∑v∈Vd+(v)=O(E) (2)
Do hai vòng lặp ngồi có (V−2)V bước lặp, thời gian thuật tốn O(V2E) Như vậy, thuật tốn DummyAllPairsSPs có thời gian chạy thời gian
thực V lần thuật toán Bellman-Ford
Cải tiến giải thuật
Gọi D[1,…,V][1,…,V][0,…,[log2(V−2)]] mảng chiều ô
D[u,v,i] tương ứng với khoách cách ngắn từ u tới v qua tối đa 2i đỉnh Từ có cơng thức truy hồi: D[u,v,i]=minx∈V(D[u,x,i−1]+D[x,v,i−1]) (3)
(58)58
Từ định nghĩa ta suy ra, D[u,v,[log2(V−2)]] đường ngắn từ u tới v
đi qua tối đa đỉnh Do đó, D[u,v,[log2(V−2)]] đường ngắn từ u tới v
Chú ý với định nghĩa bảng D trên, trường hợp D[u,v,1] Do đó, ta áp dụng vịng lặp thuật tốn DummyAllPairsSPs để tính giá trị
Mã giả:
Phân tích thuật tốn: Bước khởi tạo thời gian O(V2log
2(V)) để
khởi tạo bảng D bước thứ hai (2 vòng lặp for đầu tiên) thời gian O(V2) Phân
tích bước thứ ba (2 vòng lặp for tiếp theo), tương tự phân tích trên, thời gian O(VE) Bước cuối thực vòng lặp lồng nhau, vịng lặp có V3
bước lặp vịng lặp ngồi có log2(V−2) bước lặp Như vậy, tổng thời gian
thuật toán O(VE+V3log2(V)) = O(V3log2(V))
Thuật toán Floyd-Warshall
(59)59
Như vậy, D[u,v,0] có ý nghĩa đường ngắn từ u tới v mà không qua đỉnh trung gian ta khởi tạo D[u,v,0]=w(u→v) Với định nghĩa trên, ta có cơng thức truy hồi:
D[u,v,i]=min(D[u,v,i−1],D[u,i,i−1]+D[i,v,i−1]) (4)
Ý nghĩa công thức truy hồi có đường từ u tới v qua đỉnh tập hợp {1,2,…,i} đường qua đỉnh i khơng qua đỉnh i
- Nếu đường khơng qua đỉnh i đường ngắn đường ngắn từ u tới v qua đỉnh trung gian {1,2,…,i−1} Do D[u,v,i]=D[u,v,i−1]
- Nếu đường qua đỉnh i đường từ u tới i đường từ i tới v qua đỉnh thuộc tập {1,…,i−1} Do D[u,v,i]=D[u,v,i−1]+D[i,v,i−1]
- Ta lấy hai trường hợp để xác định giá trị D[u,v,i] Từ định nghĩa ta suy ra, D[u,v,V] đường ngắn từ u tới v
Mã giả:
(60)60 Giảm nhớ:
Thuật tốn Floyd-Warshall trình bày có nhớ O(V3) đầu
chúng ta mảng chiều D Để giảm nhớ có nhận xét: Các giá trị bảng chiều thứ i (là giá trị D[u,v,i] với cặp đỉnh u,v) phụ thuộc vào giá trị bảng hai chiều thứ i−1 (là giá trị D[u,v,i−1] với cặp đỉnh u,v) Do đó, thay lưu bảng chiều, ta lưu hai bảng hai chiều, bảng tương ứng với bảng hai chiều thứ i với i chẵn, với bảng hai chiều thứ i với i lẻ tái sử dụng bảng hai chiều qua vòng lặp Ý tưởng giống cách giảm nhớ thuật tốn tính số Fibonacci
Tuy nhiên, ta đơn giản hóa cách bỏ hẳn chiều thứ bảng chiều D Tính chất nhỏ khoảng cách ngắn đảm bảo thuật tốn ln ta bỏ hẳn chiều bảng quy hoạch động
Mã giả:
Truy vết đường đi
Để in đường ngắn hai đỉnh (u,v) bất kì, cần phải lưu vết trình quy hoạch động Mỗi có thay đổi bảng quy hoạch động (D[u,v]>D[u,i]+D[i][v]), ta phải ghi lại thay đổi Ta dùng mảng chiều T[1,…,V][1,…,V] T[u,v]=i đường ngắn từ u tới v qua đỉnh i
(61)61
Mã giả:
Khi có bảng T[1,…,V][1,…,V], ta in đường ngắn hai đỉnh u,v sau:
Ghi chú: Mặc dù sau nhiều năm nghiên cứu, chưa có thuật tốn tốt thời gian O(V3) cách đáng kể cho trường hợp Do đó, người ta giả thuyết khơng tồn thuật toán (gọi giả thuyết đường ngắn cặp đỉnh) Thuật toán (ngẫu nhiên) tốt phát triển
Ryan Williams có thời gian
(62)62
CẤU TRÚC HEAP
Thi Thị Thanh Tuyền THPT Chuyên Nguyễn Thiện Thành – Trà Vinh
I GIỚI THIỆU 1 Khái niệm
Heap cấu trúc liệu đặc biệt quan trọng, giúp ta giải nhiều tốn thời gian cho phép Độ phức tạp thông thường làm việc với heap O(logN) Heap cân thỏa mãn tính chất heap: B nút A khóa(A)≥khóa(B)
Hệ quả: khóa lớn ln nằm nút gốc, đống thường
gọi heap-max Nếu phép so sánh bị đảo ngược khiến cho khóa nhỏ ln nằm nút gốc đống gọi heap-min Khơng có hạn chế số lượng nút nút đống thơng thường nút có khơng q hai nút
Cấu trúc Heap cách thực kiểu liệu trừu tượng mang tên hàng đợi ưu tiên có vai trị quan trọng nhiều thuật toán cho đồ thị chẳng hạn thuật toán Dijkstra, Kruskal, Prim … hay thuật toán xếp Heapsort
2 Các thao tác thường gặp Heap
Mặc dù mô tả heap biểu diễn mảng Nút nút i 2*i 2*i+1 Do Heap cân nên độ cao nút <=lgN
Mơ hình biểu diễn heap nhị phân mảng
* Khai báo Heap:
Const
(63)63
Var
nHeap:LongInt;
Heap : array[0 maxn] of LongInt;
* Upheap: Nếu nút lớn nút cha di chuyển lên Procedure UpHeap(I : LongInt);
begin
if (i = 1) or (Heap[i] < Heap[i div 2]) then
exit; // Nếu i nút gốc nhỏ nút cha khơng làm việc
swap(Heap[i],Heap[i div 2]); // Đổi chỗ phần tử Heap; UpHeap(i div 2); // Tiếp tục di chuyển lên trên
end;
* Downheap: Nếu nút nhỏ nút đẩy xuống Procedure UpHeap(i : LongInt);
Begin
if (i = 1) or (Heap[i] < Heap[i div 2]) then
exit; // Nếu i nút gốc nhỏ nút cha khơng làm việc
swap(Heap[i] , Heap[i div 2]); // Đổi chỗ phần tử Heap; UpHeap(i div 2); // Tiếp tục di chuyển lên
end;
* Push: Đưa phần tử vào Heap cách thêm nút vào cuối Heap
tiến hành UpHeap từ
Procedure Push(x : LongInt); begin
Inc(nHeap); // Tăng số phần tử Heap Heap[nHeap] := x; // Thêm x vào Heap
UpHeap(nHeap); end;
* Pop:Lấy phần tử vị trí v Heap cách gán Heap[v] := Heap[nHeap] tiến hành chỉnh lại Heap
Function Pop(v : LongInt) : LongInt; begin
(64)64
Heap[v] := Heap[nHeap]; // Đưa phần tử cuối Heap vào vị trí v Dec(nHeap); // Giảm số phần tử Heap
{Chỉnh lại Heap}
UpHeap(v); DownHeap(v); end;
Ngồi ra, sử dụng thuật tốn Dijsktra/Prim kết hợp cấu trúc Heap, bạn cịn sử dụng cách Push Pop khác thuận lợi so với cách trình bày
Update – Dijsktra:
Procedure Update(v:longint);
//Đỉnh v vừa sữa nhãn, cần chỉnh lại Heap
Var Child, Parent:longint; Begin
Child:=pos[v];//Child vị trí đỉnh heap
If child=0 then // Nếu đỉnh v chưa có Heap
Begin
Inc(nheap);
Child:=nheap;//Đưa v vào cuối Heap End;
Parent:=child div 2;
While (parent>0) and (d[heap[parent]]>d[v])
//Nếu đỉnh nút cha ưu tiên v bị “kéo xuống” nút child
Begin
Heap[child]:=Heap[parent];//Đẩy đỉnh lưu nút cha xuống nút Pos[Heap[child]]:=child;//Ghi nhận lại vị trí đỉnh
Child:=parent;//Tiếp tục di chuyển lên
Parent:=child div 2; End;
Heap[child]:=v;
//Thao tác kéo xuống tạo ô trống nút child để đặt v vào
pos[v]:=child;//Ghi nhận vị trí đỉnh v Heap
(65)65
Pop – Dijsktra:
Function Pop:Longint; //Lấy từ Heap đỉnh có nhãn tự nhỏ
Var r,v,c:longint; Begin
Pop:=Heap[1];//Nút gốc nút có nhãn tự nhỏ
v:=Heap[nHeap];//v đỉnh nút cuối Heap, đảo lên đầu vun đống Dec(Heap);
r:=1;//Bắt đầu từ nút gốc
While r*2<=nheap //Chừng r chưa phải
Begin
c:=r*2; // c nút r
if (c<=nheap) and (d[heap[c]]>d[heap[c+1]]) then
inc(c); //Trong nút chọn nút có đỉnh ưu tiên hơn
if d[heap[c]]>=d[v] then
break; //nếu v ưu tiên khơng làm việc
heap[r]:=heap[c];//Chuyển đỉnh lưu nút lên nút cha
pos[heap[r]]:=r;//Cập nhật lại vị trí Heap đỉnh
r:=c;//Tiếp tục di chuyển xuống dưới
end;
heap[r]:=v;//Đỉnh v đặt vào vị trí r để đảm bảo cấu trúc heap pos[v]:=r;//Ghi nhận vị trí đỉnh v heap
end;
II ỨNG DỤNG CỦA HEAP
1 Ứng dụng Heap giải thuật Heapsort Tư tưởng thuật toán
Đổi chỗ (Swap): Sau mảng a[1 n] đống, lấy phần tử a[1] đỉnh đống khỏi đống đặt vào vị trí cuối n, chuyển phần tử thứ cuối a[n] lên đỉnh đống phần tử a[n] đứng vị trí
Vun lại: Phần cịn lại mảng a[1 n-1] khác cấu trúc đống phần tử a[1] Vun lại mảng thành đống với n-1 phần tử
(66)66
Thuật toán Heapsort có độ phức tạp O(nlogn)
Tính chất Heap
Một Heap hai kiểu sau:
Với liệu đẩy vào 35 33 42 10 14 19 27 44 26 31
Min-Heap: giá trị nút gốc nhỏ giá trị nút
Max-Heap: giá trị nút gốc lớn giá trị nút
Cài đặt HeapSort FreePascal:
Program CaidatthuattoanHeapSort; Uses Crt;
Const
fi='HEAPSORT.INP'; fo='HEAPSORT.OUT'; nmax=100;
(67)67
Manga=array[1 nmax] of integer;
Var
A:Manga; n,q,ri,x:integer; f,g:text;
Procedure Openf; Begin
Assign(f,fi); reset(f); Assign(g,fo); rewrite(g); end;
Procedure Closef; Begin
Close(f);Close(g); end;
Procedure Readinp; Var i:integer; Begin
Readln(f,n); For i:=1 to n read(f,a[i]); End;
//Heap Min Procedure Heap;
Var i,j:integer; cont:Boolean; Begin
i:=q; j:=2*i; x:=a[i]; cont:=true;
(68)68
if j<ri then //Tìm nút có khóa nhỏ
if a[j]>a[j+1] then inc(j);
If x<=a[j] then cont:=false else
begin
a[i]:=a[j];// Chép nút j vào nút i
i:=j; j:=2*i; end; a[i]:=x; end; End;
Procedure TaoHeapbandau; Begin
q:=(n div 2) +1; ri:=n;
While q>1 begin
dec(q); Heap End; End;
{Heap Max tương tự}
//Min
Procedure Taodaycothutugiam; Begin
ri:=n;
While ri>1 begin
(69)69
a[1]:=a[ri]; a[ri]:=x; dec(ri);
// Tạo a[1] a[r] heap
Heap; end; End;
Procedure Taodaycothututang; Begin
For ri:=1 to (n div 2) begin
x:=a[ri];
a[ri]:=a[n-ri+1]; a[n-ri+1]:=x; end;
End;
Procedure Output; Var i:integer; Begin
For i:=1 to n Write(g,a[i],' '); Writeln(g);
End; BEGIN Openf; ReadInp;
Writeln(g,'Tao Heap ban dau'); Taoheapbandau;
Output;
Writeln(g,'Tao day co thu tu giam'); Taodaycothutugiam;
(70)70
Writeln(g,'Tao day co thu tu tang'); taodaycothututang;
Output; Closef; END
2 Ứng dụng heap tổ chức hàng đợi ưu tiên
Sau ta xét số tập để thấy ứng dụng heap tổ chức hàng đợi ưu tiên
Bài 1: Cắt gỗ ( http://vn.spoj.com/problems/HEAP1/ )
Một người nông dân muốn cắt gỗ có độ dài L thành N miếng,mỗi miếng có độ dài số nguyên dương A[i] (A[1] + A[2] + … A[N] = L) Tuy nhiên, để cắt miếng gỗ có độ dài X thành phần ông ta X tiền Ông nông dân khơng giỏi tính tốn lắm, bạn u cầu lập trình giúp ơng ta cho biết cần để dành tiền cắt gỗ mong muốn
Input:
- Dòng đầu: số nguyên dương T số test
- T nhóm dịng mơ tả test, nhóm dịng gồm dịng: Dòng số nguyên dương N (1 ≤ N ≤ 20000);
Dòng 2: N số nguyên dương A[1],…, A[N](1 ≤ A[i] ≤ 50000)
Output: Kết test ghi dòng, ghi số nguyên dương là chi phí tối thiểu cần để cắt gỗ
Input Output
1 4
19
Đầu tiên cắt miếng gỗ thành phần có độ dài Sau cắt tiếp miếng có độ dài -> Cắt miếng thành phần có độ dài , Như chi phí 10 + + = 19
Thuật toán:
(71)71
sau thêm phần tử có giá trị tổng phần tử vừa lấy cho vào heap Như sau n-1 lần, heap phần tử giá trị phần tử kết cần tìm Chú ý: kết vượt longint nên phải để heap int64
Chương trình: Code\Catgo.pas)
PROGRAM Catgo; Uses Crt;
Const
fi='CATGO.Inp'; fo='CATGO.Out'; maxn=20001;
Var N,ntest,nheap,x:longint; value:int64;
Heap:array[1 maxn] of longint; f,g:text;
Procedure Openf; Begin
Assign(f,fi); reset(f); Assign(g,fo); rewrite(g); end;
Procedure Closef; Begin
Close(f);Close(g); end;
Procedure Update(u:longint); Var cha,con:longint;
Begin
nheap:=nheap+1; con:=nheap; cha:=con shr 1;
While (cha>0) and (u<Heap[cha]) begin
(72)72
con:=cha; cha:=con shr 1; end;
Heap[con]:=u; End;
Procedure Pop;
Var cha,con,u:longint; Begin
u:=Heap[nheap]; Dec(nheap); cha:=1;
While (cha<=nheap shr 1) begin
con:=cha shl 1;
If (con<nheap) and (Heap[con+1]<Heap[con]) then con:=con+1;
If (u<Heap[con]) then break;
Heap[cha]:=Heap[con]; cha:=con;
end;
Heap[cha]:=u; End;
Procedure Solve; Var i:longint; Begin
Readln(f,n); nheap:=0; For i:=1 to n Begin
(73)73
Value:=0;
While (nheap>1) Begin
x:=heap[1]; Pop; X:=x+Heap[1]; Pop;
Value:=Value+Int64(x);{Int64(x) la gia tri cua x} Update(x);
End;
writeln(g,Value); End;
BEGIN Openf; Solve; Closef; END
Bài 2: KMIN (http://vn.spoj.com/problems/KMIN/cstart=30 )
Cho dãy số nguyên A B Với số A[i]thuộc A B[j] thuộc B người ta tính tổng Tất tổng sau xếp không giảm tạo thành dãy C.Nhiệm vụ bạn là: Cho dãy A, B Tìm K số dãy C
Input:
- Dòng gồm số: M, N, K
- M dòng gồm M số mơ tả dãy A - N dịng gồm N số mô tả dãy B
Output: Gồm K dòng tương ứng K phần tử dãy C Giới hạn:
1 ≤ M, N, K ≤ 50000
1 ≤ Ai, Bi ≤ 109
Input Output
4
(74)74
3 4
5 5
Giới hạn
1 ≤ M, N, K ≤ 50000 1 ≤ Ai, Bi ≤ 109 Thuật tốn:
Với tạo mảng xếp lại được, cần thuật tốn tinh tế hơn, thuật toán đơn giản mà hiệu dùng heap
Đầu tiên xếp mảng A B không giảm, dĩ nhiên phần tử A[1] + B[1], vấn đề phần tử thứ A[2] + B[1] A[1] + B[2], ta xử lý sau:
Trước hết ta tạo heap gồm phần tử A[1]+B[1], A[1]+B[2], …, A[1]+B[n], lấy phần tử gốc (tức phần tử có giá trị nhỏ heap tại) giả sử phần tử A[i]+B[j] ta lại đẩy vào heap phần tử A[i+1]+B[j] (nếu i=m khơng đẩy thêm phần tử vào heap) Cứ ta lấy đủ K phần tử cần tìm
Độ phức tạp thuật tốn trường hợp lớn O(50000*log(50000)), chạy thời gian 1s, nhớ n
Chương trình: (Code\KMIN.pas)
Bài 3:Hàng đợi có độ ưu tiên (http://vn.spoj.com/problems/QBHEAP/ )
Cho trước danh sách rỗng Người ta xét hai thao tác danh sách đó: Thao tác “+V” (ở V số tự nhiên <= 1000000000): Nếu danh sách có 15000 phần tử thao tác bổ sung thêm phần tử V vào danh sách; Nếu khơng, thao tác khơng có hiệu lực
(75)75 Input
Gồm nhiều dòng, dòng ghi thao tác Thứ tự thao tác dòng liệt kê theo thứ tự thực
Output
Dòng 1: Ghi số lượng giá trị lại danh sách Các dịng tiếp theo: Liệt kê giá trị theo thứ tự giảm dần, dòng số
Example
Input Output
+1 +3 +2 +3 – +4 +4 – +2 +9 +7 +8 –
4
Chương trình: (Code\QBHeap.pas)
const fi='';
nmax=15001; type
data=longint; var
f:text;
(76)76
nHeap,n:data;
procedure swap(a,b:data); var
z:data; begin
z:=heap[a];
heap[a]:=heap[b]; heap[b]:=z; end;
procedure UpHeap(i:data); begin
if (i=1) or (heap[i]<=heap[i div 2]) then exit;
swap(i,i div 2); upheap(i div 2); end;
procedure push(x:data); begin
inc(nHeap); heap[nHeap]:=x; upheap(nHeap); end;
procedure downheap(i:data); var j:data;
begin j:=i*2;
if j>nHeap then exit;
if (j<nHeap) and (heap[j+1]>heap[j]) then inc(j);
(77)77
swap(i,j); downheap(j); end;
procedure pop; begin
heap[1]:=heap[nHeap]; dec(nHeap);
downheap(1); end;
procedure xuli;
var i,j,x,tmp,res:data; c:char;
begin
assign(f,fi); reset(f); nheap:=0;
while not seekeof(f) begin
read(f,c); if c='+' then begin
readln(f,x);
if nHeap<15000 then begin
push(x); end; end else begin readln(f);
if nHeap>0 then begin
(78)78
while (nheap>0) and (heap[1]=tmp) pop;
end; end; end; res:=0;
while nHeap>0 begin
tmp:=heap[1];
while (nHeap>0) and (heap[1]=tmp) pop;
inc(res); b[res]:=tmp; end;
writeln(res); for i:=1 to res writeln(b[i]); close(f);
end; begin xuli; end
Bài 4: Hội pháp sư (http://vn.spoj.com/problems/VOSNSEQ/ )
(79)79
Theo thống kê có tất N “bạch” hội đánh số từ tới N theo thứ tự bất kỳ hội có M pháp sư.N hội hội họp lại với để bàn chiến thuật cho cuộc chiến tới với “hắc” hội Vì chiến lâu dài nên chiến thuật được đưa đưa tốp N pháp sư khơng có pháp sư thuộc cùng hội đánh thay phiên Chi tiết chiến thuật tốp có sức mạnh yếu đưa trước Sức mạnh tốp pháp sư đánh giá tổng số sức mạnh pháp sư chọn Khi không cầm cự tốp quay để tốp khác lên đánh Một pháp sư chọn đợt liên tiếp Ngoài tốp pháp sư liên tiếp mạnh yếu
Yêu cầu: Tính sức mạnh tốp pháp sư lượt thứ K Input:
- Dòng đầu chứa số nguyên dương N, M, K
- N dòng tiếp theo, dòng thứ i chứa M số nguyên dương số sức mạnh của pháp sư từ yếu tới mạnh “bạch” hội thứ i Pháp sư u yếu pháp sư v nếu số sức mạnh pháp sư u nhỏ pháp sư v
Output:Chứa số tổng số sức mạnh tốp pháp sư lượt thứ K
Input Output
2 4
7
Ràng buộc:
K <= MN
Sức mạnh pháp sư <= 109 20% số test có N = M <= 20
20% số test có N = M <= 106
20% số test thỏa mãn: N = 3, M <= 105, tổng số sức mạnh hội <= 107
40% số test có N = 10, M <= 105 K <= 106 Thuật toán:
(80)80
Với : Chặt nhị phân kết đếm dãy với độ phức tạp O(N)
Với Chú ý hội tổng khơng vượt , nên số phần tử phân biệt hội không vượt Ta co dãy số lại thực thuật toán
Với : Ta đưa toán N dãy: thực thuật toán cho dãy đầu, K số, tiếp tục áp dụng dãy K số với dãy thứ 3, K số tiếp theo,… Làm ta K số bé nhất, đáp án
Bài 5: Điều động không lưu (http://vn.spoj.com/problems/MOVE12/ )
Sau thực thi quy hoạch Bộ Giao thông, sơ đồ giao thông thành phố H gồm n tuyến đường ngang n tuyến đường dọc cắt tạo thành lưới ô vuông với n x n nút giao thông Các nút giao thông gán tọa độ theo hàng từ đến n, từ trên xuống theo cột từ đến n, từ trái sang phải Ban đạo an toàn giao thông định điều n cảnh sát giao thông đến nút giao thông làm nhiệm vụ Ban đầu cảnh sát phân công đứng nút tuyến đường ngang khác nhau Đến cao điểm, xuất ùn tắc tuyến đường dọc khơng có cảnh sát giao thơng Để sớm giải tình trạng này, Ban đạo an tồn giao thông định điều động số cảnh sát giao thông số nút, từ nút sang nút khác hàng ngang để đảm bảo tuyến đường dọc có mặt cảnh sát giao thông
Yêu cầu: Biết cảnh sát hàng ngang thứ i cần ti đơn vị thời gian để di chuyển qua cạnh lưới ô vuông (i = 1, 2, ., n), giúp Ban đạo an toàn giao thơng tìm cách điều động cảnh sát thỏa mãn yêu cầu đặt cho việc điều động hoàn thành thời điểm sớm Giả thiết cảnh sát điều động đồng thời thực việc di chuyển đến vị trị thời điểm
Ràng buộc: 50% số tests ứng với 50% số điểm có n ≤ 100 Input:
- Dòng thứ chứa số nguyên dương n (n ≤ 10000)
- Dòng thứ i số n dòng chứa hai số nguyên dương ci, ti (ti ≤
(81)81
Hai số dịng ghi cách dấu cách Output: Ghi số nguyên thời điểm sớm tìm
Input Output
5
5 10
3 10
3 20
2
2 15
10
Thuật toán:
Chặt nhị phân kết quả, giả sử x Với cảnh sát ta xác định lo[i] hi[i] vị trí mà cảnh sát i di chuyển thời gian x Bài toán quy xếp cảnh sát vào vị trí ans[i] cho
Sắp xếp cảnh sát theo tăng dần theo lo, với cột j, ta tìm cảnh sát i thỏa mãn để xếp vào nhỏ i thỏa mãn điều kiện Để thực thao tác này, ta dùng heap để chứa hi[i]
i := 1;
for j := to n begin
while (i <= n) and (lo[i] <= j) begin
push(hi[i]);
inc(i);
end;
u := pop; ans[u] := j; end;
Chương trình: (Code\ MOVER12.pas)
Var
(82)82
n,nh:longint;
a,b,h,pos,c,t,v:array[0 10000]of longint; procedure nhap;
var i:longint; begin
read(fi,n); for i:=1 to n read(fi,c[i],t[i]); end;
procedure swap(var x,y:longint); var tg:longint;
begin
tg:=x; x:=y; y:=tg; end;
procedure upheap(i:longint); var j:longint;
begin
j:=i div 2;
if (i>1) and (b[h[i]]<b[h[j]]) then begin
swap(h[i],h[j]); upheap(j); end;
end;
procedure downheap(i:longint); var j:longint;
begin j:=i+i;
if (j>nh) then exit;
if (j<nh) and (b[h[j]]>b[h[j+1]]) then inc(j);
(83)83
begin
swap(h[i],h[j]); downheap(j); end;
end;
procedure push(x:longint); begin
inc(nh); h[nh]:=x; upheap(nh); end;
function pop:longint; begin
pop:=h[1]; h[1]:=h[nh]; dec(nh); downheap(1); end;
procedure sort(le,r:longint); var i,j,key:longint;
begin
i:=le;j:=r;
key:=a[le+random(r-le+1)]; repeat
while a[i]<key inc(i); while a[j]>key dec(j); if i<=j then
begin
(84)84
until i>j;
if i<r then sort(i,r); if le<j then sort(le,j); end;
function check(x:longint):boolean; var i,j,u:longint;
begin nh:=0;
for i:=1 to n begin
a[i]:=c[i]-(x div t[i]); b[i]:=c[i]+(x div t[i]); end;
sort(1,n); i:=0; j:=1;
for i:=1 to n begin
while (a[j]<=i) and (j<=n) begin
push(j); inc(j); end;
if nh>0 then u:=pop else u:=0; if b[u]<i then exit(false); end;
exit(true); end;
procedure xuli;
var le,r,mid,res:longint; begin
(85)85
r:=10000*10000; while le<=r begin
mid:=(le+r) div 2; if check(mid) then begin
res:=mid; r:=mid-1; end
else le:=mid+1; end;
writeln(fo,res); end;
begin
assign(fi,tfi); assign(fo,tfo); reset(fi); rewrite(fo); nhap; xuli; close(fo); end
3 Ứng dụng heap lý thuyết đồ thị
Ứng dụng heap lý thuyết đồ thị giúp cải tiến tốc độ thực thuật toán Chẳng hạn:
a) Trong thuật toán Dijkstra
(86)86
Tại bước lặp thuật tốn Dijkstra có hai thao tác: Tìm đỉnh cố định nhãn sửa nhãn
Thao tác tìm đỉnh cố định nhãn lấy đỉnh lưu gốc Heap, cố định nhãn, đưa phần tử cuối Heap chỗ thực việc vun đống
Thao tác sửa nhãn, duyệt danh sách kề đỉnh vừa cố định nhãn sửa nhãn đỉnh tự kề với đỉnh này, lần sửa nhãn đỉnh đó, ta xác định đỉnh nằm đâu Heap thực việc chuyển đỉnh lên phía gốc Heap cần bảo tồn cấu trúc Heap
Thuật tốn Dijkstra tổ chức Heap tìm đường ngắn từ đỉnh s đến đỉnh t - Cập nhật nút Heap (tương ứng với đỉnh xuất phát s, có giá trị khóa
bằng 0)
- Vịng lặp Heap rỗng (khơng cịn nút nào)
Begin
o Lấy đỉnh u nút gốc heap (phép loại bỏ gốc Heap)
o Nếu u=t (đỉnh t đỉnh đích đường đi) khỏi vòng lặp
o Đánh dấu u đỉnh cố định nhãn
o Duyệt danh sách cung để tìm cung có đỉnh đầu u, đỉnh cuối v Nếu v đỉnh tự d[v] > d[u] + khoảng cách (u,v)
Begin
Sửa nhãn cho v ghi nhận đỉnh trước v u
Trên Heap, cập nhật lại nút tương ứng với đỉnh v
end
End
Thuật tốn Dijkstra có độ phức tạp O(n2), việc kết hợp với cấu trúc liệu
Heap giúp thuật toán cải tiến có độ phức tạp O(max(n,m).logn)
Bài :Centre http://vn.spoj.com/problems/CENTRE28/)
(87)87
đường hai chiều, tuyến đường nối N thành phố cho khơng có thành phố nối tuyến đường Trong N thành phố thành phố thành phố N trung tâm kinh tế lớn nước hệ thống đường đảm bảo ln có cách từ thành phố đến thành phố N
Tuy nhiên,cả trung tâm có dấu hiệu 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ố 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í đườ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ố chọn làm trung tâm kinh tế thứ ba cho thành phố chọn thỏa mãn điều kiện
Input
- Dòng ghi số nguyên dương N M số thành phố số tuyến đường
- Dòng thứ i số M dòng ghi số nguyên dương xi, yi di với ý nghĩa tuyến đường thứ i có độ dài di nối thành phố xi, yi
Output
- Dòng ghi số tự nhiên S số lượng thành phố 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 số thứ tự thành phố chọn ( In theo thứ tự tăng dần )
Example
Input Output
6 2 3 1 100 100
(88)88
5 100
Thuật tốn: (sưu tầm)
Tìm đường ngắn đỉnh đến đỉnh đỉnh n đến đỉnh
sử dụng thuật toán Dijkstra Đồng thời ta tính số đường ngắn
Gọi d1[i] đường ngắn từ đỉnh đến i
Gọi d2[i] đường ngắn từ đỉnh n đến i
Gọi f1[i] số đường ngắn từ đỉnh đến i
Gọi f2[i] số đường ngắn từ đỉnh n đến i
Điều kiện để đỉnh i làm trung tâm kinh tế thứ là: (d1[i]+dn[i]<>d1[n])
hoặc (f1[i]*fn[i]<>f1[n])
Chương trình: (Code\Centre28.pas)
const fi = ''; fo = '';
maxn = 30000+3; maxc = trunc(1e9)+3; maxm = trunc(1e5);
type arrd = array[1 maxn] of longint; arrf = array[1 maxn] of int64;
arrk = array[-maxm maxm] of longint; arrh = array[1 maxn] of longint;
var d1,dn,d :arrd; f1,fn,f :arrf; n,i,j,m :longint; res :longint; ans :arrd;
head :array[1 maxn] of longint; link,ts,ke :arrk;
// dijkstra heap;
h,p :array[1 maxn] of longint; nh :longint;
(89)89
begin
link[i]:=head[u]; head[u]:=i; ke[i]:=v; ts[i]:=w; end;
procedure enter; var u,v,w:longint; begin
assign(input,fi);reset(input); readln(n,m);
for i:=1 to m begin
read(u,v,w); add(i,u,v,w); add(-i,v,u,w); end;
close(input); end;
procedure swap(var x,y:longint); var tg:longint;
begin
tg:=x;x:=y;y:=tg; end;
procedure upheap(i:longint); var j:longint;
begin
j:=i div 2; if i>1 then
if d[h[i]] < d[h[j]] then begin
(90)90
swap(p[h[i]],p[h[j]]); upheap(j);
end; end;
procedure downheap(i:longint); var j:longint;
begin
j:=i + i;
if j>nh then exit;
if (j<nh) and (d[h[j]] > d[h[j+1]]) then inc(j); if d[h[j]] < d[h[i]] then
begin
swap(h[i],h[j]); swap(p[h[i]],p[h[j]]); downheap(j);
end; end;
procedure push(i:longint); begin
inc(nh); h[nh]:=i; p[i]:=nh; upheap(nh); end;
function pop:longint; begin
(91)91
procedure update(i:longint); begin
if p[i]=0 then push(i) else upheap(p[i]); end;
procedure dijkstra(s:longint); var u,v,i:longint;
begin
fillchar(h,sizeof(f),0); fillchar(p,sizeof(p),0); nh:=0;
fillchar(f,sizeof(f),0); for i:=1 to n d[i]:=maxc; d[s]:=0; f[s]:=1; push(s); repeat u:=pop; i:=head[u]; while i<>0 begin
v:=ke[i];
if d[v]>d[u]+ts[i] then begin
d[v]:=d[u]+ts[i]; f[v]:=f[u]; update(v); end
else
if d[v]=d[u]+ts[i] then begin
(92)92
end;
i:=link[i]; end;
until nh=0; end;
procedure process; begin
dijkstra(1); f1:=f; d1:=d; dijkstra(n); fn:=f; dn:=d;
for i:=1 to n
if (d1[i]+dn[i]<>d1[n]) or (f1[i]*fn[i]<>f1[n]) then begin
inc(res); ans[res]:=i; end;
end;
procedure print; begin
assign(output,fo);rewrite(output); {for i:=1 to n writeln(d1[i]);} writeln(res);
for i:=1 to res writeln(ans[i]); close(output);
(93)93
end
b) Trong thuật tốn Kruskal tìm khung nhỏ
Một vấn đề quan trọng làm để xét cạnh từ cạnh có trọng số nhỏ tới cạnh có trọng số lớn Ta thực cách xếp danh sách cạnh theo thứ tự không giảm trọng số, sau duyệt từ đầu tới cuối danh sách cạnh, nên sử dụng thuật toán xếp hiệu để đạt tốc độ nhanh trường hợp số cạnh lớn Trong trường hợp tổng quát, thuật toán Heapsort hiệu cho phép chọn cạnh từ cạnh trọng số nhỏ tới cạnh trọng số lớn khỏi Heap xử lư (bỏ qua hay thêm vào cây) ln
Thuật tốn Kruskal có độ phức tạp O(mlogn), đồ thị có khung m ≥ n-1 chi phí thời gian chủ yếu nằm thao tác xếp danh sách cạnh độ phức tạp HeapSort O(mlogm), độ phức tạp tính tốn thuật tốn O(mlogm) trường hợp xấu
Bài 1: Net (Test\Net.rar)
Một công ty lập dự án mắc điện thoại cho N (N<5000) nhân viên lưới đoạn nối từ máy người đến máy người khác (không phải người nối với nhau) Dự án phải đảm bảo yêu cầu sau cho (gọi u cầu tính thơng suốt mạng): lưới điện thoại nhân viên Cơng ty nhắn tin cho nhân viên khác trực tiếp thông qua số nhân viên trung gian
Yêu cầu:Kiểm tra xem dự án có đáp ứng u cầu tính thơng sống khơng ?, có, tìm cách loại bỏ số đường nối cho mạng thông suốt đồng thời giảm đến mức tối thiểu chi phí nối mạng
Dữ liệu vào: Trong File NET.INP
- Dòng chứa hai số n (số nhân viên), m (số đường nối)
- Các dòng chứa đường nối dự án: dòng gồm số nguyên theo thứ tự số hai máy nối chi phí nối hai máy
Kết quả: Ghi File NET.OUT
- Dịng ghi chi phí tối thiểu nối mạng
(94)94
- K dịng mơ tả thơng tin k đoạn nối cần loại bỏ: dòng chứa hai số nguyên xác định điểm đầu điểm cuối đoạn nối
Ví dụ:
NET.INP NET.OUT
5 16 38 43 50 25 12 29
91 5
Thuật toán:
- Xây dựng khung nhỏ với n-1 cạnh
- Sắp xếp cạnh đồ thị theo thứ tự không giảm độ dài để lựa chọn cạnh bổ song (hay loại bỏ) Tuy nhiên để xây dựng khung nhỏ với n-1 cạnh, ta khơng cần phải tồn thứ tự cạnh mà cần xét phần dãy chứa r<m cạnh Để làm việc ta sử dụng thủ tục xếp dạng vun đống (Heap Sort), sử dụng (Quick Sort)
Việc lựa chọn cạnh (bổ sung hay loại bỏ) đòi hỏi phải có thủ tục hiệu kiểm tra tập cạnh T {e} có chứa chu trình hay không Để ý cạnh T bước lập trung gian tạo thành rừng Cạnh e cần khảo sát tạo thành chu trình
Chương trình (Code\Net.pas):
PROGRAM NetWork; Uses Crt;
Const
(95)95
Arrn=array[1 maxn] of Integer; Arrm=array[1 maxm] of Integer; Var
D1:Arrn;D2,W:Arrm; CuoiT,DauT,Father:Arrm; m,n,MinL:integer;
noi:Boolean; f:text;
Procedure Nhap; Var i:integer; Begin
Assign(f,fi); Reset(f); Readln(f,n,m);
For i:=1 to m
Read(f,D1[i],D2[i],W[i]); Close(f);
End;
Procedure Heap(First,last:integer); Var j,k,t1,t2,t3:integer;
Begin j:=first;
While (j<=trunc(last/2)) Begin
If (2*j<last) and (W[2*j+1]<W[2*j]) then k:=2*j+1
Else k:=2*j;
If W[k]<W[j] then begin
t1:=D1[j]; t2:=D2[j]; t3:=W[j];
(96)96
j:=k; end
Else j:=last; end;
end;
Function Find(i:integer):integer; Var tro:integer;
Begin Tro:=i;
While Father[tro]>0 tro:=Father[tro]; Find:=tro;
End;
Procedure Union(i,j:integer); Var x:integer;
Begin
x:=Father[i]+Father[j]; If Father[i]>father[j] then Begin
Father[i]:=j; Father[j]:=x; end
Else Begin
Father[j]:=i; Father[i]:=x; end;
End;
Procedure Xuli;
(97)97
For i:=1 to n Father[i]:=-1;
last:=m; Ncanh:=0; Ndinh:=0;noi:=true; For i:=trunc(m/2) downto Heap(i,m); MinL:=0; Noi:=true;
While (Ndinh<n-1) and (Ncanh<m) Begin
Ncanh:=Ncanh+1; u:=D1[1]; v:=D2[1]; r1:=Find(u);
r2:=Find(v); If r1<>r2 then Begin
Ndinh:=Ndinh+1; Union(r1,r2); DauT[Ndinh]:=u; CuoiT[Ndinh]:=v; MinL:=MinL+W[1];
end;
D1[1]:=D1[last]; D2[1]:=D2[last]; W[1]:=W[last]; last:=last-1;
Heap(1,last); end;
If Ndinh<>N-1 then noi:=false; End;
Procedure Xuat; Var i,canh:integer; Begin
Assign(f,fo); Rewrite(f); If noi then
begin
Writeln(f,MinL);canh:=0; For i:=1 to n-1
(98)98
Writeln(f,canh); For i:=1 to n-1
If (D1[i]<>DauT[i]) and (D2[i]<>CuoiT[i]) then Writeln(f,DauT[i],' ',CuoiT[i]);
end
Else Writeln(f,'Mang chua thong suot'); Close(f);
End; BEGIN Clrscr; Nhap; Xuli; Xuat; END
Bài 2: Xây dựng thành phố (http://vn.spoj.com/problems/NKCITY/ )
Nước Anpha lập kế hoạch xây dựng thành phố đại Theo kế hoạch, thành phố có N vị trí quan trọng, gọi N trọng điểm trọng điểm đánh số từ tới N Bộ giao thông lập danh sách M tuyến đường hai chiều xây dựng hai trọng điểm 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 thỏa mãn
Do nhận đầu tư lớn từ phủ, giao thơng th hẳn đội thi công riêng cho tuyến đường cần xây dựng Do đó, thời gian để hồn thành tồn tuyến đường cần xây dựng thời gian lâu hồn thành tuyến đường
u cầu: Giúp giao thơng tính thời gian hồn thành tuyến đường sớm thỏa mãn yêu cầu nêu
Dữ liệu
(99)99
- M tiếp theo, dòng chứa ba số nguyên u, v t cho biết xây dựng tuyến đường nối trọng điểm u trọng điểm v thời gian t Khơng có hai tuyến đường nối cặp trọng điểm
Kết
Một số nguyên thời gian sớm hoàn thành tuyến đường thỏa mãn yêu cầu nêu
Chương trình: Code\NKCiTy_Krus.pas
uses math; const fi =''; fo =''; maxn =1000; maxm =10000; type data =record u, v, c :longint; end;
var f :Text; n, m :longint;
Edges :array[1 maxm] of Data; b :Array[1 maxm] of longint; Father :Array[1 maxn] of longint; res :longint;
procedure nhap;
var i, u, v, c :longint; begin
assign(f, fi); reset(f);
readln(f, n, m); for i:=1 to m begin
(100)100
Edges[i].c:=c; end;
close(f); end;
Procedure init; var i :longint; begin
for i:=1 to m b[i]:=i; for i:=1 to n father[i]:=-1; res:=-1;
end;
procedure QS(l,r:longint); var i, j, x, tg :longint; begin
i:=l; j:=r;
x:=edges[b[ (l+r) div 2] ].c; repeat
while Edges[b[i]].c < x inc(i); while Edges[b[j]].c > x dec(j); if i<=j then
begin tg:=b[i]; b[i]:=b[j]; b[j]:=tg; inc(i); dec(j); end; until i>j;
(101)101
function find(i:longint):longint; var t :longint;
begin t:=i;
while father[t]>0 t:=father[t]; exit(t);
end;
procedure union(i, j:longint); var x :longint;
begin
x:=father[i]+father[j]; if father[i]>father[j] then begin
father[i]:=j; father[j]:=x; end
else begin father[j]:=i; father[i]:=x; end;
end;
procedure Kruskal;
var i, u,v, c, r1, r2 :longint; begin
init; QS(1,m);
for i:=1 to m begin
(102)102
r2:=find(v); if r1<>r2 then begin
union(r1,r2); res:=max(res,c); end;
end; end;
procedure xuat; begin
assign(f, fo); rewrite(f); writeln(f, res); close(f); end;
BEGIN nhap; Kruskal; xuat; END
c) Trong thuật tốn Prim tìm khung nhỏ
Xét độ phức tạp tính tốn, thuật tốn Prim có độ phức tạp O(n2) Tương tự
thuật toán Dijkstra, kết hợp thuật toán Prim với cấu trúc Heap thuật toán với độ phức tạp O((m+n)logn)
Bài 1: Xây dựng thành phố
Chương trình: Code\NKCiTy_Pri.pas
const fi=''; fo='';
maxn=1000; maxc=trunc(1e9);
(103)103
u,v,t:longint;
cx:array[1 maxn] of boolean;
c:array[1 maxn,1 maxn] of longint; procedure init;
begin
for i:=1 to n d[i]:=maxc; d[1]:=0;
fillchar(cx,sizeof(cx),true); end;
procedure prim; var min,u:longint; begin
repeat
min:=maxc;u:=0; for i:=1 to n if cx[i] then if d[i]<min then begin
min:=d[i]; u:=i; end;
if (u=0) then exit; cx[u]:=false;
if d[u]>res then res:=d[u]; for v:=1 to n
if cx[v] then
if d[v]>c[u,v] then begin
d[v]:=c[u,v]; end;
(104)104
begin
assign(input,fi);reset(input); assign(output,fo);rewrite(output); readln(n,m);
for i:=1 to n for j:=1 to n if i<>j then c[i,j]:=maxc else c[i,j]:=0; for i:=1 to m begin
readln(u,v,t); c[u,v]:=t; c[v,u]:=t; end;
init; prim;
writeln(res);
close(input);close(output); end
Bài 2:Tưới nước đồng cỏ (http://vn.spoj.com/problems/FWATER/ )
Nông dân John định mang nước tới cho N (1 <= N <= 300) đồng cỏ mình, để thuận tiện ta đánh số đồng cỏ từ đến N Để tưới nước cho đồng cỏ John chọn cách, đào đồng cỏ giếng lắp ống nối dẫn nước từ đồng cỏ trước có nước tới
Để đào giếng đồng cỏ i cần số tiền W_i (1 <= W_i <= 100,000) Lắp ống dẫn nước nối đồng cỏ i j cần số tiền P_ij (1 <= P_ij <= 100,000; P_ij = P_ji; P_ii=0)
Tính xem nơng dân John tiền để tất đồng cỏ có nước
DỮ LIỆU
(105)105
Các dòng N + 1: Dòng i+1 chứa số nguyên nhất: W_i
Các dòng N+2 2N+1: Dòng N+1+i chứa N số nguyên cách dấu cách; số thứ j P_ij
KẾT QUẢ
Một số nguyên chi phí tối thiểu để cung cấp nước cho tất đồng cỏ
VÍ DỤ
FWATER.INP FWATER.OUT
4 4 2 2 3 4
9
GIẢI THÍCH:
Có đồng cỏ Mất tiền để đào giếng đồng cỏ 1, tiền để đào đồng cỏ 2, tiền để đào đồng cỏ Các ống dẫn nước tốn 2, 3, tiền tùy thuộc vào nối đồng cỏ với Nơng dân John đào giếng đồng cỏ thứ lắp ống dẫn nối đồng cỏ với tất đồng cỏ cịn lại, chi phí tổng cộng + + + =
Chương Chương trình: Code\FWATER.pas
(106)106
a : array[1 maxN,1 maxN] of longint; d,tr,w : array[1 maxN] of longint;
free : array[1 maxN] of boolean; n : longint;
procedure readf; var f: text; i,j: longint; begin
assign(f,fi); reset(f); readln(f,n);
for i:=1 to n for j:=1 to n
if i=j then a[i,j] := else a[i,j] := maxC; for i:=1 to n readln(f,w[i]); for i:=1 to n begin
for j:=1 to n read(f,a[i,j]); readln(f);
end; close(f); end;
procedure init; var i: longint; begin
inc(n);
for i:=1 to n begin a[n,i] := w[i]; a[i,n] := w[i]; end;
(107)107
for i:=2 to n d[i] := maxC; fillchar(free,sizeof(free),true); end;
procedure Prim;
var k,i,u,v,min: longint; connected: boolean; begin
fillchar(tr,sizeof(tr),0); connected := true; for k:=1 to n begin u := 0;
:= maxC; for i:=1 to n
if free[i] and (d[i] < min) then begin := d[i];
u := i; end;
if u=0 then begin connected := false; break;
end;
free[u] := false; for v:=1 to n
if free[v] and (d[v] > a[u,v]) then begin d[v] := a[u,v];
tr[v] := u; end;
end; end;
(108)108
var f: text; t,i: longint; begin
assign(f,fo); rewrite(f); t := 0;
for i:=2 to n t := t + a[tr[i],i]; writeln(f,t);
close(f); end;
BEGIN readf; init; Prim; writef; END
III KẾT LUẬN
Có thể thấy cấu trúc liệu Heap cấu trúc liệu quan trọng có tính ứng dụng cao, kết hợp thuật toán với cấu trúc Heap giúp cải tiến cách đáng kể thời gian thực thuật tốn Ngồi ra, có nhiều cấu trúc liệu heap cịn có nhiều dạng biến thể B-heap, Binary heap, Fibonacci heap…
Qua trình nghiên cứu tài liệu từ nhiều nguồn khác nhau, viết chun đề này, mong hữu ích cho đồng nghiệp, kinh nghiệm hạn hẹp mong có thêm nhiều góp ý trao đổi
IV TÀI LIỆU THAM KHẢO
[1] Các chuyên đề nhiều tác giả viết trước [2] Tài liệu sách giáo khoa chuyên – Tập 1, 2,
[3] Giáo trình Giải thuật lập trình – Tác giả Lê Minh Hoàng
[4] Sáng tạo thuật tốn lập trình tập 1, 2, – Nguyễn Xuân Huy [5] Trang kienthuc24h.com
(109)109
(110)110
CẤU TRÚC DỮ LIỆU HEAP
Trường THPT Chuyên Long An
I. Khái niệm
Heap cấu trúc liệu đặc biệt quan trọng, giúp ta giải nhiều tốn thời gian cho phép Độ phức tạp thông thường làm việc với Heap O(logN)
Heap thực chất cân thỏa mãn điều kiện sau: Một nút có khơng q nút
Với Heap Max nút gốc nút lớn nhất, nút không lớn nút cha Với Heap Min ngược lại
Mặc dù mơ tả Heap biểu diễn mảng Nút nút i 2*i 2*i+1 Do Heap cân nên độ cao nút <= logN
Ứng dụng chủ yếu Heap tìm Min, Max tập hợp động (có thể thay đổi, thêm, bớt phần tử) q đủ
(Mơ hình biểu diễn Heap nhị phân mảng)
II Các thao tác thường dùng Heap Max
1. Khai báo
(111)111
Const
maxn = 100000; Var
nHeap : LongInt;
Heap : array[0 maxn] of LongInt;
2. UpHeap
Nếu nút lớn nút cha di chuyển lên trên:
Procedure UpHeap(i : LongInt); Begin
if (i = 1) or (Heap[i] < Heap[i div 2]) then exit; // Nếu i nút gốc nhỏ nút cha khơng làm việc
swap(Heap[i] , Heap[i div 2]); // Đổi chỗ phần tử Heap; UpHeap(i div 2); // Tiếp tục di chuyển lên
end;
3. DownHeap
(112)112
Procedure DownHeap(i : LongInt); Var
j : LongInt; Begin
j := i*2;
if j > nHeap then exit; // Nếu i khơng có nút không làm việc
if (j < nHeap) and (Heap[j] < Heap[j+1]) then Inc(j); // Nếu i có nút chọn nút ưu tiên hơn
if Heap[i] < Heap[j] then // Nếu nút cha nhỏ nút con
begin
swap(Heap[i] , Heap[j]); // Đổi chỗ phần tử Heap DownHeap(j); // Tiếp tục di chuyển xuống dưới
end; end;
4. Push
(113)113
Procedure Push(x : LongInt); Begin
Inc(nHeap); // Tăng số phần tử Heap Heap[nHeap] := x; // Thêm x vào Heap
UpHeap(nHeap); End;
5. Pop
Rút phần tử vị trí v Heap: Gán Heap[v] := Heap[nHeap] tiến hành chỉnh lại Heap:
Function Pop(v : LongInt) : LongInt; Begin
Pop := Heap[v]; // Lấy phần tử vị trí v khỏi Heap
Heap[v] := Heap[nHeap]; // Đưa phần tử cuối Heap vào vị trí v Dec(nHeap); // Giảm số phần tử Heap 1
{Chỉnh lại Heap}
UpHeap(v); DownHeap(v); End;
Ngoài ra, sử dụng thuật tốn Dijkstra/Prim kết hợp cấu trúc Heap, bạn cịn sử dụng cách Push Pop khác thuận lợi so với cách trình bày trên:
6. Dijkstra Heap
1/ Update – Dijkstra:
Procedure Update(v : LongInt); // Đỉnh v vừa sửa nhãn, cần chỉnh lại Heap var
child , parent : LongInt; begin
child := pos[v]; // child vị trí đỉnh v Heap
if child = then // Nếu đỉnh v chưa có Heap
begin
(114)114
end;
parent := child div 2; // parent nút cha child
while (parent > 0) and (d[Heap[parent]] > d[v]) // Nếu đỉnh nút parent ưu tiên v bị “kéo xuống” nút child
begin
Heap[child] := Heap[parent]; // Đẩy đỉnh lưu nút cha xuống nút
con
pos[Heap[child]] := child; // Ghi nhận lại vị trí đỉnh đó
child := parent; // Tiếp tục di chuyển lên
parent := child div 2; end;
Heap[child] := v; // Thao tác “kéo xuống” tạo ô trống nút child
để đặt v vào
pos[v] := child; // Ghi nhận vị trí đỉnh v Heap
end;
2/ Pop – Dijkstra:
Function Pop : LongInt; // Lấy từ Heap đỉnh có nhãn tự nhỏ nhất
var
r , v , c : LongInt; begin
Pop := Heap[1]; // Nút gốc nút có nhãn tự nhỏ nhất
v := Heap[nHeap]; // v đỉnh nút cuối Heap, đảo lên đầu vun
đống
Dec(nHeap); // Giảm số phần tử Heap
r := 1; // Bắt đầu từ nút gốc
while r*2 <= nHeap // Chừng r chưa phải lá
begin
c := r*2; // c nút r
if (c <nHeap) and (d[Heap[c]] > d[Heap[c+1]]) then Inc(c); // Trong nút chọn nút chứa đỉnh ưu tiên hơn
if d[Heap[c]] >= d[v] then break; // Nếu v ưu tiên khơng làm việc nữa
(115)115
pos[Heap[r]] := r; // Cập nhật lại vị trí Heap đỉnh đó
r := c; // Tiếp tục di chuyển xuống dưới
end;
Heap[r] := v; // Đỉnh v đặt vào vị trí r để đảm bảo cấu trúc Heap pos[v] := r; // Ghi nhận vị trí đỉnh v Heap
end;
III. Một số tập ứng dụng
1. Bài tập 1: Heapuri
Cho danh sách gồm N phần tử Chúng ta thực số thao tác danh sách Có loại thao tác sau :
- Thao tác : Cho phần tử x vào danh sách
- Thao tác : Xóa phần tử thứ t (theo thứ tự nhập vào) - Thao tác 3: Lấy phần tử nhỏ danh sách Yêu cầu: Hãy cho biết kết thao tác ?
INPUT: HEAPURI.INP
- Dòng 1: N
- Các dòng dãy thao tác cần xử lý theo định dạng x, x tương ứng thao tác 1,
OUTPUT : HEAPURI.OUT
- Ghi nhiều dòng, dòng kết thao tác theo thứ tự file input
Ví dụ :
HEAPURI.INP HEAPURI.OUT
9 2
(116)116
2
Dễ thấy danh sách động (số lượng phần tử thay đổi), ta xây dựng heapmin để lấy giá trị nhỏ thao tác Để xóa phần tử thứ t theo thứ tự nhập vào ta lưu thêm mảng Pos
#include <stdio.h> #include <assert.h>
#define maxn 200010
int N, L, NR;
int A[maxn], Heap[maxn], Pos[maxn];
void push(int x) {
int aux;
while (x/2 && A[Heap[x]]<A[Heap[x/2]]) {
aux = Heap[x];
Heap[x] = Heap[x/2]; Heap[x/2] = aux;
Pos[Heap[x]] = x; Pos[Heap[x/2]] = x/2; x /= 2;
} }
(117)117
int aux, y = 0;
while (x != y) {
y = x;
if (y*2<=L && A[Heap[x]]>A[Heap[y*2]]) x = y*2;
if (y*2+1<=L && A[Heap[x]]>A[Heap[y*2+1]]) x = y*2+1;
aux = Heap[x]; Heap[x] = Heap[y]; Heap[y] = aux;
Pos[Heap[x]] = x; Pos[Heap[y]] = y; }
}
intmain() {
freopen("heapuri.in", "r", stdin); freopen("heapuri.out", "w", stdout);
int i, x, cod;
scanf("%d ", &N);
assert(1<=N && N<=200000);
for (i=1; i<=N; i++) {
(118)118
assert(1<=cod && cod<=3); if (cod < 3)
{
scanf("%d ", &x);
assert(1<=x && x<=1000000000); }
if (cod == 1) {
L++, NR++; A[NR] = x;
Heap[L] = NR; Pos[NR] = L;
push(L); }
if (cod == 2) {
A[x] = -1; assert(Pos[x] != 0);
assert(1<=x && x<=NR); push(Pos[x]);
Pos[Heap[1]] = 0; Heap[1] = Heap[L ]; Pos[Heap[1]] = 1; if (L>1) pop(1); }
(119)119
return 0; }
2. Bài tập 2: Thả xốp
Có N hạt xốp, hạt thứ i có khối lượng , thả xuống ống nước đặc biệt thiết kế cho thời điểm có hạt xốp nhẹ lên bề mặt Trước lần thả, hạt xốp bề mặt bị ngấm nước tăng gấp đôi khối lượng Hỏi sau thả hạt xốp cuối vào ống khối lượng xốp tăng so với tổng khối lượng ban đầu ?
INPUT:SPONGE.INP
- Dòng 1: Số nguyên dương N - Dòng 2: N số nguyên dương
OUTPUT :SPONGE.OUT
- Ghi số đáp án tốn
Ví dụ:
SPONGE.INP SPONGE.OUT
3
3
Tương tự 1, dễ thấy danh sách động (giá trị phần tử thay đổi), ta xây dựng heapmin để lấy hạt xốp nhỏ nhất, tăng gấp đôi khối lượng cập nhật lại heapmin
COnst
tfi='sponge.in'; tfo='sponge.out'; Type
arr1=array[0 100001] of longint; Var
fi,fo : text;
(120)120
{==================================} Procedure nhap;
Var
i :longint; Begin
Assign(fi,tfi);Reset(fi); read(fi,n);
For i := to n read(fi,w[i]);
cost := w; Close(fi); End;
{==================================} Procedure doicho(varx,y : longint);
var
tmp :longint; Begin
tmp := x; x := y; y := tmp; End;
{==================================} Procedure upheap(i : longint);
Var
j :longint; Begin
While (i <> 1) and (heap[i] <heap[i div 2]) Begin
doicho(heap[i],heap[i div 2]); i := i div 2;
(121)121
{=============================} Procedure downheap(i : longint);
Var
j :longint; Begin
While (2*i <= nheap) Begin
j := 2*i;
If (heap[j] >heap[j+1]) and (j <nheap) then inc(j); If heap[i] > heap[j] then
Begin
doicho(heap[i],heap[j]); i :=j;
End else exit; End; End;
{=============================} Procedure push(x :longint);
Begin inc(nheap); heap[nheap] := x; upheap(nheap); End;
{=============================} Procedure xuly;
Var
i1 :longint; Begin
For i1 := to n-1 Begin
(122)122
delta := delta + heap[1]; heap[1] := heap[1]*2; downheap(1);
End; End;
{==================================} BEGIN
Assign(fo,tfo);Rewrite(fo); nhap;
xuly;
write(fo,delta); Close(fo); END
3. Bài tập 3: PILOT
HT AIRLINE hãng hàng không danh tiếng Việt Nam, nhiên, để tồn bão suy thoái kinh tế, Ban giám đốc định giảm chi phi tiền lương cho phi cơng nhiều tốt
HT airline có tất N phi công (N số chẵn), phi công đánh số từ đến N (Phi công phi công trẻ nhất, phi công i phi cơng có tuổi cao thứ i,… phi cơng n phi công cao tuổi nhất) HT airline cần xác
2 N
phi hành đồn, phi hành đồn gồm phi cơng (một lái lái phụ), lái phải nhiều tuổi lái phụ Hợp đồng mà cơng ty kí với phi cơng có điều khoản rõ ràng: tiền lương lái tiền lương lái phụ Rõ ràng, phi công, tiền lương lái cao tiền lương lái phụ Tuy nhiên, với phi hành đồn, tiền lương lái lại thấp lái phụ
Để giảm chi phí trả tiền lương, HT phải xác định cách phân chia tối ưu
2 N
phi hành đoàn
Bạn giúp HT viết chương trình xác định số tiền tối thiểu để trả lương cho N phi công
(123)123
- Dòng : Số nguyên dương N, số phi công HT airline.(2 N 10000; N số chẵn)
- N dịng tiếp theo, dịng thứ i thơng tin phi công i : gồm hai số a c viết cách dấu cách trống, tương ứng tiền lương lái tiền lương lái phụ (1 a < c 100.000)
OUTPUT: PILOT.OUT
- Một số nguyên tiền lương tối thiểu phải trả cho N phi cơng
Ví dụ :
PILOT.INP PILOT.OUT PILOT.INP PILOT.OUT
6
10000 7000 9000 3000 6000 4000 5000 1000 9000 3000 8000 6000
32000
5000 3000 4000 1000 9000 7000 11000 5000 7000 3000 8000 6000
33000
Time limits / test: 1s
Ta có nhận xét sau: Theo thứ tự i nhập vào từ đến N, i lẻ phải có lái phụ Vì vậy, ta xây dựng heap max, cho vào heap, i lẻ ta lấy heap nút, lái phụ
CONST
tfi = 'pilot.inp'; tfo = 'pilot.out'; max = 100000; TYPE
Arr1 = array[1 max] of longint; VAR
fi,fo : text;
(124)124
{* -*} procedurenhap;
var
i : longint; begin
assign(fi,tfi);reset(fi); read(fi,n);
For i := to n read(fi,a[i],b[i]);
close(fi); end;
{* -*} proceduredoicho(varx,y : longint);
var
tg : longint; begin
tg := x; x := y; y := tg; end;
{* -*} procedureupheap(i : longint);
begin
if (i = 1) or (h[i] < h[i div 2]) then exit; if h[i div 2] < h[i] then
begin
doicho(h[i div 2],h[i]); upheap(i div 2);
end; end;
(125)125
var
j : longint; begin
j := *i;
if j >nh then exit;
if (j <nh) and (h[j] < h[j+1]) then inc(j); if h[i] < h[j] then
begin
doicho(h[i],h[j]); downheap(j); end;
end;
{* -*} procedure push(x : longint);
begin inc(nh); h[nh] := x; upheap(nh); end;
{* -*} function pop : longint;
begin pop := h[1]; h[1] := h[nh]; dec(nh); downheap(1); end;
{* -*} procedurexuli;
var
(126)126
sum := 0;nh := 0;kq := 0;
For i := to n begin
sum := sum + a[i]; push(a[i] - b[i]);
if i mod = then kq := kq + pop; end;
end;
{* -*} procedureinkq;
begin
assign(fo,tfo);rewrite(fo); write(fo,sum-kq);
close(fo); end;
{* -*} {* -*} {* -*} BEGIN
nhap; xuli; inkq; END
4. Bài tập 4: Tiểu thuyết trinh thám
(127)127
Thực ra, bí mật thành cơng Ivan chổ khơng phải anh nghĩ tình mà người em trai Alexei Ivan có nhiệm vụ viết thành bestsellers Dĩ nhiên hai anh em chia hợp lý số tiền kiếm Điều đáng tiếc khả sáng tạo tình ly kỳ Alexei lại phụ thuộc vào chu kỳ sinh học anh Hai anh em phân tích bảng chu kỳ sinh học Alexei thấy thời gian tới Alexei nghĩ n chủ đề mới, chủ đề thứ i nghĩ ngày ri Trong ngày Alexei nghĩ tới vài câu chuyện
Với chủ đề, Ivan thời lượng cần thiết để hồn thành tác phẩm tính chủ đề thứ i cần có pi ngày để viết Ivan có trí nhớ cực tốt, anh tạm bỏ dở truyện, chuyển sang viết truyện khác sau quay lại hồn thành nốt truyện dở dang
Dĩ nhiên, hai anh em muốn sách viết nhanh tốt, tức phải cực tiểu hóa thời gian trung bình từ thời điểm tới thời điểm lúc tiểu thuyết hồn thành Vì số sách cố định, nên điều tương đương với việc cực tiểu hóa tổng thời gian viết tất sách Điều có nghĩa sách thứ i hồn thành vào ngày ci ci phải cực tiểu hóa Ví dụ, ngày thứ Alexei nghĩ cốt chuyện mà Ivan cần viết ngày, Ivan bắt tay viết Ngày thứ Alexei nghĩ thêm cót chuyện cần viết ngày Ivan chuyển sang viết chuyện mới, ngày thứ 3: chuện thứ hai hoàn thành Ivan quay lại viết tiếp chuyện thứ nhất, thêm ngày nữa, đến ngày thứ chuyện thứ hoàn thành Tổng thời điểm hoàn thành 3+7 = 10
Yêucầu: Cho n, ri pi Hãy xác định tổng thời gian ngắn hoàn thành tất
truyện
INPUT:PULP.INP:
- Dòng 1: Chứa số nguyên n (1 ≤ n ≤ 100.000)
- Mỗi dòng n dòng sau chứa số nguyên ri pi
OUTPUT: PULP.OUT
- Một số nguyên – tổng thời gian ngắn tìm
Ví dụ:
PULP.INP PULP.OUT
(128)128
1
Thuật toán:
1 Sort tăng theo R[i]
2 Ví dụ với thời điểm , dễ dàng ta thấy
3 Với khoảng thời gian t = R[i] – R[i-1], ta ưu tiên giải công việc cần thời gian nhất, giả sử cơng việc P (Thấy sử dụng Heap để lấy cơng việc có thời gian nhỏ nhất)
a Nếu thời gian thực cơng việc P < t thực hết cơng việc P, thời gian cịn lại thực tiếp cơng việc có thời gian nhỏ b Nếu thời gian thực công việc P > t thực cơng việc
P khoảng thời gian t, thời gian lại t[P] – t ta lưu lại Heap để tính tiếp
4 Sau xét đến thời điểm N, lúc ta phải làm tất cơng việc cịn lại từ nhỏ đến lớn, lấy tất phần tử Heap xong
CONST
tfi = 'pulp.inp'; tfo = 'pulp.out'; max = 100000; TYPE
Arr1 = array[1 max] of longint; VAR
fi,fo : text;
n,nh,sum,res : longint; r,p,h : Arr1;
{* -*} procedurenhap;
var
i : longint; begin
(129)129
read(fi,n);
For i := to n read(fi,r[i],p[i]);
close(fi); end;
{* -*} proceduredoicho(varx,y : longint);
var
tg : longint; begin
tg := x; x := y; y := tg; end;
{* -*} procedureupheap(i : longint);
begin
if (i = 1) or (h[i] > h[i div 2]) then exit; if h[i div 2] > h[i] then
begin
doicho(h[i div 2],h[i]); upheap(i div 2);
end; end;
{* -*} proceduredownheap(i : longint);
var
j : longint; begin
j := * i;
if j >nh then exit;
(130)130
if h[i] > h[j] then begin
doicho(h[i],h[j]); downheap(j); end;
end;
{* -*} procedure push(x : longint);
begin inc(nh); h[nh] := x; upheap(nh); end;
{* -*} function pop : longint;
begin pop := h[1]; h[1] := h[nh]; dec(nh); downheap(1); end;
{* -*} procedurexuli;
var
i,t,x : longint; begin
nh := 0; push(p[1]); sum := r[1]; res := 0;
(131)131
t := r[i] - r[i-1];
while (nh> 0) and (t > 0) begin
x := pop; if (x <= t) then begin
t := t - x;
sum := sum + x; res := res + sum; end
else begin push(x-t); t := 0; end; end;
push(p[i]);sum := r[i]; end;
whilenh> begin
x := pop;
sum := sum + x; res := res + sum;
end; end;
{* -*} procedure sort(x,y:longint);
var
i,j,key1,key2 : longint; begin
(132)132
j := y;
key1 := r[x+random(y-x+1)]; key2 := p[x+random(y-x+1)]; repeat
while (r[i] < key1) or ((r[i] = key1) and (p[i] < key2)) inc(i); while (r[j] > key1) or ((r[j] = key1) and (p[j] > key2)) dodec(j); if i <= j then
begin
doicho(r[i],r[j]); doicho(p[i],p[j]); inc(i);
dec(j); end; until i > j ;
if x < j then sort(x,j); if y > i then sort(i,y); end;
{* -*} {* -*} procedureinkq;
begin
assign(fo,tfo);rewrite(fo); write(fo,res);
close(fo); end;
(133)133
randomize; nhap; sort(1,n); xuli; inkq; END
5. Bài tập 5: Cezar
Tại HT Land, có tất n thượng nghị sĩ n biệt thự (đánh số từ đến n), ngơi nhà có đường nhất: đường trực tiếp không trực tiếp (đi qua đường khác) Tất nhà đến
Mỗi thượng nghị sĩ từ nhà đến thượng viện, phải trả USD qua phố (phố = đường nối trực tiếp nhà bất kỳ) HT – người đứng đầu thượng viện - nghĩ cách cho số tiền mà thượng nghĩ sĩ phải trả tối thiểu Vì vậy, HT định
Có k phố miễn phí (thượng nghị sĩ trả tiền phố này)
Đặt tòa nhà thượng viện n nhà
Bạn viết chương trình tính xem chi phí tối thiểu bao nhiêu?
INPUT: CEZAR.INP
- Dòng 1: Số nguyên n k tương ứng số nhà HT land số đường phố miễn phí
- N – dòng tiếp theo, dòng chứa số i j có nghĩa có phố nối nhà i nhà j
OUTPUT: CEZAR.OUT
(134)134 Giới hạn:
Ví dụ
CEZAR.INP CEZAR.OUT Giải thích
13 2 8 5 10 10 11 10 12 10 13 11
Có nhiều phương án giải quyết: Đây phương án:
– 5-7, 7-8, 8-10
là đường miễn phí
– 8 thượng viện
Chi phí tối thiểu là:
11
Time limit:0.5 s/test Thuật tốn:
1 Chúng ta tìm đường phải trả phí lại
2 Khởi tạo D[i] = trọng số đỉnh i với ý nghĩa = số lượng người đến thượng viện phải qua đường Ban đầu
3 Tại bước cho nút vào Heap, lần lấy phần tử heap update lại trọng số đỉnh kề với đỉnh vừa lấy đỉnh vừa update thành nút ta lại cho vào heap Sau lấy xong đỉnh cập nhật đáp án
Const
tfi='cezar.inp'; tfo='cezar.out'; oo = 1000000000;
1
2
4
6
7
9 10 11 12
(135)135
Type
arr1=array[0 100000] of longint; arr2=array[0 100000] of boolean; arr3=array[0 15000] of longint; arr4=array[0 15000] of arr3; Var
fi,fo : text;
heap,head,ke,d,c,pos,cost,trace,deg : arr1; free : arr2;
k,n,nheap,f,r,sum,res : longint; count : arr4;
{===============================} Procedure nhap;
Var
i,u,v : longint; Begin
Assign(fi,tfi);Reset(fi); read(fi,n,k);
For i := to n-1 Begin
read(fi,u,v); d[i] := u; c[i] := v; inc(deg[u]); inc(deg[v]); ENd;
head[1] := deg[1]; For i := to n+1 Begin
(136)136
For i := to n-1 Begin
u := d[i]; v := c[i];
ke[head[u]] := v; ke[head[v]] := u; cost[head[u]] := 1; cost[head[v]] := 1; dec(head[u]); dec(head[v]); End; Close(fi); End;
{===============================} Procedure khoitao;
Var
i :longint; Begin
Fillchar(free,sizeof(free),true); For i := to n
BEgin pos[i] := 0; cost[i] := 1; End; nheap := 0; End;
{===============================} Procedure downheap(root : longint);
Var
child,u : longint; BEgin
(137)137
repeat
child := root*2;
If (child <nheap) and (cost[heap[child]] > cost[heap[child+1]]) then inc(child); If (child >nheap) or (cost[heap[child]]>= cost[u]) then break;
heap[root] := heap[child]; pos[heap[root]] := root; root := child;
Until false; heap[root] := u; pos[u] := root; End;
{===============================} Procedure upheap(child : longint);
Var
root,u : longint; Begin
u := heap[child]; Repeat
root := child div 2;
If (root=0) or (cost[heap[root]] <= cost[u]) then break; heap[child] := heap[root];
pos[heap[child]] := child; child := root;
Until false; heap[child] := u; pos[u] := child; End;
{===============================} FUnctionpop :longint;
Begin
pop := heap[1];
(138)138
pos[heap[1]] := 1; dec(nheap); downheap(1); End;
{===============================} Procedure push(x :longint);
Begin inc(nheap); heap[nheap] := x;
pos[heap[nheap]] := nheap; upheap(nheap);
End;
{===============================} Procedure update(v : longint);
Var
i :longint; Begin
For i := head[v] + to head[v+1] Begin
dec(deg[ke[i]]);
cost[ke[i]] := cost[ke[i]] + cost[v]; If deg[ke[i]] = then push(ke[i]); End;
End;
{===============================} Function dijkstra(x :longint) : longint ;
Var
i,u : longint; Begin
(139)139
free[u] := false; update(u); untilnheap = 0; End;
{===============================} Procedure xuly;
Var
i,u : longint; Begin khoitao; res := 0;
For i := to n Begin
If deg[i]=1 then push(i); End;
repeat u := pop;
sum := sum + cost[u]; inc(res);
If res=n-1-k then break; free[u] := false;
update(u); until false;
End;
{===============================} Procedure inkq;
Begin
Assign(fo,tfo);REwrite(fo); write(fo,sum);
(140)140
{===============================} BEGIN
nhap; xuly; inkq; END
6. Bài tập 6: Toy Cars
Bé Tom đứa trẻ ba tuổi thích chơi đồ chơi oto Bé Tom có n oto khác nhau, chúng đặt chiếu giá cao mà Tom khơng thể tự lấy Phịng Tom nhỏ, thời điểm, khơng thể có nhiều k oto đồ chơi sàn nhà
Tom chơi với oto sàn nhà, Mẹ Ton ln phịng với Tom thời gian chơi Tom Khi Bé Tom muốn chơi với oto khác, sàn nhà, Tom tự lấy để chơi, oto giá, Mẹ Tom lấy xuống cho Tom (Khi Mẹ Tom lấy oto cho Tom,cùng lúc cô lấy oto khác sàn nhà để đặt lên giá – để có đủ khoảng không gian cho k oto)
Mẹ Tom người mẹ hiểu ý thích mình, ta biết oto mà trai muốn chơi Cơ ta muốn biết số lần mà ta giúp Tom lấy xe oto từ giá
INPUT: TOYCARS.INP
- Dòng 1: số nguyên dương N, K, P số lượng oto mà Tom có, số lượng oto đặt sàn thời điểm độ dài dãy oto mà Tom muốn chơi Các oto đánh số từ đến N
- P dòng tiếp theo, dòng số nguyên dương oto mà Tom muốn chơi (theo thứ tự thời gian)
OUTPUT: TOYCARS.OUT
(141)141 Ví dụ:
TOYCARS.INP TOYCARS.OUT
3 3
4
Thuật toán :
1 Gọi Next[i] = thời điểm gần sau thời điểm i mà bé Tom muốn chơi đồ chơi P[i] Next[i] = i thời điểm cuối bé Tom muốn chơi P[i] Như coi Next[i] = độ tồi việc giữ lại đồ chơi P[i] sàn (Next[i] lớn tức việc giữ lại P[i] vơ ích)
2 Xét thời điểm i :
a Nếu P[i] nằm sàn : bỏ qua xét thời điểm b Nếu mon P[i] nằm giá :
i Nếu sàn chưa đầy : Ta lấy P[i] để lên sàn
ii Nếu sàn đầy : Ta chọn từ sàn đồ chơi có độ tồi lớn đặt lên giá, sau lấy P[i] để lên sàn
3 Dễ dàng nhận thấy sử dụng cấu trúc Heap để thực thuật giải thời gian N.logK
const
tfi = 'toycars.inp'; tfo = 'toycars.out'; type
arr1 = array[0 1000000] of longint; arr2 = array[0 100000] of boolean; var
(142)142
n,k,p,res,nheap,x,dem,vc:longint; c,deg,d,a,pos,b,vt,next:arr1; free:arr2;
{ -} procedurenhap;
vari,j:longint; begin
assign(fi,tfi);reset(fi); read(fi,n,k,p);
for i:=1 to p read(fi,c[i]); close(fi); end;
{ -} procedurekhoitao;
vari,j:longint; begin
res:=0;
for i:=1 to n begin
free[i]:=true; vt[i]:=p+1; end;
for i:=p downto begin
b[i]:=vt[c[i]]; vt[c[i]]:=i; end; end;
{ -} proceduredoicho(vari,j:longint);
(143)143
begin tg:=i; i:=j; j:=tg; end;
{ -} procedureupheap(i:longint);
varj,k:longint; begin
if (i=1)or(d[a[i]]<=d[a[i div 2]]) then exit; j:=i div 2;
doicho(a[i],a[j]);
doicho(pos[a[i]],pos[a[j]]); upheap(j);
end;
{ -} proceduredownheap(i:longint);
var j:longint; begin
j:=i*2;
if j>nheap then exit;
if (d[a[j]]<d[a[j+1]])and(j<nheap) then inc(j); if d[a[j]]>d[a[i]] then
begin
doicho(a[i],a[j]);
doicho(pos[a[i]],pos[a[j]]); downheap(j);
end; end;
{ -} procedure push(i,j:longint);
(144)144
inc(nheap); a[nheap]:=i; pos[i]:=nheap; d[i]:=b[j]; upheap(nheap); end;
{ -} procedure pop;
vari,j:longint; begin
x:=a[1];
a[1]:=a[nheap]; pos[a[nheap]]:=1; dec(nheap); downheap(1); end;
{ -} procedurexuly;
vari,j:longint; begin
for i:=1 to p if free[c[i]] then begin
ifnheap=k then begin
pop;
free[x]:=true; end;
push(c[i],i); free[c[i]]:=false; inc(res);
(145)145
else begin
d[c[i]]:=b[i]; upheap(pos[c[i]]); end;
end;
{ -} procedureinkq;
vari,j:longint; begin
assign(fo,tfo);rewrite(fo); write(fo,res);
close(fo); end;
{ -} BEGIN
nhap; khoitao; xuly; inkq; END
7. Bài tập 7: BASE3
Cho số hệ số (viết số 0, 1, 2) Hãy tìm chữ số N hệ số cho N có số lẻ chữ số số nằm số N Số N phải thu từ việc liên kết số cho trước, số số sử dụng nhiều lần:
Yêu cầu:
Xác định số lượng nhỏ chữ số N thỏa mãn tính chất đầu
INPUT: BASE3.INP
(146)146
OUTPUT: BASE3.OUT
- Số lượng chữ số nhỏ N Nếu khơng tồn N ghi số
Giới hạn
- Số chữ số số số đầu cho khoảng từ đến 16.000
Ví dụ:
BASE3.IN BASE3.OUT
001 020 2020
13
Giải thích
N = 2020001001001 Time limit/test: 0.4
Ký tự xâu kết phải thuộc xâu:
Giả sử ký tự kết nằm vị trí I xâu , ta xây dựng xâu kết cách thêm xâu vào trái phải (phần L trái, ký tự giữa, phần R phải)
Ta xây dựng xâu đến dừng lại (yêu cầu đề bài)
Dễ thấy, ta quan tâm tới ghi nhận kết Tức tồn cách xây dựng L’, R’ mà
thỏa mãn sử dụng cách xây dựng với cặp với , cần lưu cặp có
Quy hoạch động = cách xếp có với
Nếu lần ta ln nắp xâu vào nửa ngắn ln đảm bảo (do ) Với trạng thái , ta cập nhật cho trạng thái + độ dài xâu nắp thêm vào Bài toán trở thành Dijkstra Heap trạng thái Các trạng thái sở ký tự xâu ban đầu Đáp án toán
1 R
(147)147
{$H+} uses math ; const
tfi = 'Base3.inp'; tfo = 'Base3.out'; Nmax = 16001;
vc = 1000000000000000000 ; type
arr1 = array[0 Nmax] of longint ; var
fi,fo : text;
n,m,nh,ss,xi,yi : longint; s : array[1 3] of string ; len : array[1 3] of longint ;
d : array[-Nmax Nmax,0 1] of int64 ; pos : array[-Nmax Nmax,0 1] of longint; h,h2 : array[0 4*Nmax] of longint; procedureNhap;
var
i,j :longint; begin ss := 0;
for i := to begin
readln(fi,s[i]);
len[i] := length(s[i]); ss := max(ss,len[i]) ; end;
end;
procedureUpheap(i:longint) ; var
(148)148
begin x := h[i] ; y := h2[i];
while (i > 1) and (d[x,y] < d[h[i div 2],h2[i div 2]]) begin
h[i] := h[i div 2] ; h2[i] := h2[i div 2]; pos[h[i],h2[i]] := i ; i := i div 2;
end ; h[i] := x; h2[i] := y; pos[x,y] := i; end;
proceduredownheap(i:longint); var
j,x,y:longint; begin
x := h[i]; y := h2[i];
while i * <= nh begin
j := i * ;
if (j <nh) and (d[h[j],h2[j]] >d[h[j+1],h2[j+1]]) then inc(j); if d[x,y] <= d[h[j],h2[j]] then break ;
h[i] := h[j]; h2[i] := h2[j]; pos[h[i],h2[i]] := i; i := j;
(149)149
pos[x,y] := i ; end;
procedure Push(i,j:longint); begin
ifpos[i,j] = then begin
inc(nh) ; h[nh] := i; h2[nh] := j; pos[i,j] := nh; Upheap(nh) ;
end else Upheap(pos[i,j]) ; end ;
procedure Pop; begin
xi := h[1]; yi := h2[1]; h[1] := h[nh]; h2[1] := h2[nh]; pos[h[1],h2[1]] := 1; dec(nh);
downheap(1) ; end;
procedure Update ; var
i,j,k:longint; begin
for i := to begin
if s[i][1] = '0' then k := else k := 1; j := xi + len[i];
(150)150
begin
d[j,k] := d[xi,yi] + len[i] ; push(j,k) ;
end;
j := xi - len[i] ;
if (j >= -ss) and (d[j,yi] > d[xi,yi] + len[i]) then begin
d[j,yi] := d[xi,yi] + len[i]; push(j,yi) ;
end ; end; end ;
procedureinit; var
i,j,k,u: longint; begin
for i := -ss to ss for j := to d[i,j] := vc ; for i := to
for j := to len[i] if s[i][j] = '1' then begin
if s[i][1] = '0' then k := else k := 1; u := (j - 1) - (len[i] - j) ;
d[u,k] := len[i] ; push(u,k) ; end; end;
procedurexuly; var
(151)151
whilenh> begin
pop;
if (xi = 0) then begin
write(fo,d[xi,yi]); exit;
end; update; end;
write(fo,0) ; end ;
begin
assign(fi,tfi);reset(fi); assign(fo,tfo);rewrite(fo); Nhap;
init; xuly; close(fi); close(fo) ; end
IV. Một số tập vận dụng
8. Bài tập 1: BOCSOI13
Bước vào tiểu học, Bé Bi cô giáo chủ nhiệm cho làm lớp trưởng Nhân dịp kỷ niệm ngày thành lập trường, Bé Bi tổ chức cho lớp chơi trị chơi sau:
Có N đống sỏi xếp thành hàng, đống thứ i có Ai viên sỏi Ta ghép hai
đống sỏi thành đống chi phí 5% tổng hai đống sỏi Hãy tìm cách ghép N đống sỏi thành đống với chi phí nhỏ
Ví dụ: Nếu có đống sỏi với số lượng sỏi 10, 11, 12 13
(152)152
- Bước 2: Ghép đống 21 vừa thu với đống 12 thành đống có số lượng 33 (chi phí 1.65)
- Bước 3: Ghép đống 33 vừa thu với đống 13 thành đống cuối có số lượng sỏi 46 (chi phí 2.3)
- Vậy tổng chi phí 5.00 Tuy nhiên khơng phải phương án ghép đống tối ưu, có phương án ghép đống thành đống với chi phí nhỏ 4.60
Các bạn tìm giúp Bé Bi phương án chơi tối ưu nhé!
INPUT:BOCSOI13.INP:
- Dòng 1: Số nguyên dương N số đống sỏi
- Dòng tiếp theo, ghi N số nguyên dương, tương ứng số lượng sỏi đống Số lượng sỏi không vượt 10.000
OUTPUT:BOCSOI13.OUT:
- Ghi số thực chi phí nhỏ phải trả để ghép N đống sỏi thành đống Kết ghi dạng chữ số sau dấu thập phân
Ví dụ:
BOCSOI13.INP BOCSOI13.OUT
4
10 11 12 13
4.60
2 1
0.10
9. Bài tập 2: Kế hoạch làm
Nobita giao n tập nhà đánh số từ tới n Mỗi tập cần đơn vị thời gian để làm thời điểm, Nobita làm tập Bài tập thứ I cần hồn thành khơng muộn thời điểm ti thứ I bị nộp muộn Nobita
sẽ bị thày giáo cho pi điểm
Giả sử Nobita định làm tập từ thời điểm a đến hết thời điểm b Hãy giúp Nobita lên kế hoạch làm tập để số điểm phải nhận
INPUT: PENALTY.INP
- Dòng 1: Chứa số nguyên dương n ;
(153)153 OUTPUT: PENALTY.OUT
Một số số điểm tối thiểu phải nhận
Ví dụ:
PENALTY.INP PENALTY.OUT Giải thích
5 100 20 10
25 Làm từ thời điểm đến thời điểm Làm từ thời điểm đến thời điểm Làm từ thời điểm đến thời điểm Bài bị nộp muộn
10. Bài tập 3: CATUN
Tại vương quốc HT có N thị trấn đánh số từ đến N M đường nối thị trấn Trong số N thị trấn có K thị trấn pháo đài chống giặc ngoại xâm Khi có giặc ngoại xâm, pháo đài bảo vệ thị trấn gần Nếu co thị trấn có khoảng cách tới pháo đài thị trấn bảo vệ pháo đài có số nhỏ hơn?
INPUT: CATUN.IN
- Dòng 1: N, M K (
- Dòng 2: K số nguyên dương số K pháo đài
- M dòng tiếp theo, dòng ghi số x, y, z có nghĩa có đường chiều nối thị trấn x với thị trấn y có chiều dài z
OUTPUT: CATUN.OUT
- Ghi N số nguyên dương, số thí I số pháo đài bảo vệ thị trấn i Nếu thị trấn I khơng bảo vệ pháo đài ghi số
Ví dụ:
CATUN.IN CATUN.OUT
8 2 6
(154)154
2 6 7 1000
Time limit: 0.2s
11. Bài tập 3: Barbar
Sau chiến tranh thần thánh, HT bị bắt giam vào ngục tối bảng cỡ R*C (R dịng C cột) Tại di chuyển sang kề cạnh Trong ngục có số di chuyển tự do, số ô tường di chuyển qua đặc biệt số ô bị chiếm giữ rồng lửa (không thể lại gần không muốn bị cháy thành than) Bạn giúp HT di chuyển qua ngục tối cho khoảng cách ngắn đến rồng lửa lớn nhất?
INPUT: BARBAR.IN
- Dòng 1: R C (
- R dòng tiếp theo, dòng ghi C ký tự biểu diễn ngục tối “.” Ô di chuyển tự
“*” Tường “D” Rồng lửa
“I”: Vị trí xuất phát HT “O”: Vị trí khỏi ngục tối
OUTPUT: BARBAR.OUT
- Một số giá trị lớn khoảng cách ngắn đến rồng lửa HT di chuyển khỏi ngục tối Nếu khơng có cách di chuyển ghi -1
Ví dụ:
BARBAR.IN BARBAR.OUT Giải thích
10 10 .I D
2
(155)155
D D * D* * D **** O
.* oo D* ooooo
* D o
**** o
Ooooooo
Time limit: 0.4s
12. Bài tập 4:Cầu cảng
Một cầu cảng có m cầu cảng để tiếp nhận tàu cập bến Tại thời điểm, cầu cảng tiếp nhận không tàu Ban đầu cầu cảng trống có n tàu xin đăng ký cập bến, tàu thứ i muốn đậu cảng ngày sau thời điểm tới hết thời điểm Có thể coi thời gian tàu thứ i muốn đậu cảng khoảng trục thời gian Tàu vào cầu cảng đậu suốt thời gian nằm cảng Yêu cầu : Hãy cho biết với m cầu cảng cho, tiếp nhận tối đa bao nhiều tàu lịch trình tiếp nhận cầu cảng ?
INPUT:SEAPORTS.INP
- Dòng 1: Chứa số nguyên dương
- N dòng tiếp theo, dòng thứ i số nguyên
OUTPUT:SEAPORTS.OUT
- Dòng : Ghi số lượng tàu tiếp nhận phục vụ
- Dòng : Ghi n số nguyên, số thứ i số hiệu cầu cảng tiếp nhận tàu thứ i trường hợp tàu thứ i tiếp nhận, tàu thứ i khơng tiếp nhận số thứ i
Ví dụ :
SEAPORTS.INP SEAPORTS.OUT
2 3 2
4
(156)156
1
13. Bài tập 5:K TỔNG BÉ NHẤT
Cho dãy số nguyên A B Với số A[i] thuộc A B[j] thuộc B (1 ≤ Ai, Bi ≤
109 ) người ta tính tổng Tất tổng sau xếp không giảm
tạo thành dãy C Nhiệm vụ bạn là: Cho dãy A, B Tìm K số dãy C
INPUT: KMIN.INP
- Dòng gồm số: M, N, K (1≤M, N, K ≤ 50.000) - M dòng gồm M số mơ tả dãy A
- N dịng gồm N số mô tả dãy B
OUTPUT:KMIN.OUT
- Gồm K dòng tương ứng K phần tử dãy C
Ví dụ:
KMIN.INP KMIN.OUT
4 4
3 4 5
Time limit: 1s
14. Bài tập 6: BINLADEN
(157)157
Bạn tìm cách từ mặt đất xuống phịng Bin Laden nhanh khơng
INPUT: BINLADEN.INP
- Dòng ghi M N
- Dòng đến 2M + 1, dòng chẵn ghi N số, dòng lẻ ghi N - số chi phí để phá cửa Chi phí cánh cửa thuộc [0, 1000]
OUTPUT: BINLADEN.OUT
- Ghi số thời gian nhỏ để đến phòng Bin Laden
Ví dụ:
BINLADEN.INP BINLADEN.OUT
4 99 10 10 99 99 10 10 99
44 + 99 + 10 + | | |
| | | | |
+ 10 + 99 + | | |
| | | | |
+ 99 + 10 + | | |
| | | | |
+ 10 + 99 + | | |
| | | | | + -+ -+
(158)158 V. Tài liệu tham khảo
1 Nguồn tập: Thầy Nguyễn Thanh Tùng, Thầy Lê Minh Hoàng, Rumania OI, Poland OI, Russian OI, vn.spoj.pl
2 Giải thuật lập trình – T.S Lê Minh Hồng – ĐHSP Hà Nội Website: www.infoarena.ro
(159)159
CẤU TRÚC DỮ LIỆU TẬP RỜI RẠC
Nguyễn Hoàng Phú
THPT chuyên Lý Tự Trọng – Cần Thơ Email: nghoangphu@cantho.edu.vn SĐT: 0917.699.599
1. Tóm tắt
Cấu trúc liệu tập rời rạc (Union-Find Disjoint Sets) cấu trúc liệu mô tả tập hợp tập rời rạc cho cấu trúc ta cài đặt hai phép toán sau với độ phức tạp xấp xỉ O(1):
- Xác định xem phần tử thuộc tập - Hợp hai tập thành tập
2. Cài đặt
Ý tưởng cho cấu trúc liệu sử dụng rừng để biểu diễn tập rời rạc, đại diện cho tập Và ứng với tập, ta biểu diễn tương ứng cách chọn phần tử làm đại diện cho tập (nút gốc cây) Để biểu diễn nút thơng tin nút giữ vector pSet (thông tin nút cha cây), pSet[i] = j cho biết nút thứ thuộc chứa nút thứ j (một hiểu khác phần tử thứ i thuộc tập chứa phần tử thứ j)
vector<int> pSet(1000);
Thủ tục khởi tạo tập rời rạc tạo tập hợp gồm n tập rời rạc mà tập ban đầu chứa phần tử
void initSet(int n) {
pSet.resize(n);
for (int i = 0; i < n; ++i) pSet[i] = i;
(160)160
Để xác định phần tử thuộc tập nào, ta tiến hành gọi đệ quy nút đại diện cho phần tử để tìm nút gốc cây, trình gọi đệ quy ta tiến hành điều chỉnh ln thơng tin lưu trữ nút để cho sau kết thúc trình gọi đệ quy nút qua thông tin lưu pSet tương ứng nút gốc cây, điều giúp cho độ phức tạp việc xác định lần O(1)
int findSet(int i) {
return (pSet[i] == i) ? i : (pSet[i] = findSet(pSet[i]));
}
Để hợp tập, ta đơn giản thay đổi thông tin nút gốc tập (hiện có giá trị nó) thành thơng tin nút gốc tập lại
void unionSet(int i, int j) {
pSet[findSet(i)] = findSet(j);
}
Để xác định xem phần tử có thuộc tập hay không, đơn giản ta kiểm tra xem nút gốc chứa phần tử có trùng hay không?
bool isSameSet(int i, int j) {
return findSet(i) == findSet(j);
}
3. Cải tiến
Một cách cải tiến để cấu trúc liệu hoạt động hiệu hơn: lưu trữ thơng tin nút cây, ngồi việc sử dụng vector pSet ta sử dụng thêm vector rSet để lưu độ cao cây, rSet[i] cho biết độ cao với i nút gốc Điều hữu ích cho ta tiến hành hợp tập, lúc ta chọn tập thuộc có độ cao nhỏ để gắn vào tập thuộc có độ cao lớn
Đoạn code cải tiến mô tả sau:
(161)161
vector<int> rSet(1000);
void initSet(int n) {
pSet.resize(n);
rSet.resize(n);
for (int i = 0; i < n; ++i) {
pSet[i] = i;
rSet[i] = 0;
}
}
int findSet(int i) {
return (pSet[i] == i) ? i : (pSet[i] = findSet(pSet[i]));
}
bool isSameSet(int i, int j) {
return findSet(i) == findSet(j);
}
void unionSet(int i, int j) {
if (!isSameSet(i, j)) {
(162)162
int y = findSet(j);
if (rSet[x] > rSet[y]) pSet[y] = x;
else {
pSet[x] = y;
if (rSet[x] == rSet[y]) rSet[y]++;
}
}
}
4. Một số ứng dụng
Cấu trúc liệu thường ứng dụng thuật toán Kruskal toán liên quan đến phân vùng đồ thị mà cần lưu vết lại thành phần liên thơng chúng
a) Thuật tốn Kruskal
vector< pair<int, pair<int, int> > > edge, mst;
sort(edge.begin(), edge.end());
initSet(n);
int ans = 0;
for (int i = 0; i < edge.size(); ++i) {
int w = edge[i].first, u = edge[i].second.first, v = edge[i].second.second;
if (isSameSet(u, v)) continue;
(163)163
ans += w;
unionSet(u, v);
}
b) Bài tốn: Mạng máy tính
Bình giao nhiệm vụ quản trị mạng máy tính cơng ty Gbum Hiện bạn giữ ghi kết nối hai chiều máy tính mạng Hai máy tính liên thơng với chúng có đường truyền trực tiếp chúng liên thông với máy khác Một hơm, Bình muốn biết cách nhanh chóng cho hai máy tính mạng có liên thơng với hay khơng biết thơng tin kết nối mạng máy tính
Yêu cầu: Viết chương trình đếm số lượng câu trả lời “CÓ” số lượng câu trả
lời “KHƠNG” cho câu hỏi: Máy tính thứ i có liên thơng với máy thứ j hay khơng?
Dữ liệu vào: Cho file văn network.inp có cấu trúc sau:
Dòng đầu ghi số nguyên dương n, m cho biết số lượng máy tính có mạng số lượng truy vấn
m dòng dòng truy vấn thuộc hai dạng sau:
o Dạng 1: c i j cho biết máy tính i kết nối với máy tính j qua đường truyền trực tiếp
o Dạng 2: q i j cho biết câu hỏi máy tính i có liên thơng với máy tính j hay khơng?
Mạng máy tính cập nhật xuất truy vấn dạng 1, câu trả lời cho truy vấn dạng áp dụng thơng tin mạng máy tính
Kết quả: ghi file văn network.out gồm số, số cho biết số lượng câu trả lời “CÓ” số thứ hai cho biết số lượng câu trả lời “KHƠNG”
Ví dụ:
(164)164
10
c
c
q
c
q
c
q
1
c) Bài toán: Bạn bè
Một thị trấn có n dân cư dân thành phố tiếng thân thiện nên A bạn B B bạn C A bạn C
Yêu cầu: Cho thông tin số cặp bạn xác định xem nhóm có số
lượng lớn bạn bè
Dữ liệu vào: Cho file văn friend.inp có cấu trúc sau:
Dòng ghi số nguyên dương n m cho biết số lượng cư dân số cặp bạn
m dòng tiếp theo, dòng ghi số nguyên dương a b cho biết a b bạn
Kết quả: Ghi file văn friend.out số số lượng lớn tìm
Ví dụ:
friend.inp friend.out
10 12
1
(165)165
3
3
5
3
4
5
2
7 10
1
9 10
8
5. Tài liệu tham khảo
[1] Competitive programming 3, Steven Halim & Felix Halim, 2013
[2] https://uva.onlinejudge.org/
(166)166
CÂY KHUNG NHỎ NHẤT
THPT Chuyên Huỳnh Mẫn Đạt – Kiên Giang
A. Lý thuyết
Bài toán khung nhỏ đồ thị số toán tối ưu đồ thị tìm ứng dụng nhiều lĩnh vực khác đời sống Trong mục trình bày thuật toán để giải toán Trước hết phát biểu nội dung toán
1. Phát biểu toán:
Cho G =(V,E) đồ thị vô hướng liên thông với tập đỉnh V ={1, 2, ,n} tập cạnh E gồm m cạnh Mỗi cạnh E đồ thị G gán với số không âm c(e), gọi độ dài Giả sử H=(V,T) khung đồ thị G Ta gọi độ dài c(H) khung H tổng độ dài cạnh
Bài tốn đặt tất khung đồ thị G tìm khung với độ dài nhỏ Cây khung vậy gọi khung nhỏ đồ thị toán đặt gọi toán khung nhỏ
2. Thuật tốn Kruskal:
Thuật tốn Kruskal dựa mơ hình xây dựng khung thuật tốn hợp (§5), có điều thuật tốn khơng phải xét cạnh với thứ tự tuỳ ý mà xét cạnh theo thứ tự xếp: Với đồ thị vô hướng G = (V, E) có n đỉnh Khởi tạo T ban đầu khơng có cạnh Xét tất cạnh đồ thị từ cạnh có trọng số nhỏ đến cạnh có trọng số lớn, việc thêm cạnh vào T khơng tạo thành chu trình đơn T kết nạp thêm cạnh vào T Cứ làm khi:
❖Hoặc kết nạp n - cạnh vào T ta T khung nhỏ
(167)167
1.1.1.1 Như có hai vấn đề quan trọng cài đặt thuật toán Kruskal:
Thứ nhất, làm để xét cạnh từ cạnh có trọng số nhỏ tới cạnh có trọng số lớn Ta thực cách xếp danh sách cạnh theo thứ tự khơng giảm trọng số, sau duyệt từ đầu tới cuối danh sách cạnh Trong trường hợp tổng qt, thuật tốn HeapSort hiệu cho phép chọn cạnh từ cạnh trọng nhỏ tới cạnh trọng số lớn khỏi Heap xử lý (bỏ qua hay thêm vào cây)
Thứ hai, làm kiểm tra xem việc thêm cạnh có tạo thành chu trình đơn T hay khơng Để ý cạnh T bước tạo thành rừng (đồ thị khơng có chu trình đơn) Muốn thêm cạnh (u, v) vào T mà không tạo thành chu trình đơn (u, v) phải nối hai khác rừng T, u, v thuộc tạo thành chu trình đơn Ban đầu, ta khởi tạo rừng T gồm n cây, gồm đỉnh, sau đó, xét đến cạnh nối hai khác nhau của rừng T ta kết nạp cạnh vào T, đồng thời hợp hai lại thành cây
Nếu cho đỉnh v nhãn Lab[v] số hiệu đỉnh cha đỉnh v cây, trường hợp v gốc Lab[v] gán giá trị âm Khi ta hồn tồn xác định gốc chứa đỉnh v hàm GetRoot đây:
function GetRoot(vỴV): ỴV; begin
while Lab[v] > v := Lab[v]; GetRoot := v; end;
Vậy để kiểm tra cạnh (u, v) có nối hai khác rừng T hay khơng? ta kiểm tra GetRoot(u) có khác GetRoot(v) hay khơng, có gốc
(168)168
thì khơng quan trọng Vậy để hợp gốc r1 gốc r2, ta việc coi r1 nút cha r2 cách đặt:
Lab[r2] := r1
Tuy nhiên, để thuật toán làm việc hiệu quả, tránh trường hợp tạo thành bị suy biến khiến cho hàm GetRoot hoạt động chậm, người ta thường đánh giá: Để hợp hai lại thành một, gốc nút bị coi gốc
Thuật toán hợp gốc r1 gốc r2 viết sau: {Count[k] số đỉnh gốc k}
procedure Union(r1, r2 Ỵ V); begin
if Count[r1] < Count[r2] then {Hợp thành gốc r2}
begin
Count[r2] := Count[r1] + Count[r2]; Lab[r1] := r2; end
else {Hợp thành gốc r1}
begin
Count[r1] := Count[r1] + Count[r2]; Lab[r2] := r1; end;
end;
Khi cài đặt, ta tận dụng nhãn Lab[r] để lưu số đỉnh gốc r, giải thích trên, Lab[r] cần mang giá trị âm đủ, ta coi Lab[r] = -Count[r] với r gốc
(169)169 begin
x := Lab[r1] + Lab[r2]; {-x tổng số nút hai cây}
if Lab[r1] > Lab[r2] then {Cây gốc r1 nút gốc r2, hợp thành gốc r2}
begin
Lab[r1] := r2; {r2 cha r1}
Lab[r2] := x; {r2 gốc mới, -Lab[r2] số nút mới}
end
else {Hợp thành gốc r1}
begin
Lab[r1] := x; {r1 gốc mới, -Lab[r1] số nút mới}
Lab[r2] := r1; {cha r2 r1}
end; end;
Mơ hình thuật tốn Kruskal:
for "kỴV Lab[k] := -1;
for ("(u, v) Ỵ E theo thứ tự từ cạnh trọng số nhỏ tới cạnh trọng số lớn) begin r1 := GetRoot(u); r2 := GetRoot(v);
if r1 ¹ r2 then {(u, v) nối hai khác nhau}
begin
<Kết nạp (u, v) vào cây, đủ n - cạnh thuật toán dừng> Union(r1, r2); {Hợp hai lại thành cây}
(170)170 end;
Xét độ phức tạp tính tốn, ta chứng minh thao tác GetRoot có độ phức tạp O(lgn), thao tác Union O(1) Giả sử ta có danh sách cạnh xếp xét vịng lặp dựng khung, duyệt qua danh sách cạnh với cạnh gọi lần thao tác GetRoot, độ phức tạp O(mlgn), đồ thị có khung m ³ n-1 nên ta thấy chi phí thời gian chủ yếu nằm thao tác xếp danh sách cạnh độ phức tạp HeapSort O(mlgm) Vậy độ phức tạp tính tốn thuật tốn O(mlgm) trường hợp xấu Tuy nhiên, phải lưu ý để xây dựng khung thuật tốn phải duyệt tồn danh sách cạnh mà phần danh sách cạnh mà
3.Thuật toán Prim:
Thuật toán Kruskal hoạt động chậm trường hợp đồ thị dày (có nhiều cạnh) Trong trường hợp người ta thường sử dụng phương pháp lân cận gần Prim Thuật tốn phát biểu hình thức sau:
Đơn đồ thị vơ hướng G = (V, E) có n đỉnh cho ma trận số C Qui ước c[u, v] =
+¥ (u, v) khơng cạnh Xét T G đỉnh v, gọi khoảng cách từ v tới T trọng số nhỏ số cạnh nối v với đỉnh T:
d[v] = min{c[u, v] ⎪ T}
Ban đầu khởi tạo T gồm có đỉnh {1} Sau chọn số đỉnh T đỉnh gần T nhất, kết nạp đỉnh vào T đồng thời kết nạp cạnh tạo khoảng cách gần Cứ làm khi:
❖ Hoặc kết nạp tất n đỉnh ta có T khung nhỏ
❖ Hoặc chưa kết nạp hết n đỉnh đỉnh ngồi T có khoảng cách tới T
(171)171
Về mặt kỹ thuật cài đặt, ta rỗng khởi tạo d[1] := d[v] := +Ơ vi "v Ti mi bc chn đỉnh đưa vào T, ta chọn đỉnh u ngồi T có d[u] nhỏ Khi kết nạp u vào T nhãn d[v] thay đổi: d[v]mới := min(d[v]cũ, c[u, v]) Bước lặp kết nạp đỉnh vào cây, từ bước lặp thứ hai, trước kết nạp đỉnh vào cây, ta lưu lại cạnh tạo khoảng cách gần từ tới đỉnh để cuối in khung nhỏ
B.Ví dụ: 1 Ví dụ 1:
Cho G = (V, E) đồ thị vơ hướng liên thơng có trọng số, với khung T G, ta gọi trọng số T tổng trọng số cạnh T Bài toán đặt số khung G, khung có trọng số nhỏ nhất, khung gọi khung nhỏ đồ thị (minimum spanning tree) tốn gọi tốn khung nhỏ Sau ta xét hai thuật tốn thơng dụng để giải toán khung nhỏ đơn đồ thị vơ hướng có trọng số Bạn đưa vào số sửa đổi nhỏ thủ tục nhập liệu để giải toán trường hợp đa đồ thị
Input: file văn MINTREE.INP:
❖Dòng 1: Ghi hai số số đỉnh n (≤ 1000) số cạnh m đồ thị cách dấu cách
❖m dòng tiếp theo, dịng có dạng số u, v, c[u, v] cách dấu cách thể đồ thị có cạnh (u, v) trọng số cạnh c[u, v] (c[u, v] số nguyên có giá trị tuyệt đối không 1000)
Output: file văn MINTREE.OUT ghi cạnh thuộc khung trọng số
khung
(172)172 6
1 1 2 2 2 3 3 4 5
Minimum spanning tree: (2, 4) = (3, 6) = (2, 5) = (1, 3) = (1, 2) = Weight =
Cách giải: áp dụng thuật tốn Kruskal trình bày
Code:
type re=record
u,v,c:longint;
kn:boolean;
end;
const fi='bt.inp';
fo='bt.out';
var f:text;
a:array[0 100001] of re;
root:array[0 100001] of longint;
n,m,w,count:int64;
i:longint;
con:boolean;
(173)173
begin
assign(f,fi);
reset(f);
readln(f,n,m);
for i:=1 to m readln(f,a[i].u,a[i].v,a[i].c);
close(f);
end;
procedure sort(l,r: longint);
var
i,j: longint;
x,y:longint;
begin
i:=l;
j:=r;
x:=a[(l+r) div 2].c;
repeat
while a[i].c<x
inc(i);
while x<a[j].c
dec(j);
if not(i>j) then
(174)174
y:=a[i].c;
a[i].c:=a[j].c;
a[j].c:=y;
y:=a[i].u;
a[i].u:=a[j].u;
a[j].u:=y;
y:=a[i].v;
a[i].v:=a[j].v;
a[j].v:=y;
inc(i);
j:=j-1;
end;
until i>j;
if l<j then
sort(l,j);
if i<r then
sort(i,r);
end;
function getroot(v:longint):longint;
begin
if (root[v]=0) then exit(v);
(175)175
exit(root[v]);
end;
procedure kruskal;
var r1,r2:longint;
begin
con:=false;
count:=0;
for i:=1 to n root[i]:=0;
for i:=1 to m
begin
r1:=getroot(a[i].u);
r2:=getroot(a[i].v);
if r1<>r2 then
begin
a[i].kn:=true;
inc(count);
if count=n-1 then
begin
con:=true;
exit;
end;
(176)176
end;
end;
end;
procedure output;
begin
assign(f,fo);
rewrite(f);
if con=false then writeln(f,'BEEP')
else
begin
count:=0;
w:=0;
for i:=1 to m
begin
if a[i].kn then
begin
writeln(f,a[i].u,' ',a[i].v,' ',a[i].c);
inc(count);
w:=w+a[i].c;
end;
if count=n-1 then break;
(177)177
write(f,w);
end;
close(f);
end;
begin
input;
sort(1,m);
kruskal;
output;
end
2.Ví dụ 2:
Nước Anpha lập kế hoạch xây dựng thành phố đại Theo kế hoạch, thành phố có N vị trí quan trọng, gọi N trọng điểm trọng điểm đánh số từ tới N Bộ giao thông lập danh sách M tuyến đường hai chiều xây dựng hai trọng điểm Mỗi tuyến đường có thời gian hồ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 thỏa mãn
Do nhận đầu tư lớn từ 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 tuyến đường cần xây dựng thời gian lâu hoàn thành tuyến đường
(178)178
1.1.2 Dữ liệu
Dòng chứa số N M (1 ≤ N ≤ 1000; ≤ M ≤ 10000)
M tiếp theo, dòng chứa ba số nguyên u, v t cho biết xây dựng tuyến đường nối trọng điểm u trọng điểm v thời gian t Khơng có hai tuyến đường nối cặp trọng điểm
1.1.3 Kết
Một số nguyên thời gian sớm hoàn thành tuyến đường thỏa mãn yêu cầu nêu
1.1.4 Ví dụ
Dữ liệu Kết quả
5
1 2
1
2
1
1
5
3 4
3
Cách giải: Một toán khung nhỏ Ở dùng thuật tốn Kruskal + Heap sort Chi phí lớn cạnh thứ n-1 khung kết nạp vào
Code:
(179)179
maxN =1000;
maxM =10000;
Type
TEdge =Record
u,v,c :SmallInt;
end;
Var
n,m,res :SmallInt;
E :Array[1 maxM] of TEdge;
Lab :Array[1 maxN] of SmallInt;
procedure Enter;
var
i :SmallInt;
begin
Read(n,m);
for i:=1 to m
with (E[i]) Read(u,v,c);
end;
procedure Init;
var
i :SmallInt;
(180)180
for i:=1 to n Lab[i]:=-1;
end;
function GetRoot(x :SmallInt) :SmallInt;
begin
while (Lab[x]>0) x:=Lab[x];
Exit(x);
end;
procedure Union(r1,r2 :SmallInt);
var
x :SmallInt;
begin
x:=Lab[r1]+Lab[r2];
if (Lab[r1]>Lab[r2]) then
begin
Lab[r1]:=r2; Lab[r2]:=x;
end
else
begin
Lab[r1]:=x; Lab[r2]:=r1;
end;
end;
(181)181
var
Key :TEdge;
child :SmallInt;
begin
Key:=E[root];
while (root*2<=leaf)
begin
child:=root*2;
if (child<leaf) and (E[child].c>E[child+1].c) then Inc(child);
if (Key.c<=E[child].c) then Break;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
procedure Greedy;
var
count,i,r1,r2 :SmallInt;
Tmp :TEdge;
begin
for i:=m div downto DownHeap(i,m);
(182)182
for i:=m-1 downto
begin
Tmp:=E[1]; E[1]:=E[i+1]; E[i+1]:=Tmp;
DownHeap(1,i);
r1:=GetRoot(E[i+1].u); r2:=GetRoot(E[i+1].v);
if (r1<>r2) then
begin
Inc(count);
if (count=n-1) then
begin
res:=E[i+1].c; Break;
end;
Union(r1,r2);
end;
end;
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Init;
(183)183
Write(res);
Close(Input); Close(Output);
End
3.Ví dụ 3:
Nông dân John định mang nước tới cho N (1 <= N <= 300) đồng cỏ mình, để thuận tiện ta đánh số đồng cỏ từ đến N Để tưới nước cho đồng cỏ John chọn cách, đào đồng cỏ giếng lắp ống nối dẫn nước từ đồng cỏ trước có nước tới
Để đào giếng đồng cỏ i cần số tiền W_i (1 <= W_i <= 100,000) Lắp ống dẫn nước nối đồng cỏ i j cần số tiền P_ij (1 <= P_ij <= 100,000; P_ij = P_ji; P_ii=0)
Tính xem nơng dân John tiền để tất đồng cỏ có nước
1.1.5 DỮ LIỆU
Dịng 1: Một số nguyên nhất: N
Các dòng N + 1: Dòng i+1 chứa số nguyên nhất: W_i
Các dòng N+2 2N+1: Dòng N+1+i chứa N số nguyên cách dấu cách; số thứ j P_ij
1.1.6 KẾT QUẢ
Dòng 1: Một số nguyên chi phí tối thiểu để cung cấp nước cho tất đồng cỏ
1.1.7 VÍ DỤ
Dữ liệu Kết quả
4
(184)184
4 2 2 3 4 1.1.8 GIẢI THÍCH
Có đồng cỏ Mất tiền để đào giếng đồng cỏ 1, tiền để đào đồng cỏ 2, tiền để đào đồng cỏ Các ống dẫn nước tốn 2, 3, tiền tùy thuộc vào nối đồng cỏ với
Nơng dân John đào giếng đồng cỏ thứ lắp ống dẫn nối đồng cỏ với tất đồng cỏ lại, chi phí tổng cộng + + + =
Cách giải: Dùng thuật toán Prim Tạo sẵn đỉnh ảo đỉnh bắt đầu để kết nạp
các cạnh Tương ứng C[0, i] chi phí để đặt giếng nước cánh đồng i Kết tổng D[i]
Code:
Const
oo =1000000001;
Var
n :Integer;
D :Array[0 300] of LongInt;
Free :Array[0 300] of Boolean;
C :Array[0 300,0 300] of LongInt;
procedure Enter;
(185)185
i,j :Integer;
begin
Read(n);
FillChar(Free,SizeOf(Free),true);
for i:=1 to n Read(C[0,i]);
for i:=1 to n
for j:=1 to n Read(C[i,j]);
for i:=1 to n D[i]:=oo; D[0]:=0;
end;
procedure Optimize;
var
i,u,v :Integer;
:LongInt;
begin
for i:=0 to n
begin
min:=High(LongInt); u:=-1;
for v:=0 to n
if (min>D[v]) and (Free[v]) then
begin
min:=D[v]; u:=v;
(186)186
if (u=-1) then Break;
Free[u]:=false;
for v:=1 to n
if (Free[v]) and (D[v]>C[u,v]) then D[v]:=C[u,v];
end;
end;
procedure Escape;
var
i :Integer;
res :LongInt;
begin
res:=0;
for i:=1 to n Inc(res,D[i]);
Write(res);
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Optimize;
Escape;
(187)187
End
4.Ví dụ 4:
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 sốt giao thông để bắt giữ tay đua phạm luật Hệ thống bao gồm số camera đặt tuyến đường khác Để đảm bảo tính hiệu cho hệ thống, cần có camera dọc theo vòng đua
Hệ thống đường Singapore mơ tả dãy nút giao thơng đườ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, đường bao gồm tuyến đường cuối quay trở lại điểm xuất phát Trong vòng đua, tuyến đường qua lần, theo 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 tuyến đường Các số lớn xác định nút giao thông Camera đặt tuyến đường nút giao thông Bạn cần chọn số tuyến đường cho chi phí lắp đặt thấp đồng thời đảm bảo có camera dọc theo vịng đua
Viết chương trính tìm cách đặt camera theo dõi giao thơng cho tổng chi phí lắp đặt thấp
1.1.9 Dữ liệu
Dòng chứa số nguyên n, m ( ≤ n ≤ 10000, ≤ m ≤ 100000) số nút giao thông số đường nối Các nút giao thông đánh số từ đến n m dòng mơ tả đường nối, dịng bao gồm số nguyên dương
cho biết hai đầu mút tuyến đường chi phí lắp đặt camera Chi phí lắp đặt thuộc phạm vi [1, 1000]
1.1.10 Kết qủa
(188)188
1.1.11 Ví dụ
Dữ liệu: Kết qủa
6 3 5 6 3
6
Cách giải: Dùng thuật toán Kruskal
Kết : Tổng trọng số đồ thị – Tổng trọng số khung lớn
Giải thích : Do tính chất khung thêm cạnh vào ta thu chu trình đơn Vì chọn khung lớn nhất, cạnh lại cạnh nhỏ Lắp camera vào cạnh lại đảm bảo chu trình đồ thị phải qua cạnh này, tức ta khơng bỏ lỡ vịng đua
Code:
Const
maxN =10000;
maxM =100000;
Type
TEdge =Record
u,v,c :SmallInt;
(189)189
Var
n :SmallInt;
m,sum,res :LongInt;
E :Array[1 maxM] of TEdge;
Lab :Array[1 maxN] of SmallInt;
procedure Enter;
var
i :LongInt;
begin
Read(n,m); sum:=0;
for i:=1 to m
with (E[i])
begin
Read(u,v,c); Inc(sum,c);
end;
end;
procedure Init;
var
i :SmallInt;
begin
for i:=1 to n Lab[i]:=-1;
(190)190
function GetRoot(x :SmallInt) :SmallInt;
begin
while (Lab[x]>0) x:=Lab[x];
Exit(x);
end;
procedure Union(r1,r2 :SmallInt);
var
x :SmallInt;
begin
x:=Lab[r1]+Lab[r2];
if (Lab[r1]>Lab[r2]) then
begin
Lab[r1]:=r2; Lab[r2]:=x;
end
else
begin
Lab[r1]:=x; Lab[r2]:=r1;
end;
end;
procedure DownHeap(root,leaf :LongInt);
(191)191
Key :TEdge;
child :LongInt;
begin
Key:=E[root];
while (root*2<=leaf)
begin
child:=root*2;
if (child<leaf) and (E[child].c<E[child+1].c) then Inc(child);
if (Key.c>=E[child].c) then Break;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
procedure Greedy;
var
Tmp :TEdge;
r1,r2 :SmallInt;
i,count :LongInt;
begin
for i:=m div downto DownHeap(i,m);
(192)192
for i:=m-1 downto
begin
Tmp:=E[1]; E[1]:=E[i+1]; E[i+1]:=Tmp;
DownHeap(1,i);
r1:=GetRoot(E[i+1].u); r2:=GetRoot(E[i+1].v);
if (r1<>r2) then
begin
Inc(count);
Inc(res,E[i+1].c);
if (count=n-1) then Break;
Union(r1,r2);
end;
end;
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Init;
Greedy;
Write(sum-res);
(193)193
End
5 Ví dụ 5:
Bác John dạo lười khơng muốn bảo trì đường dẫn bác đến thăm N (5 <= N <= 10,000) cánh đồng (đánh số từ đến N) Mỗi cánh đồng nơi bị Bác John có kế hoạch loại bỏ nhiều P (N-1 <= P <= 100,000) đường cho cánh đồng liên thông
Ban phải xác định N-1 đường cần giữ lại
Đường nối hai chiều j nối cánh đồng Sj Ej (1 <= Sj <= N; <= Ej <= N;
Sj # Ej) cần Lj (0 <= Lj <= 1000) thời gian để di chuyển Khơng có hai cánh đồng
nào nối trực tiếp nhiều đường
Đàn bị buồn hệ thống giao thơng chúng bị rút gọn Bạn phải thăm bị lần ngày để động viên Mỗi lần thăm cánh đồng i (dù ngang qua), bạn phải trị chuyện với bị thời gian Ci (1 <= Ci <= 1000)
Bạn nghỉ lại đêm cánh đồng (bạn chọn) đàn bò hết bị suy sụp Bạn trị chuyện với bị cánh đồng mà bạn nghỉ lại lần vào buổi sáng thức dậy vào buổi tối trở nghỉ
Giả dụ bác John theo lời khuyên bạn giữ lại số đường bạn chọn cánh đồng tối ưu để nghỉ lại, xác định thời gian nhỏ bạn cần để thăm tất đàn bị lần ngày
1.1.12 Dữ liệu
* Dòng 1: Hai số nguyên N P cách khoảng trắng * Dòng N+1: Dòng i+1 chứa số nguyên Ci
* Dòng N+2 N+P+1: Dòng N+j+1 chứa ba số nguyên phân biệt: Sj, Ej Lj
1.1.13 Kết
* Dòng 1: Một số nguyên nhất, tổng thời gian cần để thăm tất đàn bị (bao gồm hai lần thăm bị nơi mà bạn nghỉ)
1.1.14 Ví dụ
Dữ liệu: Kết quả:
5 10
30
3 17 15
(194)194
10 20
2 12
3 12
Cách giải:
Nhìn hình vẽ, nhận thấy với khung chọn, cạnh khung qua hai lần đỉnh qua số cạnh kề với (chỉ tính cạnh chọn) Ví dụ hình cạnh 1-2 chọn giả sử bắt đầu đỉnh cạnh qua hai lần, về; đỉnh thăm lần (không kể trường hợp bắt đầu phải thăm hai lần), đỉnh thăm lần (có cạnh kề tơ màu tím, đường nhiên khơng tính đỉnh bắt đầu 2) Riêng đỉnh bắt đầu khung thăm thêm lần ban đầu thăm nó.Từ nhận xét ta gán trọng số cạnh (u,v) từ w thành w * + c[u] + c[v] Khi ta chọn cạnh ta tính ln lần qua số lần thăm u, v (các bạn test tay để nhận đắn thuật toán) Lúc cần tìm khung nhỏ sau chọn đỉnh bắt đầu nhỏ
Code:
Uses Math;
Const
maxN =10000;
maxM =100000;
(195)195
TEdge =Record
u,v,c :SmallInt;
end;
Var
n :SmallInt;
m :LongInt;
A,Lab :Array[1 maxN] of SmallInt;
E :Array[1 maxM] of TEdge;
res :Int64;
procedure Enter;
var
i :LongInt;
begin
Read(n,m);
for i:=1 to n Read(A[i]);
for i:=1 to m
with (E[i])
begin
Read(u,v,c);
c:=2*c+A[u]+A[v];
end;
(196)196
procedure Init;
var
i :SmallInt;
begin
for i:=1 to n Lab[i]:=-1;
end;
function GetRoot(x :SmallInt) :SmallInt;
begin
while (Lab[x]>0) x:=Lab[x];
Exit(x);
end;
procedure Union(r1,r2 :SmallInt);
var
x :SmallInt;
begin
x:=Lab[r1]+Lab[r2];
if (Lab[r1]>Lab[r2]) then
begin
Lab[r1]:=r2; Lab[r2]:=x;
end
(197)197
begin
Lab[r1]:=x; Lab[r2]:=r1;
end;
end;
procedure DownHeap(root,leaf :LongInt);
var
Key :TEdge;
child :LongInt;
begin
Key:=E[root];
while (root*2<=leaf)
begin
child:=root*2;
if (child<leaf) and (E[child].c>E[child+1].c) then Inc(child);
if (Key.c<=E[child].c) then Break;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
(198)198
var
Tmp :TEdge;
i,count :LongInt;
r1,r2 :SmallInt;
begin
for i:=m div downto DownHeap(i,m);
count:=0; res:=0;
for i:=m-1 downto
begin
Tmp:=E[1]; E[1]:=E[i+1]; E[i+1]:=Tmp;
DownHeap(1,i);
r1:=GetRoot(E[i+1].u); r2:=GetRoot(E[i+1].v);
if (r1<>r2) then
begin
Inc(count);
Inc(res,E[i+1].c);
if (count=n-1) then Break;
Union(r1,r2);
end;
end;
(199)199
procedure Escape;
var
i,minV :SmallInt;
begin
minV:=A[1];
for i:=2 to n minV:=Min(minV,A[i]);
Write(res+minV);
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Init;
Greedy;
Escape;
Close(Input); Close(Output);
End
C.Tài liệu tham khảo:
- Giải thuật lập trình (thầy Lê Minh Hồng)
- Tài liệu giáo khoa chuyên tin (thầy Hồ Sĩ Đàm)
(200)200
Ngồi bạn tham khảo thêm sách sau đây:
❖Alfred V Aho, Jeffrey D Ullman, John E Hopcroft Data Structures and Algorithms, ISBN: 0201000237, Addison Wesley, 1983
❖Robert Sedgewick Algorithms 2nd edition, ISBN: 0201066734, Addison Wesley, 1988
BÀI TỐN TÌM XÂU CON CHUNG DÀI NHẤT : BÀI TOÁN XÂU CON ĐỐI XỨNG DÀI NHẤT : BÀI TOÁN DI CHUYỂN TỪ TÂ : https://goo.gl/DbO6hd kiểu liệu trừu tượng hàng đợi ưu tiên thuật toán đồ thị thuật toán Dijkstra, Krus p Heapsort. ( http://vn.spoj.com/problems/HEAP1/ IN (http://vn.spoj.com/problems/KMIN/cstart=30 ưu tiên (http://vn.spoj.com/problems/QBHEAP/ p sư (http://vn.spoj.com/problems/VOSNSEQ/ ng lưu (http://vn.spoj.com/problems/MOVE12/ (http://vn.spoj.com/problems/NKCITY/ (http://vn.spoj.com/problems/FWATER/ bsite: www.infoarena.ro 2] https://uva.onlinejudge.org/ 3] http://www.spoj.com/