Kì yếu Trại hè Phương Nam - Môn Tin học - Cà Mau 2017

328 78 0
Kì yếu Trại hè Phương Nam - Môn Tin học - Cà Mau 2017

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

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

Thông tin tài liệu

+ 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 MC LC

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

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

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

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 - pomin(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;

 pov - 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 cici 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/

Ngày đăng: 25/02/2021, 08:39

Tài liệu cùng người dùng

Tài liệu liên quan