Professional Documents
Culture Documents
Trai He PN Lan IV-ky Yeu Mon Tin 646eccad98
Trai He PN Lan IV-ky Yeu Mon Tin 646eccad98
Trai He PN Lan IV-ky Yeu Mon Tin 646eccad98
MỤC LỤC
2
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
3
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Trong mọi khía cạnh của cuộc sống hàng ngày, công việc và nghiên cứu, con
người phải luôn đối mặt với các bài toán để lựa chọn phương án có thể chấp nhận
được. Trong số những bài toán đó, có những bài toán chỉ đơn giản là cần có một giải
pháp nhưng cũng có những bài toán khắt khe hơn, đòi hỏi không chỉ là một giải pháp
mà giải pháp đó còn phải là giải pháp tốt nhất (nhanh nhất, đơn giản nhất, hiệu quả
nhất...) đó được gọi là những bài toán tối ưu. Các bài toán tối ưu thường là một số rất
lớn nghiệm, việc tìm ra nghiệm tối ưu đòi hỏi rất nhiều thời gian.
Ý tưởng phương pháp tham lam là một trong những hướng suy nghĩ được sử
dụng phổ biến trong những tình huống này. Các thuật toán sử dụng phương pháp tham
lam như nền tảng logic để tiếp cận, giải quyết bài toán được gọi là các thuật toán tham
lam. Các thuật toán ứng dụng phương pháp tham lam thường diễn ra qua nhiều giai
đoạn và tại mỗi giai đoạn chúng ta thường không biết thông tin toàn bộ dữ liệu của cả
quá trình mà chỉ biết tình trạng hiện tại và thông tin cho bước đi kế tiếp. Ý tưởng chính
của phương pháp tham lam là chúng ta không cần quan tâm tới dữ liệu tổng thể mà chỉ
từ những dữ liệu tại từng giai đoạn để chọn ra giải pháp tối ưu tại mỗi giai đoạn đó với
hy vọng là tổng hợp những giải pháp tối ưu cục bộ sẽ mang lại một giải pháp tối ưu
cho tổng thể. Từ ý tưởng đó cho thấy, kết quả có được từ thuật toán tham lam chỉ
mang tính tối ưu tương đối, thông thường giải pháp cuối cùng có thể chưa là tối ưu mà
chỉ là tiệm cận với phương án tối ưu. Tuy nhiên với những điều kiện, thông tin thường
không mấy rõ ràng của đầu vào, và cách tiếp cận vấn đề khá “trong sáng” của mình,
phương pháp tham lam thường đưa ra giải pháp tương đối tốt trong giới hạn có thể
chấp nhận và đặc biệt có tốc độ tính toán nhanh (giảm độ phức tạp tính toán của các
thuật toán). Các thuật toán tham lam nói chung là đơn giản và hiệu quả (vì các tính
toán để tìm ra quyết định tối ưu địa phương thường là đơn giản). Tuy nhiên, các thuật
toán tham lam có thể không tìm được nghiệm tối ưu mà cho ra nghiệm tiệm cận tối ưu
(nghiệm tương đối tốt), nhưng thông thường lại giúp giảm độ phức tạp thuật toán của
các thuật toán một cách đáng kể. Bên cạnh đó cũng có nhiều thuật toán được thiết kế
4
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
theo kỹ thuật tham lam vẫn cho ta nghiệm tối ưu, chẳng hạn thuật toán Dijkstra tìm
đường đi ngắn nhất từ một đỉnh tới các đỉnh còn lại trong đồ thị định hướng, các thuật
toán Prim và Kruskal tìm cây bao trùm ngắn nhất trong đồ thị vô hướng.
procedure Greedy;
begin
X:= ;
i:=0;
while (Chua_xay_dung_het_cac_thanh_phan_cua_nghiem) do
begin
i:=i+1;
<Xac_dinh_Si>;
X Select(Si);
end;
end;
II. Một số bài toán được giải quyết bằng phương pháp tham lam:
1. Bài toán cái ba lô
a. Phát biểu bài toán
Có n vật, mỗi vật i, i∈{1,..,n} được đặc trưng bởi
trọng lượng wi và giá trị vi. Có một ba lô có khả năng
mang được 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ô sao cho ba lô
thu được có giá trị nhất.
Dữ liệu: Vào từ file BAG.INP có dạng:
- Dòng đầu tiên gồm n và m cách nhau ít nhất Bài toán cái ba lô
một dấu cách.
- n dòng tiếp theo, mỗi dòng thứ i cho biết trọng lượng và giá trị của vật thứ i.
Kết quả: Ghi vào file BAG.OUT có dạng:
- Dòng đầu tiên ghi giá trị tối ưu của bài toán.
5
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- n dòng tiếp theo, mỗi dòng cho biết số thứ tự của đồ vật và số lượng lấy của đồ
vật thứ i.
Ví dụ:
BAG.INP BAG.OUT
5 11 15
33 11
46 22
Test 1
54 30
9 10 40
42 50
4 37 83
15 30 10
Test 2 10 25 23
22 31
46 41
25 9
Test 3 35 11
24 21
- Bước 2: Sắp xếp các loại đồ vật theo đơn giá từ lớn đến nhỏ.
- Bước 3: Xét các đồ vật theo thứ tự đã sắp xếp, với mỗi đồ vật được xét sẽ lấy số
lượng tối đa mà trọng lượng còn lại của ba lô cho phép.
- Bước 4: Xác định trọng lượng còn lại của ba lô và quay lại bước 3 cho đến khi
xét hết các đồ vật hoặc trọng lượng còn lại của ba lô là 0.
Bước 1: Tính đơn giá cho các đồ vật, ta được bảng sau:
6
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bước 2: Sắp xếp lại các đồ vật theo thứ tự từ lớn đến bé, ta có bảng sau:
Bước 3 và 4:
+ Với m = 11, xét đồ vật 2, ta có thể lấy số lượng là 2. Khi đó m được tính lại là
m = 11 – 8 = 3.
+ Với m = 3, xét đồ vật 4, ta không thể lấy đồ vật 4 do vượt quá trọng lượng còn
lại của cái ba lô.
+ Với m = 3, xét đồ vật 1, ta có thể lấy số lượng là 1. Khi đó m được tính lại là m
= 3 – 3 = 0.
Vậy ta lấy được đồ vật 2, 1 với số lượng lần lượt tương ứng là 2, 1 với giá trị tối
ưu là 15.
Nhận xét:
Với thuật toán tham lam, trong một số trường hợp ta chỉ tìm được phương án đủ
“tốt” tiệm cận với phương án tối ưu. Điều đáng lưu tâm ở đây là một số bài toán tối ưu
tổ hợp có thuật toán tìm nghiệm chính xác đòi hỏi thời gian mũ, khi dữ liệu bài toán
lớn thì rất khó áp dụng. Trong trường hợp này ta sử dụng thuật toán tham lam cho
7
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
nghiệm đủ tốt mà thời gian thực hiện rất nhanh, đây cũng chính là lý do tại sao thuật
toán tham lam được mọi người quan tâm.
Với 3 bộ test đã cho, ta tìm được nghiệm tối ưu ở 2 test đầu, test thứ 3 không cho
được nghiệm tối ưu. Muốn tìm được nghiệm tối ưu, ta dùng phương pháp quy hoạch
động cho bài toán này.
Độ phức tạp thuật toán: Quá trình sắp xếp nếu làm theo QuickSort mất
O(nlog(n)), quá trình lặp n đồ vật mất O(n). Vậy tổng độ phức tạp của thuật toán là
O(nlog(n)).
- dòng tiếp theo, dòng thứ cho biết chứa hai số nguyên
( ).
Ví dụ:
AS.INP AS.OUT
5 3
79 351
Test 1
68
13
8
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
06
37
11 4
35 2 5 10 4
14
06
12 14
57
Test 2
38
2 13
6 10
59
8 11
8 12
Ý tưởng:
Trước tiên ta có nhận xét là trong các phương án tối ưu, chắc chắn có một
phương án mà nhiệm vụ đầu tiên được chọn là nhiệm vụ kết thúc sớm nhất.
Gọi i1 là nhiệm vụ kết thúc sớm nhất. Với một phương án tối ưu bất kỳ, giả sử
thứ tự các nhiệm vụ cần thực hiện trong phương án tối ưu đó là (j1, j2, …, jk). Do i1 là
nhiệm vụ kết thúc sớm nhất nên chắc chắn nó không thể kết thúc muộn hơn j1, vì vậy
việc thay j1 bởi i1 trong phương án này sẽ không gây ra sự xung đột nào về thời gian
thực hiện các nhiệm vụ. Sự thay thế này cũng không làm giảm bớt số lượng nhiệm vụ
thực hiện được trong phương án tối ưu, nên (i1, j2, …, jk) cũng sẽ là một phương án tối
ưu. Yêu cầu của bài toán là chỉ cần đưa ra một phương án tối ưu, vì thế ta sẽ chỉ ra
phương án tối ưu có nhiệm vụ đầu tiên là nhiệm vụ kết thúc sớm nhất trong số 𝑛
nhiệm vụ. Điều này có nghĩa là chúng ta không cần thử 𝑛 khả năng chọn nhiệm vụ đầu
tiên, đi giải các bài toán con, rồi mới đánh giá chúng để đưa ra quyết định cuối cùng.
Chúng ta sẽ đưa ngay ra quyết định tức thời: chọn ngay nhiệm vụ kết thúc sớm nhất i1
làm nhiệm vụ đầu tiên.
Sau khi chọn nhiệm vụ i1, bài toán lớn quy về bài toán con: Chọn nhiều nhiệm
vụ nhất trong số các nhiệm vụ được bắt đầu sau khi i1 kết thúc. Phép chọn tham lam lại
cho ta một quyết định tức thời: nhiệm vụ tiếp theo trong phương án tối ưu sẽ là nhiệm
vụ bắt đầu sau thời điểm f[i1] và có thời điểm kết thúc sớm nhất, gọi đó là nhiệm vụ i2.
9
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Và cứ như vậy chúng ta chọn tiếp các nhiệm vụ i3, i4…. Cứ như vậy cho tới khi không
còn nhiệm vụ nào chọn được nữa.
Đến đây ta có thể thiết kế một thuật toán tham lam như sau:
Sắp xếp các nhiệm vụ theo thứ tự không giảm của thời điểm kết thúc.
Khởi tạo thời điểm 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 ∶= 0.
Duyệt các nhiệm vụ theo danh sách đã sắp xếp (nhiệm vụ kết thúc sớm sẽ được
xét trước nhiệm vụ kết thúc muộn), nếu xét đến nhiệm vụ 𝑖 có [𝑖] ≥ 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 thì
chọn ngay nhiệm vụ 𝑖 vào phương án tối ưu và cập nhật 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 thành thời điểm
kết thúc nhiệm vụ 𝑖: 𝐹𝑖𝑛𝑖𝑠ℎ𝑇𝑖𝑚𝑒 ≔ 𝑓[𝑖].
Minh họa với dữ liệu test 1:
Công việc i 3 4 5 2 1
s[i] 1 0 3 6 7
f[i] 3 6 7 8 9
FinishTime = 0.
Với bảng trên, ta chọn công việc 3 trước, khi đó cập nhật lại FinishTime= 3.
Tiếp tục duyệt các công việc còn lại, công việc 4 sẽ không được chọn do
s[4]< FinishTime.
Nhận xét: Thuật toán tham lam cho bài toán này luôn cho ra phương án tối ưu.
10
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Độ phức tạp thuật toán: Dễ thấy rằng ở thủ tục GreedySelection được thực
hiện trong thời gian O(n). Thời gian mất chủ yếu nằm ở thuật toán sắp xếp các công
việc theo thời điểm kết thúc. Như đoạn code ở trên, ta dùng QuickSort, do đó độ phức
tạp thuật toán sẽ là O(nlog(n)).
Một máy ATM hiện có n (n < 20) tờ tiền có mệnh giá t1, t2,..., tn. Hãy tìm cách trả
ít tờ nhất với số tiền đúng bằng s.
- Dòng đầu tiên gồm n và s cách nhau ít nhất một dấu cách.
Kết quả: Ghi vào file ATM.OUT, nếu không có phương án thì ghi -1, ngược lại
thì:
Ví dụ:
ATM.INP ATM.OUT
10 390 5
Test 1
200 10 20 20 50 50 50 50 100 100 200 100 50 20 20
11 100 8
Test 2
50 20 20 20 20 20 2 2 2 2 2 50 20 20 2 2 2 2 2
6 100 -1
Test 3
50 20 20 20 20 20
Ý tưởng: Như đã phân tích ở ví dụ 1.2, ta thiết kế thuật toán tham lam như sau:
tại mỗi bước ta sẽ chọn tờ tiền lớn nhất còn lại không vượt quá lượng tiền còn phải trả,
cụ thể:
Bước 1: Sắp xếp các tờ tiền giảm dần theo giá trị.
11
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bước 2: Lần lượt xét các tờ tiền từ giá trị lớn đến giá trị nhỏ, nếu vẫn còn chưa
lấy đủ s và tờ tiền đang xét có giá trị nhỏ hơn hoặc bằng s thì lấy luôn tờ tiền đó.
Bước 1: Sau khi sắp xếp các tờ tiền giảm dần theo giá trị, ta có:
Bước 2: Với tiền cần rút là 390, ta chọn được 2 phần tử đầu tiên trong dãy, khi đó
số tiền cần rút còn lại là 90. Do đó ta không chọn tờ 100, mà sẽ xét tiếp phần tử tiếp
theo. Ta chọn được một tờ 50, tiền cần rút còn lại là 40. Vì vậy ta không chọn được
các tờ 50. Xét tiếp ta chọn được hai tờ 20, khi đó đã đủ số tiền cần rút, dừng thuật
toán. Kết quả là ta nhận được là 200 100 50 20 20.
Nhận xét:
Với bộ test 1, thuật toán tham lam cũng cho được nghiệm tối ưu. Tuy nhiên với
bộ test 2, thuật toán tham lam không cho nghiệm tối ưu và bộ test 3 thì thuật toán tham
lam không tìm được nghiệm mặc dù có nghiệm.
Độ phức tạp thuật toán: Thuật toán tham lam qua một lần duyệt, có độ phức tạp
là O(n). Toàn bộ quá trình mất thời gian nhiều nhất là ở quá trình sắp xếp, ở đây ta sắp
xếp theo phương pháp chọn, có độ phức tạp là O(n2). Vậy độ phức tạp của thuật toán
là O(n2).
Người ta cần ghi n bài hát, được mã số từ 1 đến n, vào một băng nhạc có thời
lượng tính theo phút đủ chứa toàn bộ các bài hát đã cho. Với mỗi bài hát ta biết thời
lượng phát của mỗi bài hát đó. Băng sẽ được lắp vào một máy phát nhạc đặt trong một
siêu thị. Khách hàng muốn nghe bài hát nào chỉ cần nhấn phím tương ứng với bài hát
đó. Để tìm và phát bài hát thứ i trên băng, máy xuất phát từ đầu cuộn băng, quay băng
để bỏ qua i-1 bài ghi trước đó. Thời gian quay băng bỏ qua mỗi bài hát và thời gian
phát bài hát đó được tính là như nhau. Tính trung bình, các bài hát trong một ngày
được khách hàng lựa chọn với số lần (tần suất) là như nhau. Hãy tìm cách ghi các bài
trên băng sao cho tổng thời gian quay băng trong mỗi ngày là ít nhất.
12
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Dòng thứ 2 gồm n số nguyên dương thể hiện dung lượng tính theo phút của mỗi
bài, mỗi số cách nhau một dấu cách.
- n dòng đầu tiên thể hiện trật tự ghi bài hát trên băng, mỗi dòng gồm hai số
nguyên dương j và d cách nhau bởi một dấu cách, trong đó j là số thứ tự của bài hát
cần ghi, d là thời gian tìm và phát bài đó theo trật tự này.
- Dòng thứ n+1 ghi tổng số thời gian quay băng nếu mỗi bài hát được phát một
lần trong ngày.
Ví dụ:
CASSETTE.INP CASSETTE.OUT
3 22
723 35
1 12
19
Ý tưởng:
Mã số bài hát 1 2 3
Thời gian phát 7 2 3
Ta xét vài tình huống ghi băng để rút ra kết luận cần thiết:
Vậy phương án tối ưu sẽ là (2,3,1): ghi bài 2 rồi đến bài 3, cuối cùng ghi bài 1.
Tổng thời gian theo phương án này là 19 phút.
Để có phương án tối ưu, ta chỉ cần ghi băng theo trật tự tăng dần của thời lượng.
Bài toán được cho với giả thiết băng đủ lớn để ghi được toàn bộ các bài [3].
Rất đơn giản, ta chỉ cần sắp xếp theo thứ tự không giảm thời gian phát của từng
bài nhạc. Với dữ liệu đã cho ta sẽ có thứ tự là 2, 3, 1 và thời gian phát toàn bộ sẽ được
tính là 2 + (2 + 3) + (2 + 3 + 7) = 19 phút.
Nhận xét:
Thuật toán tham lam trong trường hợp này là các thời gian phát của mỗi bài nhạc
được tính theo trật tự tăng dần. Lời giải tìm được sẽ là lời giải tối ưu.
Độ phức tạp thuật toán: Thuật toán tham lam qua một lần duyệt, có độ phức tạp
là O(n). Toàn bộ quá trình mất thời gian nhiều nhất là ở quá trình sắp xếp, ở đây ta sắp
xếp theo QuickSort, có độ phức tạp là O(nlog(n)). Vậy độ phức tạp của thuật toán là
O(nlog(n)).
Yêu cầu: Cho tọa độ của n điểm đen a1, a2, …, an và tọa độ của điểm trắng b1, b2,
…, bn. Hãy tìm giá trị k lớn nhất thỏa mãn yêu cầu trên [7].
- Dòng thứ hai chứa các số a1, a2, …, an (|ai| <= 109, i = 1, 2,…, n).
14
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Dòng thứ ba chứa các số b1, b2, …, bn (|bi| <= 109, i = 1, 2,…, n).
Các số trên cùng một dòng được ghi cách nhau ít nhất một dấu cách.
Kết quả: Ghi ra một số nguyên duy nhất là số k lớn nhất tìm được.
Ví dụ:
BAW.INP BAW.OUT
3 2
031
-3 5 -1
Hình 0.2. Minh họa dữ liệu bài toán nối điểm đen trắng
Ý tưởng:
Trộn hai màu lại thành một danh sách trên trục tọa độ và sắp 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, nếu thấy hai cặp liền
nhau khác màu thì tăng biến đếm lên 1, bởi vì nhận thấy rằng cần chọn các cặp điểm
kề nhau khác màu trên trục này thì sẽ được nhiều cặp điểm không giao nhau. Ta cũng
chú ý là tăng bước nhảy để tránh trường hợp 1 điểm đen (trắng) kẹp giữa 2 điểm trắng
(đen) thì tính thành 2 đoạn vì ở đây đề bài nói là các điểm đen trắng phân biệt.
Khởi tạo giá trị tất cả các phần tử của mảng color là 0 và res là 0.
15
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Thêm tất cả các giá trị của mảng b thêm sau mảng a, đồng thời ở các vị trí thêm
đó ở mảng color ta đánh dấu là 1 (để phân biệt với các phần tử của mảng a). Ta có
bảng sau:
Biến 1 2 3 4 5 6
a 0 3 1 -3 5 -1
color 0 0 0 1 1 1
Sắp xếp lại mảng a theo thứ tự tăng dần, khi đổi chỗ hai phần tử mảng a ta cũng
đồng thời đổi luôn ở mảng color. Ta có dữ liệu như sau sau khi sắp xếp lại:
Biến 1 2 3 4 5 6
a -3 -1 0 1 3 5
color 1 1 0 0 0 1
Ta duyệt trong mảng color từ trái sang phải, nếu thấy hai cặp liền nhau khác giá
trị thì tăng biến res lên 1. Sau khi duyệt, biến res = 2 là kết quả tối ưu của bài toán.
Nhận xét: Thuật toán tham lam thể hiện ở chỗ sau khi sắp xếp dữ liệu, ta chỉ
cần chọn các cặp điểm liền kề nhau khác màu trên trục là sẽ ra được kết quả tối ưu.
Độ phức tạp thuật toán: Thuật toán tham lam qua một lần duyệt n phần tử có
O(n), sắp xếp dùng QuickSort có O(nlog(n)). Vậy độ phức tạp của thuật toán là
O(nlog(n)).
16
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
17
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Cách 2 thì không như vậy. Trước hết nó tính sẵn F[1] và F[2], từ đó tính tiếp
F[3], lại tính tiếp được F[4], F[5], F[6]. Ðảm bào rằng mỗi giá trị Fibonnaci chỉ phải
tính 1 lần.
(Cách 2 còn có thể cải tiến thêm nữa, chỉ cần dùng 3 biến tính lại giá trị lẫn
nhau).
Trước khi áp dụng phương pháp quy hoạch động ta phải xét xem phương pháp
đó có thỏa mãn những yêu cầu dưới đây hay không:
18
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
+ Bài toán lớn phải phân rã được thành nhiều bài toán con, mà sự phối hợp lời
giải của các bài toán con đó cho ta lời giải của bài toán lớn.
+ Vì quy hoạch động là đi giải tất cả các bài toán con, nên nếu không đủ không
gian vật lý lưu trữ lời giải (bộ nhớ, đĩa,…) để phối hợp chúng thì phương pháp quy
hoạch động cũng không thể thực hiện được.
+ Quá trình từ bài toán cơ sở tìm ra lời giải bà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 là bài toán quy hoạch
động.
+ Công thức phối hợp nghiệm của các bài toán con để có nghiệm của bài toán
lớn gọi là công thức truy hồi của quy hoạch động.
+ Tập các bài toán nhỏ nhất có ngay lời giải để từ đó giải quyết các bài toán
lớn hơn gọi là cơ sở quy hoạch động.
+ Không gian lưu trữ lời giải các bài toán con để tìm cách phối hợp chúng gọi
là bảng phương án của quy hoạch động.
3) Các bước cài đặt một chương trình sử dụng quy hoạch động:
+ 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 phương án để tìm lời giải của những bài toán lớn hơn và lưu chúng vào
bảng phương án. Cho tới khi bài toán ban đầu tìm được lời giải.
+ Dựa vào bảng phương án, truy vết tìm ra nghiệm tối ưu.
Cho đến nay, vẫn chưa có một định lý nào cho biết một cách chính xác những bài
toán nào có thể giải quyết hiệu quả bằng quy hoạch động. Tuy nhiên để biết được bài
toán có thể giải bằng quy hoạch động hay không, ta có thể tự đặt câu hỏi : “Một
nghiệm tối ưu của bài toán lớn có phải là sự phối hợp các nghiệm tối ưu của các
bài toán con hay không?” và “Liệu có thể nào lưu trữ được nghiệm các bài toán
con dưới một hình thức nào đó để phối hợp tìm được nghiệm bài toán lớn”.
19
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Cuối cùng trước khi khảo sát một số bài toán quy hoạch động, ta nhắc lại:
Phương pháp tốt nhất để giải quyết mọi bài toán trong tin học là biết sử dụng và phối
hợp uyển chuyển nhiều thuật toán, không được lạm dụng hay coi thường bất cứ một
phương pháp nào.
II- MỘT SỐ BÀI TOÁN VÍ DỤ ĐIỂN HÌNH:
1) Bài 1: Trước tiên chúng ta hãy xét 1 bài toán thật đơn giản và quen thuộc đó là
tìm giá trị lớn nhất trong n số là a1, a2, ..., an. Giải quyết bài toán này, ta sẽ xây dựng
các cấu hình con tối ưu bằng cách lần lượt tìm số lớn nhất trong k số đầu tiên với k
chạy từ 1 đế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ư vậy khi k đạt tới n thì maxn chính là giá trị lớn nhất trong n số đã chọ Việc
cài đặt chương trình hết sức đơn giản như 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 do
begin
write('a[',i,']= ');
readln(a[i]);
end;
Max:=a[1];
For k:=2 to n do
If a[k]>max then max:=a[k];
Write('Gia tri lon nhat cua day cac so da cho la: ',max);
Readln
End.
20
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bây giờ chúng ta xét đến bài toán 2 có phần hấp dẫn hơn. Đây chính là một trong
những bài toán kinh điển cho giải thuật qui hoạch động:
2) Bài 2: BÀI TOÁN XẾP BA LÔ (một số sách ghi là bài toán cái túi, tương tự
như bài toán xếp vali) là một bài toán tối ưu hóa tổ hợp. Bài toán được đặt tên từ vấn
đề chọn những gì quan trọng có thể nhét vừa vào trong một cái túi (với giới hạn khối
lượng) để mang theo trong một chuyến đi.
Một số cách phát biểu nội dung bài toán:
Một kẻ trộm đột nhập vào một cửa hiệu tìm thấy
có n mặt hàng có trọng lượng và giá trị khác nhau, nhưng
hắn chỉ mang theo một cái túi có sức chứa về trọng lượng tối
đa là M. Vậy kẻ trộm nên bỏ vào ba lô những món nào và số
lượng bao nhiêu để đạt giá trị cao nhất trong khả năng mà
hắn có thể mang đi được.
Một hành khách chỉ được mang theo một vali có khối
lượng hàng hoá tối đa là M. Hành khách ðó ðã chuẩn bị ra N dồ vật được ðánh số từ 1
ðến N ðể chuẩn bị mang theo. Ðồ vật thứ i có trọng lượng là ai và giá trị sử dụng là
ci (i = 1, 2, .. N) . Yêu cầu: Chỉ ra ðồ vật mà hành khách ðó cần mang theo sao cho
tổng giá trị sử dụng là lớn nhất?
* Bài toán:
Trong siêu thị có n gói hàng (n ≤ 100), gói hàng thứ i có trọng lượng là 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 một cái túi
có thể mang được tối đa trọng lượng M ( M ≤ 100). Hỏi tên trộm sẽ lấy đi những gói
hàng nào để được tổng giá trị lớn nhất.
Input: file văn bản BAG.INP
• Dòng 1: Chứa hai số n, M cách nhau ít nhất một 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 nhau ít nhất
một dấu cách
Output: file văn bản BAG.OUT
• Dòng 1: Ghi giá trị lớn nhất tên trộm có thể lấy
• Dòng 2: Ghi chỉ số những gói bị lấy
21
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
BAG.INP BAG.OUT
5 11 11
3 3 521
4 4
5 4
9 10
44
22
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bảng phương án F gồm n + 1 dòng, M + 1 cột, trước tiên được điền cơ sở quy
hoạch động: Dòng 0 gồm toàn số 0. Sử dụng công thức truy hồi, dùng dòng 0 tính
dòng 1, dùng dòng 1 tính dòng 2, v.v… đến khi tính hết dòng n.
Truy vết:
Tính xong bảng phương án thì ta quan tâm đến F[n, M] đó chính là giá trị lớn
nhất thu được khi chọn trong cả n gói với giới hạn trọng lượng M. Nếu F[n, M] = F[n -
1, M] thì tức là không chọn gói thứ n, ta truy tiếp F[n - 1, M]. Còn nếu F[n, M] ≠ F[n -
1, M] thì ta thông báo rằng phép chọn tối ưu có chọn gói thứ n và truy tiếp F[n - 1, M -
Wn]. Cứ tiếp tục cho tới khi truy lên tới hàng 0 của bảng phương án
Chương trình:
const max = 100;
var
w, v: array[1..max] of integer;
f: array[0..max, 0..max] of integer;
n, m: integer;
procedure enter;
var i: integer;
begin
readln(n, m);
for i := 1 to n do readln(w[i], v[i]);
end;
procedure optimize; {tinh bang phuong an}
var i, j: integer;
begin
fillchar(f[0], sizeof(f[0]), 0); {dien co so qhd}
for i := 1 to n do
23
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
for j := 0 to m do
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 ra gia tri lon nhat co the tim duoc}
while n <> 0 do {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;
close(input); close(output);
End.
24
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bài toán: Cho dãy số nguyên A = a1, a2, …, an. (n ≤ 10000, -10000 ≤ ai ≤
10000). Một dãy con của A là một cách chọn ra trong A một số phần tử giữ nguyên
thứ tự. Như vậy A có 2n dãy con.
Yêu cầu: Tìm dãy con đơn điệu tăng của A có độ dài lớn nhất.
Ví dụ: A = (1, 2, 3, 4, 9, 10, 5, 6, 7, 8). Dãy con đơn điệu tăng dài nhất là: (1, 2,
3, 4, 5, 6, 7, 8).
* Dữ liệu (Input) vào từ file văn bản INCSEQ.INP
• Dòng 1: Chứa số n
• Dòng 2: Chứa n số a1, a2, …, an cách nhau ít nhất một dấu cách
* Kết quả (Output) ghi ra file văn bản INCSEQ.OUT
• Dòng 1: Ghi độ dài dãy con tìm được
• Các dòng tiếp: ghi dãy con tìm được và chỉ số những phần tử được chọn vào
dãy con đó.
INCSEQ.INP INCSEQ.OUT
11 8
1 2 3 8 9 4 5 6 a[1] = 1
20 9 10 a[2] = 2
a[3] = 3
a[6] = 4
a[7] = 5
a[8] = 6
a[10] = 9
a[11] = 10
25
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Với mọi i: 0 ≤ i ≤ n + 1. Ta sẽ tính L[i] = độ dài dãy con đơn điệu tăng dài nhất
bắt đầu tại ai.
Cơ sở quy hoạch ðộng (bài toán nhỏ nhất):
L[n+1] = Độ dài dãy con đơn điệu tăng dài nhất bắt đầu tại an+1 = +∞. Dãy con
này chỉ gồm mỗi một 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 con tăng
dài nhất bắt đầu tại ai. L[i] được tính trong điều kiện L[i + 1], L[i + 2], …, L[n + 1] đã
biết:
Dãy con đơn điệu tăng dài nhất bắt đầu từ ai sẽ được thành lập bằng cách lấy ai
ghép vào đầu một trong số những dãy con đơn điệu tăng dài nhất bắt đầu tại vị trí aj
đứng sau ai. Ta sẽ chọn dãy nào để ghép ai vào đầu? Tất nhiên là chỉ được ghép ai vào
đầu những dãy con bắt đầu tại aj nào đó lớn hơn ai (để đảm bảo tính tăng) và dĩ nhiên
ta sẽ chọn dãy dài nhất để ghép ai vào đầu (để đảm bảo tính dài nhất). Vậy L[i] được
tính như sau: Xét tất cả các chỉ số j trong khoảng từ i + 1 ðến n + 1 mà aj >ai, chọn
ra chỉ số jmax có L[jmax] lớn nhất. Ðặt L[i] := L[jmax] + 1.
Truy vết: Tại bước xây dựng dãy L, mỗi khi tính L[i] = L[jmax] + 1, ta đặt T[i]
= jmax. Để lưu lại rằng: Dãy con dài nhất bắt đầu tại ai sẽ có phần tử thứ hai kế tiếp là
ajmax.
Sau khi tính xong hay dãy L và T, ta bắt đầu từ 0.
T[0] là phần tử đầu tiên được chọn,
T[T[0]] là phần tử thứ hai được chọn,
T[T[T[0]]] là phần tử thứ ba được chọn …Quá trình truy vết có thể diễn tả như
sau:
Ví dụ: với A = (5, 2, 3, 4, 9, 10, 5, 6, 7, 8). Hai dãy L và T sau khi tính sẽ là:
Chương trình:
program LongestSubSequence;
const max = 10000;
26
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 := 1 to n do Read(a[i]);
end;
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 TOÁN TÌM XÂU CON CHUNG DÀI NHẤT
Bài toán: Cho hai xâu X =x1x2…xm và Y=y1y2…yn. Tìm xâu Z = z1z2…zk là xâu
con chung dài nhất của X và Y. Một xâu con của xâu A là một cách chọn trong A một
số phần tử giữ nguyên thứ tự.
* Ý tưởng giải thuật:
Bảng phương án:
Ta sẽ dùng mảng hai chiều B[0..m, 0..n] làm bảng phương án, trong đó B[i,j] là
độ dài xâu chung dài nhất của các xâu con Xi (phần đầu của X) và Yj (phần đầu của
Y).
Cơ sở: Rõ ràng nếu ít nhất một trong hai xâu X hoặc Y là xâu rỗng (j=0 hoặc
i=0) thì B[i,j]=0.
Công thức truy hồi: Dễ dàng có các nhận xét sau:
- Nếu i,j > 0 và xi=yi thì B[i,j] = B[i-1,j-1] + 1
- Nếu i,j > 0 và xi<>yj thì B[i,j] = max ( B[i,j-1], B[i-1,j] )
Truy vết :
Như vậy B[n,m] cho biết độ dài của xâu con chung dài nhất. Để chi ra tường
minh xâu con chung dài nhất ta cần xây dựng bảng T[1..m, 1..n] để ghi nhận truy vết
đánh dấu B[i,j] được tính từ B[i-1,j-1] hay B[i,j-1] hay B[i-1,j].
Chương trình:
const fi='xaucon.inp';
fo='xaucon.out';
var s1,s2,s:string;
28
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do b[i][0]:=0;
for j:=0 to n do b[0][j]:=0;
for i:=1 to m do
for j:=1 to n do
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
29
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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) do
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
assign(f,fo);
rewrite(f);
writeln(f,len);
write(f,s);
close(f);
end;
Begin
30
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 được gọi là đối xứng (palindrome) nếu như khi đọc xâu này
từ phải sang trái cũng thu được xâu ban đầu.
Yêu cầu: tìm một xâu con đối xứng dài nhất của một xâu s cho trước. Xâu con là
xâu thu được khi xóa đi một số ký tự từ xâu ban đầu.
* Dữ liệu vào: Gồm một dòng duy nhất chứa xâu S, chỉ gồm những chữ cái in
thường.
* Kết quả: Gồm một dòng duy nhất là một xâu con đối xứng dài nhất của xâu S.
Nếu có nhiều kết quả, chỉ cần in ra một kết quả bất kỳ.
Giới hạn: Xâu S có độ dài không vượt quá 2000.
Ví dụ: Dữ liệu vào: lmevxeyzl Kết quả: level
2) Ý tưởng giải thuật:
Ta sẽ chuyển bài toán về một bài toán quy hoạch động cơ bản là: Bài toán tìm
xâu con chung dài nhất.
Với dữ liệu vào là S1.
Ta tạo xâu S2 là xâu ngược của S1bằng cách chép các phần tử của xâu S1 vào
xâu S2 theo thứ tự ngược lại.
Sau đó ta sẽ tìm xâu con chung dài nhất của S1 và S2.
Ta chỉ cần tìm xâu con chung dài nhất của một phần của S1 và nghịch đảo phần
còn lại, tức là ta chỉ xét một phần của bảng phương án với i+j<=chiều dài của S1.
Ví dụ:
S1 = lmevxeyzl
Ta có bảng phương án (hình bên)
31
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 1 do s2:=s2+s1[i];
n:=length(s2);
end;
procedure optimize;
32
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
var i,j:integer;
begin
for i:=0 to m do b[i][0]:=0;
for j:=0 to n do b[0][j]:=0;
for i:=1 to m do
for j:=1 to n do
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) do
begin
if t[m,n]=0 then
begin
s:=s1[m]+s;
33
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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
1) Bài toán: Cho một bảng A kích thýớc m x n, trên ðó ghi các số nguyên. Một
người xuất phát tại ô nào ðó của cột 1, cần sang cột n (tại ô nào cũng ðược). Quy tắc:
Từ ô A[i, j] chỉ ðược quyền sang một trong 3 ô A[i, j + 1]; A[i - 1, j + 1]; A[i + 1, j +
1]. Hãy tìm vị trí ô xuất phát và hành trình ði từ cột 1 sang cột n sao cho tổng các số
ghi trên ðường ði là lớn nhất (nhỏ nhất).
34
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Với những ô (i, j) ở các cột khác. Vì chỉ những ô (i, j – 1), (i – 1, j – 1), (i + 1, j –
1) là có thể sang được ô (i, j), và khi sang ô (i, j) thì số điểm được cộng thêm A[i, j]
nữa. Chúng ta cần B[i, j] là số điểm lớn nhất có thể 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 này tính tất cả
các B[i, j]. Cuối cùng chọn ra B[i, n] là phần tử lớn nhất trên cột n của bảng B và từ đó
truy vết tìm ra đường đi nhiều điểm nhất.
* Chương trình:
const max = 2000000000;
35
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
begin
for j:=1 to n do 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 do
begin
b[0,j]:=max;
b[m+1,j]:=max;
end;
for i:=1 to m do b[i,1]:=a[i,1];
36
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
for j:=2 to n do
for i:=1 to m do
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 do
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;
truyvet(dongcuoi,n);
37
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 cái kẹo. Không được bóc bất kỳ một gói kẹo nào,
cần chia N gói kẹo thành hai phần sao cho độ chênh lệch số kẹo giữa hai gói là ít nhất.
Dữ liệu vào trong file “chiakeo.inp” có dạng :
Dòng đầu tiên là số N (N<=100);
Dòng thứ hai là N số Ai (i=1, 2,.., N; Ai <=100).
Kết quả ra file “chiakeo.out” có dạng:
Dòng đầu là độ chênh lệch nhỏ nhất giữa hai phần có thể được.
Dòng hai là một dãy N số, nếu si =1 thì gói thứ i thuộc phần 1, nếu si =2 thì gói
thứ i thuộc phần 2.
* Lời giải:
- Với một số M bất kì, nếu ta biết được có tồn tại một cách chọn các gói kẹo để
tổng số kẹo của các gói được chọn bằng đúng M không, thì bài toán được giải sẽ
quyết. Vì đơn giản là ta chỉ cần chọn số M sao cho M gần với S/2 nhất (với i
=1,2,..,N). Sau đó xếp các gói kẹo để tổng bằng M vào phần một, phần thứ hai sẽ gồm
các gói kẹo còn lại.
- Để kiểm tra được điều trên ta sẽ xây dựng tất cả các tổng có thể có của N gói
kẹo bằng cách: ban đầu chưa có tổng nào được sinh ra.
- Làm lần lượt với các gói kẹo từ 1 đến N, với gói kẹo thứ i, ta kiểm tra xem hiện
tại có các tổng nào đã được sinh ra, giả sử các tổng đó là x1, x2,.., xt vậy thì đến bước
này sẽ có thể sinh ra các tổng x1, x2,.., xt và Ai và x1+Ai,x2+Ai,..,xt+Ai.
- Với N gói kẹo, mà mỗi gói có không quá 100 cái kẹo vậy tổng số kẹo không
vượt quá N*100 ≤ 10000 cái kẹo. Dùng mảng đánh dấu D, nếu có thể sinh được ra
tổng bằng k thì D[k] = 1 ngược lại D[k] = 0.
* Chương trình:
const max = 100;
fi = 'chiakeo.inp';
fo = 'chiakeo.out';
38
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
dec(m);
assign(f,fo); rewrite(f);
writeln(f,sum-2*m);
while tr[m]>0 do
begin
s[tr[m]]:=1;
m:=m-a[tr[m]];
end;
for k:=1 to n do
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 một dãy số nguyên A1, A2,.., AN và một số k. Hãy tìm một dãy con (không
nhất thiết phải liên tiếp nhau) dài nhất có tổng các số chia hết cho số k.
Dữ liệu vào trong file “dayso.inp” có dạng:
40
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
p:=0;
for i:=1 to n do
begin
read(f,x);
x:=x mod k+k;
p:=(p+x)mod k;
for j:=0 to k-1 do
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 VUÔNG 0 1
Cho một bảng kích thước MxN, được chia thành lưới ô vuông đơn vị M dòng N
cột ( 1 <= M, N <= 1000 ). Trên các ô của bảng ghi số 0 hoặc 1. Các dòng của bảng
được đánh số 1, 2… M theo thứ tự từ trên xuống dưới và các cột của bảng được đánh
số 1, 2…, N theo thứ tự từ trái qua phải
42
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Yêu cầu: Hãy tìm một hình vuông gồm các ô của bảng thoả mãn các điều kiện
sau:
1 – Hình vuông là đồng nhất: tức là các ô thuộc hình vuông đó phải ghi các số
giống nhau (0 hoặc 1)
2 – Cạnh hình vuông song song với cạnh bảng.
3 – Kích thước hình vuông là lớn nhất có thể
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 là số ghi trên ô (i, j) của bảng
Output: Gồm 1 dòng duy nhất ghi kích thước cạnh của hình vuông tìm được
Example
Input:
11 13
0000010000000
0000111000000
0011111110000
0011111110000
0111111111000
1111111111100
0111111111000
0011111110000
0011111110000
0000111000011
0000010000011
Output: 7
* Lời giải: Đây là một bài quy hoạch động hay.
- Gọi f[i,j] là kích thước của hình vuông có góc đáy là ô [i,j].
- Vậy thì nếu bốn ô: bên trái, bên phải, chéo trên đều cùng giá trị với ô này thì nó
sẽ tạo được một ô vuông, nếu không thì xem như ô đó có kích thước chỉ là 1 vì chỉ tự
nó tạo được một ô vuông
43
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Bây giờ ta xét đến công thức quy hoạch động đối với ô đang xét ở trên. Đương
nhiên ta chỉ có thể lấy giá trị nhỏ nhất trong các f của các ô ở trên để chắc 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]) + 1
-Cơ sở quy hoạch động của bài toán chính là các ô cạnh trên và bên trái có f = 1
vì các ô có góc dưới của nó chính là chính nó và kích thước là 1.
* 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 0..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 := 1 to m do
for j := 1 to n do
read(a[i,j]);
for j := 0 to n+1 do f[1,j] := 1;
for i := 0 to m+1 do f[i,1] := 1;
for i := 1 to m do
for j := 1 to n do
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
f[i,j] := min(f[i-1,j],min(f[i,j-1],
44
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
f[i-1,j-1])) + 1
else
f[i,j] := 1;
maxf := 1;
for i := 1 to m do
for j := 1 to n do
maxf := max(maxf,f[i,j]);
writeln(maxf);
close(input);
close(output);
end.
mảng L[i, j] mất N2. Tổng cộng là O(N2) vì mỗi lần kiểm tra chỉ mất O(1). Có thể cải
tiến bằng cách dùng hai mảng một chiều L[i] và C[i] có ý nghĩa:
- L[i] là độ dài lớn nhất của palindrome độ dài lẻ nhận s[i] làm tâm;
- C[i] là độ dài lớn nhất của palindrome độ dài chẵn nhận s[i] và s[i+1] làm
tâm; L[i] và C[i] có thể tính được bằng cách 2 bài Xâu con liên tiếp đối xứng
dài nhất trong O(N2). Phần kiểm tra ta viết lại như 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 toán của chúng ta có độ phức tạp tính toán là O(N2), chi phí bộ nhớ là O(N).
1. Sách giáo khoa Chuyên Tin quyển 1 – NXB Giáo dục – Chủ biên: Hồ Sĩ Đàm
2. Toán rời rạc – Nguyễn Đức Nghĩa, Nguyễn Tô Thành. NXB ĐHQG Hà Nội
3. Thuật toán thông dụng (Dành cho bồi dưỡng HSG Tin học) – Trần Đỗ Hùng.
4. Một số vấn đề chọn lọc trong tin học – Tập 1 – Nguyễn Xuân My
5. Sáng tạo trong thuật toán và lập trình – Nguyễn Xuân Huy.
46
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 TOÁN
HÌNH HỌC
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 của điểm C so với Vector (phương pháp
vector)
a. Cơ sở toán học
47
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Xét 3 điểm A, B, C trong mặt phẳng tọa độ, vị trí tương đối C so với A, B có 3
khả năng xãy ra:
B C C B C
B
A A A
b. Thuật toán
48
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
2. Vận dụng giải quyết 1 số bài toán cơ bản trong chuyên đề hình học
2.1 Một số bài toán liên quan giữa điểm, đoạn thẳng và đường thẳng
+ Xác định điểm M có thuộc đoạn thẳng nối 2 điểm A,B
Điểm M thuộc đoạn thẳng AB nếu M nằm trên đường thẳng đi qua 2 điểm AB
và tọa của nó khi chiếu các trục tọa độ thì nó thuộc tọa độ chiếu xuống các trục tọa độ
của điểm A và B.
+ Xác định vị trí tương đối của 2 điểm M,N so với đường thẳng đi qua 2 điểm A,
B.
- 2 điểm M,N nằm cùng phía hoặc khác phía so với đường thẳng đi qua 2 điểm A,B.
Nếu 1 trong 2 điểm M hoặc N thuộc đường thẳng trên thì ta xem như M và N nằm
cùng phía với nhau.
a. Cách 1
2 đoạn thẳng giao nhau nếu thỏa điều kiện:
- Hai đường thẳng qua 2 điểm đó phải cắt nhau tại I
- Và I thuộc 2 đoạn thẳng
b. Phương pháp vector
49
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
* Định nghĩa
Một đa giác lồi được gọi là lồi nếu ta vẽ một đường thẳng đi qua 1 cạnh của đa
giác thì các điểm còn lại luôn nằm cùng 1 phía so với đường thẳng đó.
xét theo 1 chiều nào đó (cùng chiều hoặc ngược chiều kim đồng hồ), xét với
mọi 3 điểm liên tiếp nhau thì chiều của đỉnh thứ 3 luôn nằm cùng một phía nhau (trái
hoặc phải) so với 2 đỉnh trước đó.
Function Convex (P:poly;n:index):boolean;
Vari:index;
Begin
If n<3 then exit(false);
Dir:=CCW(p[1],p[2],p[3]);
50
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
For i:=3 to n do
If CCW(p[i-1],p[i],p[i+1])<>Dir then exit(false);
Convex:=true;
End;
c. Đánh giá
- Chi phí thuật toán theo cách 1 là 0(n2) trong khi đó chi phí theo phương
pháp vector là 0(n).
+ Bài toán vị trí tương đối giữa 1 điểm so với đa giác lồi
Điểm thuộc đa giác nếu điểm nằm trên các cạnh hoặc 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 và cắt trục vẽ bên trên. Ta nhận xét
nếu số giao điểm với đa giác là số lẽ thì điểm thuộc đa giác. Còn ngược lại
điểm nằm ngoài đa giác.
- Các trường hợp cắt sau ta chỉ tính cắt tại 1 giao điểm:
P[i-1] P[i]
P[i]
M N
P[i] P[i+1] N
M N
P[i+1] P[i+2] M
P[i+1]
P[i+2]
Begin
If Inside(M,P[1], P[2]) then exit(true);
Dir:=CCW(P[1],P[2],M);
For i:=2 to n do
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 bài toán trong cách 1 khó hơn cài đặt hơn bằng phương pháp vector. Hơn
nữa còn phải giải quyết vấn đề về số thực trong việc cài đặt do phải xác định 1 điểm có
thuộc đoạn thẳng.
52
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
o Nếu điểm được chọn không tạo thành đa giác lồi với các điểm đã chọn
thì lần lượt bỏ các đỉnh đã chọn cho tới khi lập thành đa giác lồi thì thôi
o Bỏ điểm vào bao lồi
Thuật toán bọc gói đòi hỏi một chi phí là O(M*N) (trong đó M là số điểm trên
bao). Vì vậy nó chỉ làm việc tốt trong trường hợp số điểm nằm trên bao nhỏ hơn nhiều
53
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
so với tổng số. Nhưng trong trường hợp xấu nhất (tất cả mọi điểm đều nằm trên bao)
thì chi phí thuật toán sẽ lên tới O(N2).
Chi phí cho thủ tục trên tỷ lệ thuận với N. Đúng vậy, mặc dù trong vòng lặp có
một vòng lặp, nhưng ta để ý là không điểm nào bị loại quá một lần nên vòng lặp này
chỉ hoạt động không đến N lần.Như vậy chi phí cho thuật toán này là O(NlogN) nếu ta
dùng phương pháp sắp xếp tốt (như Quick Sort chẳng hạn).
54
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Thuật toán tìm đường đi ngắn nhất giữa mọi cặp đỉnh của đồ thị G(V,E) (có
hướng, có trọng số và không có chu trình âm). Áp dụng quy hoạch động để giải bài
toán này trong thời gian O(n3) và bộ nhớ O(n2). Một vài cách phát triển thuật toán quy
hoạch động khác nhau, mỗi cách hi vọng sẽ mang đến một cách nhìn khác về bài toán.
Cách phát biểu cuối cùng sẽ đưa chúng ta đến thuật toán nổi tiếng Floyd-Warshall. Tài
liệu tham khảo chính là notes của 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 bài toán đường đi ngắn nhất từ một đỉnh tới
mọi đỉnh từ thuật toán Bellman-Ford.
Ghi chú: Đồ thị G trong bài này sẽ được biểu diễn bằng ma trận kề W, i.e,
W[u,v]=w(u→v) nếu tồn tại một 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ố và không có chu trình âm G(V,E), tìm
khoảng cách ngắn nhất giữa mọi cặp đỉnh của đồ thị.
Nếu trọng số của đồ thị G là không âm, ta có thể áp dụng thuật toán Dijkstra V
lần, mỗi lần áp dụng cho một đỉnh của đồ thị. Do đó, ta có:
Định lý: Ta có thể tìm được khoảng cách ngắn nhất giữa mọi cặp đỉnh trong đồ thị
không có trọng số âm trong thời gian O(V2logV) sử dụng O(V2) bộ nhớ.
Tuy nhiên trong đồ thị có trọng số âm, bài toán trở nên phức tạp hơn nhiều.
Như ta đã biết, thuật toán Bellman-Ford có thể tìm đường đi ngắn nhất từ một đỉnh tới
mọi đỉnh khác trong đồ thị trong thời gian O(VE) và bộ nhớ O(V). Do đó, nếu áp dụng
thuật toán Bellman-Ford cho mọi đỉnh của đồ thị, ta sẽ thu được thuật toán với thời
gian O(V2E) và bộ nhớ O(V2). Với đồ thị dầy E=O(V2), thời gian của thuật toán này là
O(V4).
1
Link: https://goo.gl/DbO6hd (Notes on All Pairs Shortest Paths - Jeff Erickson)
55
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Trong các giả mã dưới đây, ta quy ước w(u→u)=0 và w(u→v)=+∞ nếu như
cung u→v không tồn tại trong đồ thị G.
Ta sẽ xây dựng bảng bảng quay hoạch động tương tự như bảng quy hoạch động
trong bài thuật toán Bellman-Ford.
(1)
Ý nghĩa của công thức truy hồi trên như sau: đường đi ngắn nhất từ u tới v sẽ
phải đi qua một hàng xóm x nào đó của v mà (x→v)∈E
Nếu đường đi ngắn nhất từ u tới v đi qua tối đa i đỉnh thì đường đi con từ u tới x
sẽ đi qua không quá i−1 đỉnh. Do đó D[u,v,i]=D[u,x,i−1]+w(x→v). Do ta không biết
x, ta sẽ duyệt qua tất cả các hàng xóm có thể của v và lấy giá trị nhỏ nhất để tìm
D[u,v,i].
Khoảng cách ngắn nhất từ u tới v chính là D[u,v,V−2] vì đường đi ngắn nhất từ
u tới v sẽ đi qua tối đa V−2 đỉnh.
Mã giả:
56
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Phân tích thời gian: Bước khởi tạo đầu tiên mất thời gian O(V3) để khởi tạo
các phần tử của bảng D thành +∞. Bước khởi tạo thứ hai (2 vòng lặp for) mất thời gian
O(V2). Do đó ta mất tổng thời gian O(V3) để khởi tạo. Tiếp theo ta sẽ phân tích 4 vòng
for cuối cùng. Vòng for trong cùng duyệt qua tất các cảc đỉnh x mà có cung x→v∈E;
do đó, vòng for này có d+(v) vòng lặp trong đó d+(v) là bậc tới của v. Tổng số bước lặp
của hai vòng for trong cùng là: ∑v∈Vd+(v)=O(E) (2)
Do hai vòng lặp ngoài cùng có (V−2)V bước lặp, thời gian của thuật toán là
O(V2E). Như vậy, thuật toán DummyAllPairsSPs có thời gian chạy bằng thời gian
thực hiện V lần thuật toán Bellman-Ford.
Ý nghĩa của công thức truy hồi trên là nếu có một đường đi từ u tới v qua tối đa
2i đỉnh thì đường đi đó sẽ đi qua một đỉnh x sao cho đường đi từ u tới x sẽ qua tối đa
2i−1 đỉnh và đường đi từ x tới v đi qua tối đa 2i−1 đỉnh.
57
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Chú ý với định nghĩa bảng D như trên, trường hợp cơ bản sẽ là D[u,v,1]. Do đó,
ta sẽ áp dụng một vòng lặp trong thuật toán DummyAllPairsSPs để tính các giá trị đó.
Mã giả:
Phân tích thuật toán: Bước khởi tạo đầu tiên mất thời gian O(V2log2(V)) để
khởi tạo bảng D và bước thứ hai (2 vòng lặp for đầu tiên) mất 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ự như phân tích ở trên, mất thời gian
O(VE). Bước cuối cùng thực hiện 4 vòng lặp lồng nhau, 3 vòng lặp trong cùng có V3
bước lặp và vòng lặp ngoài cùng có log2(V−2) bước lặp. Như vậy, tổng thời gian của
thuật toán trên là O(VE+V3log2(V)) = O(V3log2(V)).
58
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
D[u,v,i]=min(D[u,v,i−1],D[u,i,i−1]+D[i,v,i−1]) (4)
Ý nghĩa của công thức truy hồi trên là nếu có một đường đi từ u tới v qua các
đỉnh trong tập hợp {1,2,…,i} thì hoặc đường đi đó đi qua đỉnh i hoặc nó không đi qua
đỉnh i
- Nếu đường đi đó không đi qua đỉnh i thì đường đi ngắn nhất đó cũng chính
là đường đi ngắn nhất từ u tới v đi qua các đỉnh trung gian trong
{1,2,…,i−1}. Do đó D[u,v,i]=D[u,v,i−1]
- Nếu đường đi đó đi qua đỉnh i thì đường đi con từ u tới i và đường đi con từ
i tới v sẽ chỉ đi qua các đỉnh thuộc tập {1,…,i−1}. Do đó
D[u,v,i]=D[u,v,i−1]+D[i,v,i−1]
- Ta sẽ lấy min trong 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] sẽ là đường đi ngắn nhất từ u tới v
Mã giả:
Giảm bộ nhớ:
Thuật toán Floyd-Warshall trình bày ở trên có bộ nhớ O(V3) vì đầu ra của
chúng ta là mảng 3 chiều D. Để giảm bộ nhớ chúng ta có nhận xét: Các giá trị ở bảng
con 2 chiều thứ i (là các giá trị D[u,v,i] với mọi cặp đỉnh u,v) chỉ phụ thuộc vào giá trị
của bảng con hai chiều thứ i−1 (là các giá trị D[u,v,i−1] với mọi cặp đỉnh u,v). Do đó,
thay vì lưu cả bảng 3 chiều, ta có thể lưu hai bảng hai chiều, một bảng tương ứng với
bảng hai chiều thứ i với i chẵn, và một với bảng hai chiều thứ i với i lẻ và tái sử dụng
các bảng hai chiều đó qua các vòng lặp. Ý tưởng này giống như cách giảm bộ nhớ của
thuật toán tính số Fibonacci.
Tuy nhiên, ta có thể đơn giản hóa bằng cách bỏ hẳn chiều thứ 3 của bảng 3
chiều D. Tính chất nhỏ nhất của khoảng cách ngắn nhất sẽ đảm bảo thuật toán luôn
đúng mặc dù ta bỏ hẳn một chiều của bảng quy hoạch động.
Mã giả:
Để in ra đường đi ngắn nhất giữa hai đỉnh (u,v) bất kì, chúng ta cần phải lưu vết
trong quá trình quy hoạch động. Mỗi khi có thay đổi trong bảng quy hoạch động
(D[u,v]>D[u,i]+D[i][v]), ta phải ghi lại sự thay đổi đó. Ta sẽ dùng một mảng 2 chiều
T[1,…,V][1,…,V] trong đó T[u,v]=i nếu đường đi ngắn nhất từ u tới v đi qua đỉnh i.
Có hai giá trị đặc biệt của T[u,v] mà ta cần phải quan tâm: nếu không tồn tại
đường đi nào từ u tới v, ta sẽ gán T[u,v]= Null. Nếu đường đi ngắn nhất từ u đến v
không đi qua đỉnh trung gian nào, ta sẽ gán T[u,v]=0.
60
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Mã giả:
Ghi chú: Mặc dù sau rất nhiều năm nghiên cứu, cho đến nay vẫn chưa có thuật toán
nào tốt hơn thời gian O(V3) một cách đáng kể và cho mọi trường hợp. Do đó, người ta
giả thuyết rằng không tồn tại thuật toán như vậy (gọi là giả thuyết đường đi ngắn nhất
giữa mọi cặp đỉnh). Thuật toán (ngẫu nhiên) tốt nhất cho đến nay được phát triển bởi
61
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Mô hình biểu diễn heap bằng cây nhị phân và bằng mảng
* Khai báo Heap:
Const
maxn=10000;
62
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Var
nHeap:LongInt;
Heap : array[0..maxn] of LongInt;
* Upheap: Nếu một nút lớn hơn nút cha của nó thì di chuyển 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 là nút gốc hoặc nhỏ hơn nút cha thì không làm việc
swap(Heap[i],Heap[i div 2]); // Đổi chỗ 2 phần tử trong Heap;
UpHeap(i div 2); // Tiếp tục di chuyển lên trên
end;
* Downheap: Nếu một nút nhỏ hơn nút con thì đẩy nó xuống dưới
Procedure UpHeap(i : LongInt);
Begin
if (i = 1) or (Heap[i] < Heap[i div 2]) then
exit; // Nếu i là nút gốc hoặc nhỏ hơn nút cha thì không làm việc
swap(Heap[i] , Heap[i div 2]); // Đổi chỗ 2 phần tử trong Heap;
UpHeap(i div 2); // Tiếp tục di chuyển lên trên
end;
* Push: Đưa một phần tử mới vào Heap bằng cách thêm một nút vào cuối Heap và
tiến hành UpHeap từ đây
Procedure Push(x : LongInt);
begin
Inc(nHeap); // Tăng số phần tử của Heap
Heap[nHeap] := x; // Thêm x vào Heap
UpHeap(nHeap);
end;
* Pop:Lấy ra phần tử ở vị trí v trong Heap bằng cách gán Heap[v] := Heap[nHeap] rồi
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 ra khỏi Heap
63
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Pop – Dijsktra:
Function Pop:Longint; //Lấy từ Heap đỉnh có nhãn tự do nhỏ nhất
Var r,v,c:longint;
Begin
Pop:=Heap[1];//Nút gốc là nút có nhãn tự do nhỏ nhất
v:=Heap[nHeap];//v là đỉnh ở nút lá cuối Heap, sẽ được đảo lên đầu và vun đống
Dec(Heap);
r:=1;//Bắt đầu từ nút gốc
While r*2<=nheap do //Chừng nào r chưa phải là lá
Begin
c:=r*2; // c là nút con của r
if (c<=nheap) and (d[heap[c]]>d[heap[c+1]]) then
inc(c); //Trong 2 nút con chọn nút con có đỉnh ưu tiên hơn
if d[heap[c]]>=d[v] then
break; //nếu v ưu tiên hơn thì không làm việc nữa
heap[r]:=heap[c];//Chuyển đỉnh lưu ở nút con lên nút cha
pos[heap[r]]:=r;//Cập nhật lại vị trí trong Heap của đỉnh đó
r:=c;//Tiếp tục di chuyển xuống dưới
end;
heap[r]:=v;//Đỉnh v được đặt vào vị trí r để đảm bảo cấu trúc heap
pos[v]:=r;//Ghi nhận vị trí mới của đỉnh v trong heap
end;
II. ỨNG DỤNG CỦA HEAP
1. Ứng dụng của Heap trong giải thuật Heapsort
Tư tưởng thuật toán
Đổi chỗ (Swap): Sau khi mảng a[1..n] đã là đống, lấy phần tử a[1] trên đỉnh của
đống ra khỏi đống đặt vào vị trí cuối cùng n, và chuyển phần tử thứ cuối cùng a[n] lên
đỉnh đống thì phần tử a[n] đã được đứng đúng vị trí.
Vun lại: Phần còn lại của mảng a[1..n-1] chỉ khác cấu trúc đống ở phần tử a[1].
Vun lại mảng này thành đống với n-1 phần tử.
Lặp: Tiếp tục với mảng a[1..n-1]. Quá trình dừng lại khi đống chỉ còn lại một
phần tử.
65
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Max-Heap: ở đây giá trị của nút gốc là lớn hơn hoặc bằng giá trị của các nút
con.
66
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
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;
While (j<=ri) and cont do
begin
67
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
a[1]:=a[ri];
a[ri]:=x;
dec(ri);
// Tạo a[1]...a[r] là heap
Heap;
end;
End;
Procedure Taodaycothututang;
Begin
For ri:=1 to (n div 2) do
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 do
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;
Output;
69
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
sau đó thêm 1 phần tử mới có giá trị bằng tổng 2 phần tử vừa lấy ra cho vào heap. Như
thế sau n-1 lần, heap chỉ còn 1 phần tử duy nhất và giá trị của phần tử này chính là kết
quả cần tìm. Chú ý: kết quả vượt quá longint nên phải để heap là 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]) do
begin
Heap[con]:=Heap[cha];
71
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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) do
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 do
Begin
Read(f,Heap[i]);
Update(Heap[i]);
End;
72
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Value:=0;
While (nheap>1) do
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 2 dãy số nguyên A và B. Với mọi số A[i]thuộc A và B[j] thuộc B người ta
tính tổng nó. Tất cả các tổng này sau khi được sắp xếp không giảm sẽ tạo thành dãy
C.Nhiệm vụ của bạn là: Cho 2 dãy A, B. Tìm K số đầu tiên trong dãy C
Input:
- Dòng đầu tiên gồm 3 số: M, N, K
- M dòng tiếp theo gồm M số mô tả dãy A
- N dòng tiếp theo gồm N số mô tả dãy B
Output: Gồm K dòng tương ứng là K phần tử đầu tiên trong dãy C
Giới hạn:
1 ≤ M, N, K ≤ 50000
1 ≤ Ai, Bi ≤ 109
Input Output
446 3
1 4
2 4
73
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
3 5
4 5
2 5
3
4
5
Giới hạn
1 ≤ M, N, K ≤ 50000
1 ≤ Ai, Bi ≤ 109
Thuật toán:
Với thì không thể tạo một mảng rồi sắp xếp lại được, vì
vậy chúng ta cần những thuật toán tinh tế hơn, 1 trong những thuật toán đơn giản mà
hiệu quả nhất là dùng heap.
Đầu tiên sắp xếp 2 mảng A và B không giảm, dĩ nhiên phần tử đầu tiên chính là
A[1] + B[1], vấn đề là phần tử thứ 2 là A[2] + B[1] hay là A[1] + B[2], ta xử lý như
sau:
Trước hết ta tạo một heap min gồm các phần tử A[1]+B[1], A[1]+B[2], …,
A[1]+B[n], mỗi khi lấy ra phần tử ở gốc (tức phần tử có giá trị nhỏ nhất trong heap
hiện tại) giả sử phần tử đó là A[i]+B[j] ta lại đẩy vào heap phần tử mới là A[i+1]+B[j]
(nếu i=m thì không đẩy thêm phần tử nào vào heap). Cứ thế cho đến khi ta lấy đủ K
phần tử cần tìm.
Độ phức tạp thuật toán trong trường hợp lớn nhất là O(50000*log(50000)), có
thể chạy trong thời gian 1s, bộ nhớ là 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 một danh sách rỗng. Người ta xét hai thao tác trên danh sách đó:
Thao tác “+V” (ở đây V là một số tự nhiên <= 1000000000): Nếu danh sách đang có ít
hơn 15000 phần tử thì thao tác này bổ sung thêm phần tử V vào danh sách; Nếu
không, thao tác này không có hiệu lực.
Thao tác “-”: Nếu danh sách đang không rỗng thì thao tác này loại bỏ tất cả các
phần tử lớn nhất của danh sách; Nếu không, thao tác này không có hiệu lực
74
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Input
Gồm nhiều dòng, mỗi dòng ghi một thao tác. Thứ tự các thao tác trên các dòng được
liệt kê theo đúng thứ tự sẽ thực hiện
Output
Dòng 1: Ghi số lượng những giá trị còn lại trong danh sách. Các dòng tiếp theo:
Liệt kê những giá trị đó theo thứ tự giảm dần, mỗi dòng 1 số.
Example
Input Output
+1 4
+3 8
+2 7
+3 2
– 1
+4
+4
–
+2
+9
+7
+8
–
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);
if heap[i]>=heap[j] then
exit;
76
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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) do
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
tmp:=heap[1];
77
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Theo thống kê thì có tất cả N “bạch” hội được đánh số từ 1 tới N theo thứ tự bất
kỳ và mỗi hội có M pháp sư.N hội này đã hội họp lại với nhau để bàn chiến thuật cho
cuộc chiến sắp tới với các “hắc” hội. Vì đây sẽ là cuộc chiến lâu dài nên chiến thuật
được đưa ra là sẽ đưa từng tốp N pháp sư trong đó không có 2 pháp sư nào thuộc
cùng một hội ra đánh thay phiên nhau. Chi tiết chiến thuật thì các tốp có sức mạnh
yếu sẽ được đưa ra trước. Sức mạnh của một tốp pháp sư được đánh giá bằng tổng chỉ
số sức mạnh của các pháp sư được chọn. Khi không cầm cự được nữa thì tốp đó sẽ
quay về để tốp khác lên đánh. Một pháp sư có thể được chọn trong 2 đợt liên tiếp.
Ngoài ra 2 tốp pháp sư liên tiếp nhau có thể mạnh hoặc yếu như nhau.
Yêu cầu: Tính sức mạnh của tốp pháp sư ở lượt thứ K.
Input:
- Dòng đầu chứa 3 số nguyên dương N, M, K.
- N dòng tiếp theo, dòng thứ i sẽ chứa M số nguyên dương là chỉ số sức mạnh
của các pháp sư từ yếu tới mạnh trong “bạch” hội thứ i. Pháp sư u yếu hơn pháp sư v
nếu chỉ số sức mạnh của pháp sư u nhỏ hơn pháp sư v.
Output:Chứa 1 số là tổng chỉ số sức mạnh của tốp pháp sư ở lượt thứ K.
Input Output
234 7
123
456
Ràng buộc:
K <= MN.
Sức mạnh của các pháp sư <= 109.
20% số test sẽ có N = 5 và M <= 20.
20% số test tiếp theo sẽ có N = 2 và M <= 106.
20% số test tiếp theo sẽ thỏa mãn: N = 3, M <= 105, tổng chỉ số sức
mạnh trong mỗi hội <= 107.
40% số test tiếp theo có N = 10, M <= 105 và K <= 106.
Thuật toán:
Với : Sinh số bằng đệ quy, sort lại rồi lấy số thứ .
79
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Với : Chặt nhị phân kết quả rồi đếm trên 2 dãy với độ phức tạp
O(N).
Với Chú ý rằng trong 1 hội thì tổng không vượt quá , vậy
nên số phần tử phân biệt trong 1 hội không vượt quá . Ta co dãy số lại rồi thực
hiện thuật toán như trên.
80
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Hai số trên cùng một dòng được ghi cách nhau ít nhất một dấu cách.
Output: Ghi ra một số nguyên duy nhất là thời điểm sớm nhất tìm được.
Input Output
5 10
5 10
3 10
3 20
2 9
2 15
Thuật toán:
Chặt nhị phân kết quả, giả sử là x. Với mỗi cảnh sát ta xác định được lo[i] và
hi[i] là vị trí mà cảnh sát i có thể di chuyển được trong thời gian x. Bài toán quy về xếp
các cảnh sát vào vị trí ans[i] sao cho và
Sắp xếp các cảnh sát theo tăng dần theo lo, với một cột j, ta sẽ tìm cảnh sát i thỏa
mãn để xếp vào và là nhỏ nhất trong các i thỏa mãn điều kiện.
Để thực hiện thao tác này, ta dùng 1 heap min để chứa các hi[i].
i := 1;
for j := 1 to n do
begin
while (i <= n) and (lo[i] <= j) do
begin
push(hi[i]);
inc(i);
end;
u := pop;
ans[u] := j;
end;
Chương trình: (Code\ MOVER12.pas)
Var
fi,fo:text;
81
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
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);
if b[h[i]]>b[h[j]] then
82
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do inc(i);
while a[j]>key do dec(j);
if i<=j then
begin
swap(a[i],a[j]);
swap(b[i],b[j]);
inc(i);dec(j);dec(j);
end;
83
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
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 do
begin
while (a[j]<=i) and (j<=n) do
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
le:=0;
84
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
r:=10000*10000;
while le<=r do
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 của heap trong lý thuyết đồ thị
Ứng dụng của heap trong lý thuyết đồ thị là giúp cải tiến tốc độ thực hiện các
thuật toán. Chẳng hạn:
a) Trong thuật toán Dijkstra
Trường hợp đồ thị có nhiều đỉnh, ít cạnh, thuật toán cần n lần cố định nhãn và
mỗi lần tìm đỉnh để cố định nhãn sẽ mất một đoạn chương trình với độ phức tạp O(n).
Để tăng tốc độ, người ta thường sử dụng cấu trúc dữ liệu Heap để lưu các đỉnh chưa cố
định nhãn. Heap ở đây là một cây nhị phân hoàn chỉnh thỏa mãn: Nếu u là đỉnh lưu ở
nút cha và v là đỉnh ở nút con thì d[u] ≤ d[v] (đỉnh r lưu ở gốc Heap là đỉnh có d[r] nhỏ
nhất).
85
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Tại mỗi bước lặp của thuật toán Dijkstra có hai thao tác: Tìm đỉnh cố định nhãn
và sửa nhãn
Thao tác tìm đỉnh cố định nhãn sẽ lấy đỉnh lưu ở gốc Heap, cố định nhãn, đưa
phần tử cuối Heap và thế chỗ và thực hiện việc vun đống.
Thao tác sửa nhãn, sẽ duyệt danh sách kề của đỉnh vừa cố định nhãn và sửa
nhãn những đỉnh tự do kề với đỉnh này, mỗi lần sửa nhãn một đỉnh nào đó, ta
xác định đỉnh này nằm ở đâu trong Heap và thực hiện việc chuyển đỉnh đó lên
phía gốc Heap nếu cần bảo toàn cấu trúc Heap.
Thuật toán Dijkstra tổ chức trên Heap tìm đường đi ngắn từ đỉnh s đến đỉnh t
- Cập nhật nút 1 của Heap (tương ứng với đỉnh xuất phát s, có giá trị khóa
bằng 0)
- Vòng lặp cho đến khi Heap rỗng (không còn nút nào)
Begin
o Lấy đỉnh u tại nút gốc của heap (phép loại bỏ gốc Heap)
o Nếu u=t (đỉnh t là đỉnh đích của đường đi) thì thoát khỏi
vòng lặp
o Đánh dấu u là đỉnh đã được cố định nhãn
o Duyệt danh sách cung để tìm các cung có đỉnh đầu bằng u,
đỉnh cuối là v. Nếu v là đỉnh tự do và d[v] > d[u] + khoảng
cách (u,v) thì
Begin
Sửa nhãn cho v và ghi nhận đỉnh trước v là u
Trên Heap, cập nhật lại nút tương ứng với
đỉnh v
end
End
Thuật toán Dijkstra có độ phức tạp là O(n2), việc kết hợp với cấu trúc dữ liệu
Heap giúp thuật toán được cải tiến và có độ phức tạp là O(max(n,m).logn).
Bài :Centre http://vn.spoj.com/problems/CENTRE28/)
Theo thống kê cho biết mức độ tăng trưởng kinh tế của nước Peace trong năm
2006 rất đáng khả quan. Cả nước có tổng cộng N thành phố lớn nhỏ được đánh số tuần
tự từ 1 đến N phát triển khá đồng đều. Giữa N thành phố này là một mạng lưới gồm M
86
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
đường đi hai chiều, mỗi tuyến đường nối 2 trong N thành phố sao cho không có 2
thành phố nào được nối bởi quá 1 tuyến đường. Trong N thành phố này thì thành phố 1
và thành phố N là 2 trung tâm kinh tế lớn nhất nước và hệ thống đường đảm bảo luôn
có ít nhất một cách đi từ thành phố 1 đến thành phố N.
Tuy nhiên,cả 2 trung tâm này đều có dấu hiệu quá tải về mật độ dân số. Vì vậy,
đức vua Peaceful quyết định chọn ra thêm một thành phố nữa để đầu tư thành một
trung tâm kinh tế thứ ba. Thành phố này sẽ tạm ngưng mọi hoạt động thường nhật,
cũng như mọi luồng lưu thông ra vào để tiến hành nâng cấp cơ sở hạ tầng. Nhưng
trong thời gian sửa chữa ấy, phải bảo đảm đường đi ngắn nhất từ thành phố 1 đến
thành phố N không bị thay đổi, nếu không nền kinh tế quốc gia sẽ bị trì trệ.
Vị trí và đường nối giữa N thành phố được mô tả như một đồ thị N đỉnh M
cạnh. Hãy giúp nhà vua đếm số lượng thành phố có thể chọn làm trung tâm kinh tế thứ
ba sao cho thành phố được chọn thỏa mãn các điều kiện ở trên
Input
- Dòng đầu tiên ghi 2 số nguyên dương N và M là số thành phố và số tuyến
đường.
- Dòng thứ i trong số M dòng tiếp theo ghi 3 số nguyên dương xi, yi và di với
ý nghĩa tuyến đường thứ i có độ dài di và nối giữa 2 thành phố xi, yi.
Output
- Dòng đầu tiên ghi số tự nhiên S là số lượng các thành phố có thể chọn làm
trung tâm kinh tế thứ ba.
- S dòng tiếp theo, mỗi dòng ghi 1 số nguyên dương là số thứ tự của thành
phố được chọn ( In ra theo thứ tự tăng dần )
Example
Input Output
66 2
121 4
231 5
361
1 4 100
4 5 100
87
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
5 6 100
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 do
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
swap(h[i],h[j]);
89
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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
pop:=h[1];
h[1]:=h[nh];
p[h[1]]:=1;
dec(nh);
downheap(1);
end;
90
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
d[i]:=maxc;
d[s]:=0;
f[s]:=1;
push(s);
repeat
u:=pop;
i:=head[u];
while i<>0 do
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
f[v]:=f[u]+f[v];
91
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
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 do writeln(d1[i]);}
writeln(res);
for i:=1 to res do writeln(ans[i]);
close(output);
end;
begin
enter;
process;
print;
92
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
end.
b) Trong thuật toán Kruskal tìm cây khung nhỏ nhất
Một trong những vấn đề quan trọng là làm thế nào để xét được các cạnh từ cạnh
có trọng số nhỏ tới cạnh có trọng số lớn. Ta có thể thực hiện bằng cách sắp xếp danh
sách cạnh theo thứ tự không giảm của trọng số, sau đó duyệt từ đầu tới cuối danh sách
cạnh, nên sử dụng các thuật toán sắp xếp hiệu quả để đạt được tốc độ nhanh trong
trường hợp số cạnh lớn. Trong trường hợp tổng quát, thuật toán Heapsort là hiệu quả
nhất bởi nó cho phép chọn lần lượt các cạnh từ cạnh trọng số nhỏ nhất tới cạnh trọng
số lớn nhất ra khỏi Heap và có thể xử lư (bỏ qua hay thêm vào cây) luôn.
Thuật toán Kruskal có độ phức tạp là O(mlogn), nếu đồ thị có cây khung thì m ≥
n-1 thì chi phí thời gian chủ yếu sẽ nằm ở thao tác sắp xếp danh sách cạnh bởi độ phức
tạp của HeapSort là O(mlogm), do đó độ phức tạp tính toán của thuật toán là
O(mlogm) trong trường hợp xấu nhất.
Bài 1: Net (Test\Net.rar)
Một công ty lập một dự án mắc điện thoại cho N (N<5000) nhân viên của mình
bằng một lưới đoạn nối từ máy của một người đến máy một người khác (không phải
người nào cũng được nối với nhau). Dự án phải đảm bảo yêu cầu sau cho (gọi là yêu
cầu về tính thông suốt của mạng): trên lưới điện thoại đó mỗi nhân viên của Công ty
đều có thể nhắn tin cho bất cứ một nhân viên khác hoặc trực tiếp hoặc thông qua một
số nhân viên trung gian.
Yêu cầu:Kiểm tra xem dự án có đáp ứng yêu cầu về tính thông sống không ?,
nếu có, hãy tìm cách loại bỏ một số đường nối sao cho mạng vẫn là 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 đầu tiên chứa hai số n (số nhân viên), m (số đường nối)
- Các dòng tiếp theo chứa các đường nối của dự án: mỗi dòng gồm 3 số
nguyên theo thứ tự là chỉ số của hai máy được nối và chi phí nối hai máy
này
Kết quả: Ghi ra File NET.OUT
- Dòng đầu tiên ghi chi phí tối thiểu nối mạng
- Dòng thứ hai ghi số k là số lượng đoạn nối cần loại bỏ
93
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- K dòng tiếp theo mô tả thông tin k đoạn nối cần loại bỏ: mỗi dòng chứa hai
số nguyên xác định điểm đầu và điểm cuối đoạn nối.
Ví dụ:
NET.INP NET.OUT
57 91
1 2 16 3
1 4 38 45
1 5 43 15
2 5 50 25
3 5 25
4 3 12
4 5 29
Thuật toán:
- Xây dựng cây khung nhỏ nhất với n-1 cạnh
- Sắp xếp các cạnh của đồ thị theo thứ tự không giảm của độ dài để lựa chọn
cạnh bổ song (hay loại bỏ). Tuy nhiên để xây dựng cây khung nhỏ nhất với
n-1 cạnh, ta không cần phải sắp toàn bộ thứ tự các cạnh mà chỉ cần xét phần
trên của dãy đó chứa r<m cạnh. Để làm việc đó ta có thể sử dụng thủ tục sắp
xếp dạng vun đống (Heap Sort), hoặc 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ó 1 thủ tục hiệu quả kiểm tra
tập cạnh T {e} có chứa chu trình hay không. Để ý rằng các cạnh trong T ở các bước
lập trung gian sẽ tạo thành 1 rừng. Cạnh e cần khảo sát tạo thành 1 chu trình.
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 do
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)) do
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];
D1[j]:=D1[k]; D2[j]:=D2[k]; W[j]:=W[k];
D1[k]:=t1; D2[k]:=t2; W[k]:=t3;
95
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
j:=k;
end
Else j:=last;
end;
end;
Function Find(i:integer):integer;
Var tro:integer;
Begin
Tro:=i;
While Father[tro]>0 do
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;
Var last,i,c,u,r1,r2,v,Ndinh,Ncanh:integer;
Begin
96
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
For i:=1 to n do
Father[i]:=-1;
last:=m; Ncanh:=0; Ndinh:=0;noi:=true;
For i:=trunc(m/2) downto 1 do Heap(i,m);
MinL:=0; Noi:=true;
While (Ndinh<n-1) and (Ncanh<m) do
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 do
If (D1[i]<>DauT[i]) and (D2[i]<>CuoiT[i]) then
inc(canh);
97
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Writeln(f,canh);
For i:=1 to n-1 do
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 đang lập kế hoạch xây dựng một thành phố mới và hiện đại. Theo
kế hoạch, thành phố sẽ có N vị trí quan trọng, được gọi là N trọng điểm và các trọng
điểm này được đánh số từ 1 tới N. Bộ giao thông đã lập ra một danh sách M tuyến
đường hai chiều có thể xây dựng được giữa hai trọng điểm nào đó. Mỗi tuyến đường
có một thời gian hoàn thành khác nhau.
Các tuyến đường phải được xây dựng sao cho N trọng điểm liên thông với
nhau. Nói cách khác, giữa hai trọng điểm bất kỳ cần phải di chuyển được đến nhau qua
một số tuyến đường. Bộ giao thông sẽ chọn ra một số tuyến đường từ trong danh sách
ban đầu để đưa vào xây dựng sao cho điều kiện này được thỏa mãn.
Do nhận được đầu tư rất lớn từ chính phủ, bộ giao thông sẽ thuê hẳn một đội thi
công riêng cho mỗi tuyến đường cần xây dựng. Do đó, thời gian để hoàn thành toàn bộ
các tuyến đường cần xây dựng sẽ bằng thời gian lâu nhất hoàn thành một tuyến đường
nào đó.
Yêu cầu: Giúp bộ giao thông tính thời gian hoàn thành các tuyến đường sớm
nhất thỏa mãn yêu cầu đã nêu.
Dữ liệu
- Dòng chứa số N và M (1 ≤ N ≤ 1000; 1 ≤ M ≤ 10000).
98
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- M tiếp theo, mỗi dòng chứa ba số nguyên u, v và t cho biết có thể xây dựng
tuyến đường nối giữa trọng điểm u và trọng điểm v trong thời gian t. Không
có hai tuyến đường nào nối cùng một cặp trọng điểm.
Kết quả
Một số nguyên duy nhất là thời gian sớm nhất hoàn thành các 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 do
begin
readln(f, u, v, c);
Edges[i].u:=u;
Edges[i].v:=v;
99
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Edges[i].c:=c;
end;
close(f);
end;
Procedure init;
var i :longint;
begin
for i:=1 to m do b[i]:=i;
for i:=1 to n do 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 do inc(i);
while Edges[b[j]].c > x do 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;
if l<j then QS(l,j);
if i<r then QS(i,r);
end;
100
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
function find(i:longint):longint;
var t :longint;
begin
t:=i;
while father[t]>0 do 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 do
begin
u:=Edges[b[i]].u;
v:=Edges[b[i]].v;
c:=Edges[b[i]].c;
r1:=find(u);
101
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 toán Prim tìm cây khung nhỏ nhất
Xét về độ phức tạp tính toán, thuật toán Prim có độ phức tạp là O(n2). Tương tự
thuật toán Dijkstra, nếu kết hợp thuật toán Prim với cấu trúc Heap sẽ được một 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);
var d:array[1..maxn] of longint;
i,j,n,m,res:longint;
102
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do 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 do
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 do
if cx[v] then
if d[v]>c[u,v] then
begin
d[v]:=c[u,v];
end;
until false;
end;
103
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
assign(input,fi);reset(input);
assign(output,fo);rewrite(output);
readln(n,m);
for i:=1 to n do
for j:=1 to n do
if i<>j then
c[i,j]:=maxc
else c[i,j]:=0;
for i:=1 to m do
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 quyết định mang nước tới cho N (1 <= N <= 300) đồng cỏ của
mình, để thuận tiện ta đánh số các đồng cỏ từ 1 đến N. Để tưới nước cho 1 đồng cỏ
John có thể chọn 2 cách, 1 là đào ở đồng cỏ đó 1 cái giếng hoặc lắp ống nối dẫn nước
từ những đồng cỏ trước đó đã có nước tới.
Để đào một cái giếng ở đồng cỏ i cần 1 số tiền là W_i (1 <= W_i <= 100,000).
Lắp ống dẫn nước nối 2 đồng cỏ i và j cần 1 số tiền là P_ij (1 <= P_ij <= 100,000; P_ij
= P_ji; P_ii=0).
Tính xem nông dân John phải chi ít nhất bao nhiêu tiền để tất cả các đồng cỏ đều có
nước.
DỮ LIỆU
Dòng 1: Một số nguyên duy nhất: N
104
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Các dòng 2..N + 1: Dòng i+1 chứa 1 số nguyên duy 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 nhau bởi dấu cách;
số thứ j là P_ij
KẾT QUẢ
Một số nguyên duy nhất là chi phí tối thiểu để cung cấp nước cho tất cả các
đồng cỏ.
VÍ DỤ
FWATER.INP FWATER.OUT
4 9
5
4
4
3
0222
2033
2304
2340
GIẢI THÍCH:
Có 4 đồng cỏ. Mất 5 tiền để đào 1 cái giếng ở đồng cỏ 1, 4 tiền để đào ở đồng
cỏ 2, 3 và 3 tiền để đào ở đồng cỏ 4. Các ống dẫn nước tốn 2, 3, và 4 tiền tùy thuộc
vào nó nối đồng cỏ nào với nhau. Nông dân John có thể đào 1 cái giếng ở đồng cỏ thứ
4 và lắp ống dẫn nối đồng cỏ 1 với tất cả 3 đồng cỏ còn lại, chi phí tổng cộng là 3 + 2
+ 2 + 2 = 9.
105
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
for j:=1 to n do
if i=j then a[i,j] := 0
else a[i,j] := maxC;
for i:=1 to n do readln(f,w[i]);
for i:=1 to n do begin
for j:=1 to n do read(f,a[i,j]);
readln(f);
end;
close(f);
end;
procedure init;
var i: longint;
begin
inc(n);
for i:=1 to n do begin
a[n,i] := w[i];
a[i,n] := w[i];
end;
d[1] := 0;
106
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
procedure Prim;
var k,i,u,v,min: longint;
connected: boolean;
begin
fillchar(tr,sizeof(tr),0);
connected := true;
for k:=1 to n do begin
u := 0;
min := maxC;
for i:=1 to n do
if free[i] and (d[i] < min) then begin
min := d[i];
u := i;
end;
if u=0 then begin
connected := false;
break;
end;
free[u] := false;
for v:=1 to n do
if free[v] and (d[v] > a[u,v]) then begin
d[v] := a[u,v];
tr[v] := u;
end;
end;
end;
procedure writef;
107
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
var f: text;
t,i: longint;
begin
assign(f,fo); rewrite(f);
t := 0;
for i:=2 to n do
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 dữ liệu Heap là một cấu trúc dữ liệu quan trọng và có tính
ứng dụng cao, kết hợp các thuật toán với cấu trúc Heap giúp cải tiến một cách đáng kể
thời gian thực hiện thuật toán. Ngoài ra, có rất nhiều cấu trúc dữ liệu heap còn có
nhiều dạng biến thể như B-heap, Binary heap, Fibonacci heap….
Qua quá trình nghiên cứu tài liệu từ nhiều nguồn khác nhau, tôi viết chuyên đề
này, mong rằng có thể hữu ích cho các đồng nghiệp, do kinh nghiệm còn hạn hẹp
mong có thêm nhiều góp ý và trao đổi.
IV. TÀI LIỆU THAM KHẢO
[1] Các chuyên đề của nhiều tác giả đã viết trước đây
[2] Tài liệu sách giáo khoa chuyên – Tập 1, 2, 3
[3] Giáo trình Giải thuật và lập trình – Tác giả Lê Minh Hoàng
[4] Sáng tạo thuật toán trong lập trình tập 1, 2, 3 – Nguyễn Xuân Huy
[5] Trang kienthuc24h.com
[6] Trang Vnoi.info
108
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
109
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
(Mô hình biểu diễn Heap bằng cây nhị phân và bằng mảng)
II. Các thao tác thường dùng đối với Heap Max
1. Khai báo
Ở ví dụ này, Heap sẽ là mảng một chiều kiểu LongInt, nHeap là số phần tử của mảng.
110
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Const
maxn = 100000;
Var
nHeap : LongInt;
Heap : array[0..maxn] of LongInt;
2. UpHeap
Nếu 1 nút lớn hơn nút cha của nó thì di chuyển nó lên trên:
111
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
113
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
end;
parent := child div 2; // parent là nút cha của child
while (parent > 0) and (d[Heap[parent]] > d[v]) do // Nếu đỉnh ở nút parent kém
ưu tiên hơn v thì bị “kéo xuống” nút child
begin
Heap[child] := Heap[parent]; // Đẩy đỉnh được lưu trong nút cha xuống nút
con
pos[Heap[child]] := child; // Ghi nhận lại vị trí mới của đỉ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” ở trên sẽ tạo ra 1 ô trống ở nút child
để đặt v vào
pos[v] := child; // Ghi nhận vị trí mới của đỉnh v trong Heap
end;
2/ Pop – Dijkstra:
Function Pop : LongInt; // Lấy từ Heap đỉnh có nhãn tự do nhỏ nhất
var
r , v , c : LongInt;
begin
Pop := Heap[1]; // Nút gốc là nút có nhãn tự do nhỏ nhất
v := Heap[nHeap]; // v là đỉnh ở nút lá cuối Heap, sẽ được đảo lên đầu và vun
đống
Dec(nHeap); // Giảm số phần tử của Heap
r := 1; // Bắt đầu từ nút gốc
while r*2 <= nHeap do // Chừng nào r chưa phải là lá
begin
c := r*2; // c là nút con của r
if (c <nHeap) and (d[Heap[c]] > d[Heap[c+1]]) then Inc(c); // Trong 2 nút con
chọn nút con chứa đỉnh ưu tiên hơn
if d[Heap[c]] >= d[v] then break; // Nếu v ưu tiên hơn thì không làm việc nữa
Heap[r] := Heap[c]; // Chuyển đỉnh lưu ở nút con lên nút cha
114
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
pos[Heap[r]] := r; // Cập nhật lại vị trí mới trong Heap của đỉnh đó
r := c; // Tiếp tục di chuyển xuống dưới
end;
Heap[r] := v; // Đỉnh v được đặt vào vị trí r để đảm bảo cấu trúc Heap
pos[v] := r; // Ghi nhận vị trí mới của đỉnh v trong Heap
end;
III. Một số bài 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 hiện một số thao tác trên danh sách. Có
3 loại thao tác sau :
- Thao tác 1 : Cho phần tử x vào danh sách
- Thao tác 2 : Xóa phần tử thứ t (theo thứ tự nhập vào)
- Thao tác 3: Lấy ra phần tử nhỏ nhất trong danh sách
Yêu cầu: Hãy cho biết các kết quả của thao tác 3 ?
INPUT: HEAPURI.INP
- Dòng 1: N
- Các dòng tiếp theo là dãy thao tác cần xử lý theo định dạng 1 x, 2 x hoặc 3
tương ứng là thao tác 1, 2 và 3
OUTPUT : HEAPURI.OUT
- Ghi trên nhiều dòng, mỗi dòng là kết quả của thao tác 3 theo thứ tự trong file
input.
Ví dụ :
HEAPURI.INP HEAPURI.OUT
9 4
1 4 2
1 7 7
1 9
3
1 2
2 1
3
115
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
2 4
3
Dễ thấy đây là một danh sách động (số lượng phần tử thay đổi), vì vậy ta xây dựng
một heapmin để lấy ra giá trị nhỏ nhất ở các thao tác 3. Để xóa phần tử thứ t theo thứ
tự nhập vào thì ta lưu thêm một mảng Pos.
#include <stdio.h>
#include <assert.h>
int N, L, NR;
int A[maxn], Heap[maxn], Pos[maxn];
void push(int x)
{
int aux;
Pos[Heap[x]] = x;
Pos[Heap[x/2]] = x/2;
x /= 2;
}
}
void pop(int x)
{
116
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
int aux, y = 0;
while (x != y)
{
y = x;
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;
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);
}
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 , được thả lần lượt xuống một ống nước
đặc biệt được thiết kế sao cho tại mỗi thời điểm chỉ có một hạt xốp nhẹ nhất nổi lên
trên bề mặt. Trước mỗi lần thả, hạt xốp đang nổi trên bề mặt sẽ bị ngấm nước và tăng
gấp đôi khối lượng. Hỏi sau khi thả hạt xốp cuối cùng vào ống thì khối lượng xốp tăng
so với tổng khối lượng ban đầu là bao nhiê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 1 số duy nhất là đáp án của bài toán
Ví dụ:
SPONGE.INP SPONGE.OUT
3 3
2 1 3
Tương tự bài 1, dễ thấy đây là một danh sách động (giá trị phần tử thay đổi), vì vậy ta
xây dựng một heapmin để lấy ra hạt xốp nhỏ nhất, tăng gấp đôi khối lượng rồi 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;
w,heap,head,ke,d,c,dd,cost : arr1;
n,nheap,delta : longint;
119
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
{==================================}
Procedure nhap;
Var
i :longint;
Begin
Assign(fi,tfi);Reset(fi);
read(fi,n);
For i := 1 to n do
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]) do
Begin
doicho(heap[i],heap[i div 2]);
i := i div 2;
End;
End;
120
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
{=============================}
Procedure downheap(i : longint);
Var
j :longint;
Begin
While (2*i <= nheap) do
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 := 1 to n-1 do
Begin
push(w[i1]);
121
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
6 32000 6 33000
10000 7000 5000 3000
9000 3000 4000 1000
6000 4000 9000 7000
5000 1000 11000 5000
9000 3000 7000 3000
8000 6000 8000 6000
123
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
{*-----------------------------------------------------------------*}
procedurenhap;
var
i : longint;
begin
assign(fi,tfi);reset(fi);
read(fi,n);
For i := 1 to n do
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;
{*-----------------------------------------------------------------*}
proceduredownheap(i : longint);
124
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
var
j : longint;
begin
j := 2 *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
i : longint;
begin
125
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
For i := 1 to n do
begin
sum := sum + a[i];
push(a[i] - b[i]);
if i mod 2 = 1 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
Ivan Đneprôp viết truyện tiểu thuyết trinh thám. Truyện của anh ta không có gì đặc
sắc: không có các tình huống ly kỳ đặc biệt, cũng không có các chi tiết hài hước tế nhị.
Thậm chí một hiệu sách đã bán các sáng tác của Ivan theo cân! Nhưng độc giả lại thích
truyện của Ivan. Nó dễ hiểu và giúp người ta thư giản sau một ngày lao động mệt
nhọc.
126
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Thực ra, bí mật sự thành công của Ivan là ở chổ không phải chính anh nghĩ ra các
tình huống mà là người em trai Alexei. Ivan có nhiệm vụ viết nó thành các bestsellers.
Dĩ nhiên hai anh em chia nhau hợp lý số tiền kiếm được. Điều đáng tiếc là khả năng
sáng tạo các tình huống ly kỳ của Alexei lại phụ thuộc vào chu kỳ sinh học của anh.
Hai anh em phân tích bảng chu kỳ sinh học của Alexei và thấy rằng trong thời gian tới
Alexei sẽ nghĩ được n chủ đề mới, chủ đề thứ i sẽ được nghĩ ra ở ngày ri. Trong cùng
một ngày có thể Alexei nghĩ ra tới vài câu chuyện.
Với mỗi chủ đề, Ivan thời lượng cần thiết để hoàn thành tác phẩm và tính được
rằng chủ đề thứ i cần có pi ngày để viết. Ivan có trí nhớ cực tốt, vì vậy anh có thể tạm
bỏ dở một truyện, chuyển sang viết truyện khác sau đó quay lại hoàn thành nốt các
truyện dở dang.
Dĩ nhiên, hai anh em muốn sách được viết càng nhanh càng tốt, tức là phải cực tiểu
hóa thời gian trung bình từ thời điểm hiện tại tới thời điểm lúc tiểu thuyết hoàn thành.
Vì số sách là cố định, nên điều này tương đương với việc cực tiểu hóa tổng thời gian
viết tất cả các cuốn sách. Điều này có nghĩa là nếu cuốn sách thứ i được hoàn thành
vào ngày ci thì c i phải được cực tiểu hóa. Ví dụ, ở ngày thứ nhất Alexei nghĩ ra một
cốt chuyện mà Ivan cần viết trong 5 ngày, Ivan bắt tay viết ngay. Ngày thứ 2 Alexei
nghĩ thêm một cót chuyện mới cần viết trong mộ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 và Ivan quay lại viết tiếp chuyện thứ nhất,
mất thêm 4 ngày nữa, đến ngày thứ 7 chuyện thứ nhất hoàn thành. Tổng các thời điểm
hoàn thành là 3+7 = 10.
Yêucầu: Cho n, ri và pi. Hãy xác định tổng thời gian ngắn nhất hoàn thành tất cả các
truyện.
INPUT:PULP.INP:
- Dòng 1: Chứa số nguyên n (1 ≤ n ≤ 100.000)
- Mỗi dòng trong n dòng sau chứa 2 số nguyên ri và pi.
OUTPUT: PULP.OUT
- Một số nguyên – tổng thời gian ngắn nhất tìm được.
Ví dụ:
PULP.INP PULP.OUT
2 10
127
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
15
21
Thuật toán:
1. Sort tăng theo R[i]
2. Ví dụ với các thời điểm , dễ dàng ta thấy
3. Với mỗi khoảng thời gian t = R[i] – R[i-1], ta ưu tiên giải quyết công việc cần ít
thời gian nhất, giả sử là công việc P (Thấy ngay là 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 hiện công việc P < t thì thực hiện hết công việc P,
thời gian còn lại thực hiện tiếp các công việc có thời gian nhỏ tiếp theo.
b. Nếu thời gian thực hiện công việc P > t thì chúng ta thực hiện công việc
P trong khoảng thời gian t, thời gian còn lại là t[P] – t ta lưu lại trong
Heap để tính tiếp.
4. Sau khi xét đến thời điểm N, lúc này ta sẽ phải làm tất cả các công việc còn lại
tuần tự từ nhỏ đến lớn, hay là lấy tất cả các phần tử trong Heap ra là 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
assign(fi,tfi);reset(fi);
128
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
read(fi,n);
For i := 1 to n do
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 := 2 * i;
if j >nh then exit;
if (j <nh) and (h[j] > h[j+1]) then inc(j);
129
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
t := r[i] - r[i-1];
while (nh> 0) and (t > 0) do
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> 0 do
begin
x := pop;
sum := sum + x;
res := res + sum;
end;
end;
{*------------------------------------------------------------*}
procedure sort(x,y:longint);
var
i,j,key1,key2 : longint;
begin
i := x;
131
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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)) do 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;
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
BEGIN
132
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
randomize;
nhap;
sort(1,n);
xuli;
inkq;
END.
133
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Giới hạn:
6
1 3
Ví dụ
4 5
CEZAR.INP CEZAR.OUT Giải thích
2 8 7
13 3 11
12 Có nhiều phương án 10 13
9
23 giải quyết: Đây là 1
11
28 phương án: 12
134
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 := 1 to n-1 do
Begin
read(fi,u,v);
d[i] := u;
c[i] := v;
inc(deg[u]);
inc(deg[v]);
ENd;
head[1] := deg[1];
For i := 2 to n+1 do
Begin
head[i] := head[i-1] + deg[i];
End;
135
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
For i := 1 to n-1 do
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 := 1 to n do
BEgin
pos[i] := 0;
cost[i] := 1;
End;
nheap := 0;
End;
{===============================}
Procedure downheap(root : longint);
Var
child,u : longint;
BEgin
u := heap[root];
136
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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];
heap[1] := heap[nheap];
137
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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] + 1 to head[v+1] do
Begin
dec(deg[ke[i]]);
cost[ke[i]] := cost[ke[i]] + cost[v];
If deg[ke[i]] = 1 then push(ke[i]);
End;
End;
{===============================}
Function dijkstra(x :longint) : longint ;
Var
i,u : longint;
Begin
push(x);
repeat
u := pop;
138
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
free[u] := false;
update(u);
untilnheap = 0;
End;
{===============================}
Procedure xuly;
Var
i,u : longint;
Begin
khoitao;
res := 0;
For i := 1 to n do
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);
Close(fo);
End;
139
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
{===============================}
BEGIN
nhap;
xuly;
inkq;
END.
6. Bài tập 6: Toy Cars
Bé Tom là một đứa trẻ ba tuổi và rất thích chơi đồ chơi oto. Bé Tom có n chiếc
oto khác nhau, chúng được đặt trên một chiếu giá cao mà Tom không thể tự mình lấy
được. Phòng của Tom cũng rất nhỏ, tại một thời điểm, không thể có nhiều hơn k chiếc
oto đồ chơi ở trên sàn nhà.
Tom chơi với một trong nhưng chiếc oto trên sàn nhà, Mẹ của Ton luôn ở trong
phòng với Tom trong cả thời gian chơi của Tom. Khi Bé Tom muốn chơi với một
chiếc oto khác, nếu chiếc này ở trên sàn nhà, Tom sẽ tự lấy để chơi, còn nếu chiếc oto
này ở trên giá, Mẹ của Tom sẽ lấy xuống cho Tom. (Khi Mẹ của Tom lấy 1 chiếc oto
cho Tom,cùng lúc cô ấy có thể lấy một chiếc oto bất kỳ khác ở sàn nhà để đặt lên giá –
để có đủ khoảng không gian cho k chiếc oto)
Mẹ của Tom là người mẹ rất hiểu ý thích của con mình, cô ta có thể biết được
những chiếc oto nào mà con trai mình muốn chơi. Cô ta muốn biết số lần ít nhất mà cô
ta giúp Tom lấy xe oto từ trên giá.
INPUT: TOYCARS.INP
- Dòng 1: 3 số nguyên dương N, K, P lần
lượt là số lượng oto mà Tom có, số lượng oto có thể đặt trên sàn tại cùng một
thời điểm và độ dài dãy các oto mà Tom muốn chơi. Các oto được đánh số từ 1
đến N.
- P dòng tiếp theo, mỗi dòng 1 số nguyên dương là chiếc oto mà Tom muốn chơi
(theo thứ tự thời gian)
OUTPUT: TOYCARS.OUT
- Một số nguyên duy nhất là số lần ít nhất Mẹ Tom lấy oto từ trên giá xuống.
140
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ví dụ:
TOYCARS.INP TOYCARS.OUT
327 4
1
2
3
1
3
1
2
Thuật toán :
1. Gọi Next[i] = thời điểm gần nhất sau thời điểm i mà bé Tom muốn chơi món đồ
chơi P[i]. Next[i] = nếu i là thời điểm cuối cùng bé Tom muốn chơi món
P[i]. Như vậy có thể coi Next[i] = độ tồi của việc giữ lại món đồ chơi P[i] ở trên
sàn (Next[i] càng lớn tức là việc giữ lại P[i] càng vô ích).
2. Xét lần lượt các thời điểm i :
a. Nếu món P[i] đang nằm trên sàn : bỏ qua và xét thời điểm tiếp theo.
b. Nếu mon P[i] đang nằm trên giá :
i. Nếu sàn chưa đầy : Ta lấy món P[i] để lên sàn.
ii. Nếu sàn đầy : Ta chọn từ sàn một món đồ chơi có độ tồi lớn nhất
và đặt lên giá, sau đó lấy món P[i] để lên sàn.
3. Dễ dàng nhận thấy có thể sử dụng cấu trúc Heap để thực hiện thuật giải trong
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
fi,fo:text;
141
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
read(fi,c[i]);
close(fi);
end;
{------------------------------------------------------------------------}
procedurekhoitao;
vari,j:longint;
begin
res:=0;
for i:=1 to n do
begin
free[i]:=true;
vt[i]:=p+1;
end;
for i:=p downto 1 do
begin
b[i]:=vt[c[i]];
vt[c[i]]:=i;
end;
end;
{------------------------------------------------------------------------}
proceduredoicho(vari,j:longint);
vartg:longint;
142
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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);
begin
143
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
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);
end
144
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 3 số ở hệ cơ số 3 (viết bởi 3 số 0, 1, 2). Hãy tìm một chữ số N ở hệ cơ số 3 sao
cho N có một số lẻ chữ số và số 1 nằm ở chính giữa số N. Số N phải thu được từ việc
liên kết 3 số cho trước, mỗi số trong 3 số có thể được sử dụng 0 hoặc nhiều lần:
Yêu cầu:
Xác định số lượng nhỏ nhất các chữ số của N thỏa mãn tính chất đầu bài
INPUT: BASE3.INP
- Gồm 3 dòng, mỗi dòng là một số ở hệ cơ số 3.
145
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
OUTPUT: BASE3.OUT
- Số lượng chữ số nhỏ nhất của N. Nếu không tồn tại N thì ghi số 0.
Giới hạn
- Số chữ số của một số trong 3 số đầu bài cho trong khoảng từ 1 đến 16.000.
Ví dụ:
BASE3.IN BASE3.OUT
001 13
020
2020
Giải thích
N = 2020001001001 .
Time limit/test: 0.4
L 1 R
thỏa mãn cũng sử dụng cách xây dựng đó được với các cặp
với , chỉ cần lưu 1 cặp có
Quy hoạch động = cách xếp có với
Nếu mỗi lần ta luôn nắp xâu vào nửa ngắn hơn thì luôn đảm bảo
{$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 := 1 to 3 do
begin
readln(fi,s[i]);
len[i] := length(s[i]);
ss := max(ss,len[i]) ;
end;
end;
procedureUpheap(i:longint) ;
var
x,y:longint ;
147
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
x := h[i] ;
y := h2[i];
while (i > 1) and (d[x,y] < d[h[i div 2],h2[i div 2]]) do
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 * 2 <= nh do
begin
j := i * 2 ;
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;
end ;
h[i] := x;
h2[i] := y;
148
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
pos[x,y] := i ;
end;
procedure Push(i,j:longint);
begin
ifpos[i,j] = 0 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 := 1 to 3 do
begin
if s[i][1] = '0' then k := 0 else k := 1;
j := xi + len[i];
if (j <= ss) and (d[j,k] > d[xi,yi] + len[i]) then
149
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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 do
for j := 0 to 1 do
d[i,j] := vc ;
for i := 1 to 3 do
for j := 1 to len[i] do if s[i][j] = '1' then
begin
if s[i][1] = '0' then k := 0 else k := 1;
u := (j - 1) - (len[i] - j) ;
d[u,k] := len[i] ;
push(u,k) ;
end;
end;
procedurexuly;
var
i,j,k: longint;
begin
150
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
whilenh> 0 do
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ố bài tập vận dụng
8. Bài tập 1: BOCSOI13
Bước vào tiểu học, Bé Bi được 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 cả lớp chơi 1 trò chơi sau:
Có N đống sỏi xếp thành một hàng, đống thứ i có Ai viên sỏi. Ta có thể ghép hai
đống sỏi bất kỳ thành một đống và mất một chi phí bằng 5% tổng hai đống sỏi đó. Hãy
tìm cách ghép N đống sỏi này thành một đống với chi phí là nhỏ nhất.
Ví dụ: Nếu chúng ta có 4 đống sỏi với số lượng sỏi là 10, 11, 12 và 13.
- Bước 1: Ghép 2 đống 10 và 11 thành 1 đống có số lượng 21 (chi phí là 1.05)
151
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Bước 2: Ghép đống 21 vừa thu được 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 được với đống 13 thành 1 đống cuối cùng có số
lượng sỏi là 46 (chi phí 2.3)
- Vậy tổng chi phí là 5.00. Tuy nhiên đây không phải là phương án ghép đống tối
ưu, chúng ta có phương án ghép 4 đống này thành 1 đống với chi phí nhỏ nhất
là 4.60.
Các bạn hãy 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 là số đống sỏi.
- Dòng tiếp theo, ghi N số nguyên dương, tương ứng là số lượng sỏi trong từng
đống. Số lượng sỏi không vượt quá 10.000.
OUTPUT:BOCSOI13.OUT:
- Ghi 1 số thực duy nhất là chi phí nhỏ nhất phải trả để ghép N đống sỏi thành 1
đống. Kết quả ghi dưới dạng 2 chữ số sau dấu thập phân.
Ví dụ:
BOCSOI13.INP BOCSOI13.OUT
4 4.60
10 11 12 13
2 0.10
11
152
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
OUTPUT: PENALTY.OUT
Một số duy nhất là số điểm 0 tối thiểu phải nhận
Ví dụ:
PENALTY.INP PENALTY.OUT Giải thích
5 1 4 25 Làm bài 1 từ thời điểm 1 đến thời điểm 2.
2 100 Làm bài 4 từ thời điểm 2 đến thời điểm 3.
2 20 Làm bài 5 từ thời điểm 3 đến thời điểm 4.
4 5 Bài 2 và bài 3 bị nộp muộn
4 10
4 6
10. Bài tập 3: CATUN
Tại vương quốc HT có N thị trấn đánh số từ 1 đến N và M con đường nối 2 thị trấn
bất kỳ. Trong số N thị trấn thì có K thị trấn là pháo đài chống giặc ngoại xâm. Khi có
giặc ngoại xâm, 1 pháo đài bất kỳ sẽ bảo vệ những thị trấn gần nó nhất. Nếu co 1 thị
trấn nào đó có khoảng cách tới 2 pháo đài bằng nhau thì thị trấn này được bảo vệ bởi
pháo đài có chỉ số nhỏ hơn?
INPUT: CATUN.IN
- Dòng 1: N, M và K (
- Dòng 2: K số nguyên dương là chỉ số của K pháo đài
- M dòng tiếp theo, mỗi dòng ghi 3 số x, y, z có nghĩa là có con đường 2 chiều
nối thị trấn x với thị trấn y có chiều dài là z.
OUTPUT: CATUN.OUT
- Ghi N số nguyên dương, số thí I là chỉ số của pháo đài bảo vệ thị trấn i. Nếu thị
trấn I không được bảo vệ bởi pháo đài nào thì ghi số 0
Ví dụ:
CATUN.IN CATUN.OUT
8 9 2 5 0 5 0 0 5 0 2
2 5
1 3 6
1 5 3
1 6 1
153
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
2 3 9
5 6 5
6 8 7
3 6 2
4 7 1000
2 8 5
Time limit: 0.2s
11. Bài tập 3: Barbar
Sau cuộc chiến tranh thần thánh, HT bị bắt giam vào ngục tối là một bảng cỡ R*C (R
dòng và C cột). Tại một ô có thể di chuyển sang 4 ô kề cạnh. Trong ngục có 1 số ô là
có thể di chuyển tự do, một số ô là tường không thể di chuyển qua và đặc biệt một số ô
bị chiếm giữ bởi các con rồng lửa (không thể đi lại gần nếu không muốn bị cháy thành
than). Bạn hãy giúp HT di chuyển qua ngục tối sao cho khoảng cách ngắn nhất đến các
con rồng lửa là lớn nhất?
INPUT: BARBAR.IN
- Dòng 1: R và C (
- R dòng tiếp theo, mỗi dòng ghi C ký tự biểu diễn ngục tối.
“.” Ô di chuyển tự do
“*” Tường
“D” Rồng lửa
“I”: Vị trí xuất phát của HT
“O”: Vị trí thoát khỏi ngục tối
OUTPUT: BARBAR.OUT
- Một số duy nhất là giá trị lớn nhất của khoảng cách ngắn nhất đến các con rồng
lửa khi HT di chuyển ra 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 2 ..........
.......... .Iooo.D...
.I....D... ....o.....
.......... ..D.o.D...
154
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
..D...D... .*..oo....
.*........ D*...ooooo
D*........ *...D....o
*...D..... ..****...o
..****.... ...Ooooooo
...O...... ..........
..........
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 các tàu cập bến. Tại một thời điểm, mỗi cầu
cảng chỉ có thể tiếp nhận không quá 1 tàu. Ban đầu các cầu cảng đều trống và 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 là một khoảng trên trục
thời gian. Tàu đã vào cầu cảng nào thì sẽ đậu ở đó trong 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, có thể tiếp nhận được tối đa bao nhiều
tàu và chỉ ra lịch trình tiếp nhận tại mỗi cầu cảng ?
INPUT:SEAPORTS.INP
- Dòng 1: Chứa 2 số nguyên dương
- N dòng tiếp theo, dòng thứ i chứ 2 số nguyên
OUTPUT:SEAPORTS.OUT
- Dòng 1 : Ghi số lượng tàu được tiếp nhận phục vụ
- Dòng 2 : Ghi n số nguyên, số thứ i là số hiệu cầu cảng sẽ tiếp nhận tàu thứ i
trong trường hợp tàu thứ i được tiếp nhận, còn nếu tàu thứ i không được tiếp
nhận thì số thứ i là 0.
Ví dụ :
SEAPORTS.INP SEAPORTS.OUT
25 4
03 11220
35
02
25
155
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
14
156
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bạn hãy tìm cách đi từ mặt đất xuống phòng của Bin Laden nhanh nhất không hắn
thoát mất.
INPUT: BINLADEN.INP
- Dòng 1 ghi M và N
- Dòng 2 đến 2M + 1, dòng chẵn ghi N số, dòng lẻ ghi N - 1 số là chi phí để phá
cửa. Chi phí của các cánh cửa thuộc [0, 1000].
OUTPUT: BINLADEN.OUT
- Ghi ra 1 số là thời gian nhỏ nhất để đến được phòng của Bin Laden
Ví dụ:
BINLADEN.INP BINLADEN.OUT
42 44 +--99--+--10--+
99 10 | | |
1 | 1 |
10 99 | | |
1 +--10--+--99--+
99 10 | | |
1 | 1 |
10 99 | | |
1 +--99--+--10--+
| | |
| 1 |
| | |
+--10--+--99--+
| | |
| 1 |
| | |
+------+------+
Đi theo đường zigzac
157
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
158
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Cấu trúc dữ liệu tập rời rạc (Union-Find Disjoint Sets) là một cấu trúc dữ liệu mô tả
một tập hợp của các tập con rời rạc sao cho trên cấu trúc này ta có thể cài đặt hai phép
toán sau với độ phức tạp xấp xỉ O(1):
2. Cài đặt
Ý tưởng chính cho cấu trúc dữ liệu này là sử dụng một rừng cây để biểu diễn các tập
rời rạc, trong đó mỗi cây đại diện cho một tập. Và ứng với mỗi tập, ta sẽ biểu diễn cây
tương ứng bằng cách chọn một phần tử làm đại diện cho tập đó (nút gốc của cây).
Để biểu diễn các nút trong một cây thì thông tin của mỗi nút trong cây sẽ được giữ
trong một vector pSet (thông tin về nút cha của nó trong cây), trong đó pSet[i] = j cho
biết nút thứ thuộc cây chứa nút thứ j (một các hiểu khác là phần tử thứ i sẽ 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 sẽ tạo một tập hợp gồm n tập rời rạc mà mỗi tập ban đầu
chỉ chứa đúng một phần tử.
void initSet(int n) {
pSet.resize(n);
159
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Để xác định một 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ử đó trong cây để đi tìm nút gốc của cây, trong quá trình gọi đệ quy ta tiến hành điều
chỉnh luôn thông tin được lưu trữ của nút đó để sao cho sau khi kết thúc quá trình gọi
đệ quy mỗi nút đi qua thông tin được lưu trong pSet tương ứng sẽ là nút gốc của cây,
điều này giúp cho độ phức tạp của việc xác định ở các lần tiếp theo sẽ là O(1).
int findSet(int i) {
Để hợp 2 tập, ta chỉ đơn giản thay đổi thông tin nút gốc của một tập (hiện tại có giá trị
bằng chính nó) thành thông tin nút gốc của tập còn lại.
pSet[findSet(i)] = findSet(j);
Để xác định xem 2 phần tử có thuộc cùng một tập hay không, đơn giản ta chỉ kiểm tra
xem nút gốc của 2 cây chứa 2 phần tử có trùng nhau hay không?
3. Cải tiến
Một cách cải tiến để cấu trúc dữ liệu có thể hoạt động hiệu quả hơn: khi lưu trữ thông
tin của một nút trong cây, ngoài việc sử dụng vector pSet ta sử dụng thêm một vector
rSet để lưu độ cao của cây, rSet[i] cho biết độ cao của cây với i là nút gốc. Điều này
hữu ích cho ta khi tiến hành hợp 2 tập, lúc này ta luôn chọn tập thuộc cây có độ cao
nhỏ hơn để gắn vào tập thuộc cây có độ cao lớn hơn.
vector<int> pSet(1000);
160
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
vector<int> rSet(1000);
void initSet(int n) {
pSet.resize(n);
rSet.resize(n);
pSet[i] = i;
rSet[i] = 0;
int findSet(int i) {
if (!isSameSet(i, j)) {
int x = findSet(i);
161
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
int y = findSet(j);
else {
pSet[x] = y;
Cấu trúc dữ liệu này thường được ứng dụng trong thuật toán Kruskal hoặc các bài toán
liên quan đến phân vùng trong đồ thị mà cần lưu vết lại các thành phần liên thông của
chúng.
...
sort(edge.begin(), edge.end());
initSet(n);
int ans = 0;
mst.pb(edge[i]);
162
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
ans += w;
unionSet(u, v);
...
Bình được giao nhiệm vụ quản trị một mạng máy tính của công ty Gbum. Hiện tại bạn
ấy đang giữ một bản ghi các kết nối hai chiều giữa các máy tính trong mạng. Hai máy
tính liên thông với nhau nếu giữa chúng có một đường truyền trực tiếp hoặc chúng liên
thông với cùng một máy khác. Một hôm, Bình muốn biết một cách nhanh chóng rằng
khi cho hai máy tính bất kỳ trong mạng có liên thông với nhau hay không khi biết
được thông tin về các kết nối của mạng máy tính.
Yêu cầu: Viết chương trình đếm số lượng các câu trả lời “CÓ” và số lượng các 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 trong file văn bản network.inp có cấu trúc như sau:
Dòng đầu ghi 2 số nguyên dương n, m cho biết số lượng máy tính có trong
mạng và số lượng truy vấn.
m dòng tiếp theo mỗi dòng là một truy vấn thuộc một trong hai dạng sau:
o Dạng 1: c i j cho biết máy tính i được 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 cập nhật mỗi khi xuất hiện truy vấn dạng 1, câu trả lời cho
truy vấn dạng 2 được áp dụng đối với thông tin mạng máy tính hiện tại.
Kết quả: ghi ra file văn bản network.out chỉ gồm 2 số, số đầu tiên cho biết số lượng
câu trả lời “CÓ” và số thứ hai cho biết số lượng các câu trả lời “KHÔNG”.
Ví dụ:
network.inp network.out
163
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
10 7 12
c15
c27
q71
c39
q96
c25
q75
Một thị trấn nọ có n dân và các cư dân trong thành phố này nổi tiếng là thân thiện nên
nếu A là bạn của B và B là bạn của C thì A sẽ là bạn của C.
Yêu cầu: Cho thông tin một số cặp là bạn của nhau hãy xác định xem nhóm có số
lượng lớn nhất là bạn bè.
Dữ liệu vào: Cho trong file văn bản friend.inp có cấu trúc như sau:
Dòng đầu tiên ghi 2 số nguyên dương n và m cho biết số lượng cư dân và số cặp
là bạn của nhau.
m dòng tiếp theo, mỗi dòng ghi 2 số nguyên dương a và b cho biết a và b là bạn
của nhau.
Kết quả: Ghi ra file văn bản friend.out chỉ 1 số duy nhất là số lượng lớn nhất tìm
được.
Ví dụ:
friend.inp friend.out
10 12 6
12
164
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
31
34
54
35
46
52
21
7 10
12
9 10
89
[2] https://uva.onlinejudge.org/
[3] http://www.spoj.com/
165
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
A. Lý thuyết
Bài toán cây khung nhỏ nhất của đồ thị là một trong số những bài toán tối ưu trên
đồ thị tìm được ứng dụng trong nhiều lĩnh vực khác nhau của đời sống. Trong mục này
chúng ta trình bày những thuật toán cơ bản để giải bài toán này. Trước hết chúng ta
phát biểu nội dung bài toán.
Cho G =(V,E) là đồ thị vô hướng liên thông với tập đỉnh V ={1, 2, . . . ,n} và tập
cạnh E gồm m cạnh. Mỗi cạnh E của đồ thị G được gán với một số không âm c(e), gọi
là độ dài của nó. Giả sử H=(V,T) là cây khung của đồ thị G. Ta gọi độ dài c(H) của
cây khung H là tổng độ dài các cạnh của nó.
Bài toán đặt ra là trong tất cả cây khung của đồ thị G hãy tìm cây khung với độ
dài nhỏ nhất. Cây khung như vậy như vậy được gọi là cây khung nhỏ nhất của đồ thị
và bài toán đặt ra được gọi là bài toán cây khung nhỏ nhất.
Thuật toán Kruskal dựa trên mô hình xây dựng cây khung bằng thuật toán hợp
nhất (§5), chỉ có điều thuật toán không phải xét các cạnh với thứ tự tuỳ ý mà xét các
cạnh theo thứ tự đã sắp xếp: Với đồ thị vô hướng G = (V, E) có n đỉnh. Khởi tạo cây T
ban đầu không có cạnh nào. Xét tất cả các cạnh của đồ thị từ cạnh có trọng số nhỏ đến
cạnh có trọng số lớn, nếu việc thêm cạnh đó vào T không tạo thành chu trình đơn trong
T thì kết nạp thêm cạnh đó vào T. Cứ làm như vậy cho tới khi:
❖ Hoặc đã kết nạp được n - 1 cạnh vào trong T thì ta được T là cây khung nhỏ nhất
❖ Hoặc chưa kết nạp đủ n - 1 cạnh nhưng hễ cứ kết nạp thêm một cạnh bất kỳ
trong số các cạnh còn lại thì sẽ tạo thành chu trình đơn. Trong trường hợp này
đồ thị G là không liên thông, việc tìm kiếm cây khung thất bại.
166
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
1.1.1.1. Như vậy có hai vấn đề quan trọng khi cài đặt thuật toán Kruskal:
Thứ nhất, làm thế nào để xét được các cạnh từ cạnh có trọng số nhỏ tới cạnh có
trọng số lớn. Ta có thể thực hiện bằng cách sắp xếp danh sách cạnh theo thứ tự không
giảm của trọng số, sau đó duyệt từ đầu tới cuối danh sách cạnh. Trong trường hợp tổng
quát, thuật toán HeapSort là hiệu quả nhất bởi nó cho phép chọn lần lượt các cạnh từ
cạnh trọng nhỏ nhất tới cạnh trọng số lớn nhất ra khỏi Heap và có thể xử lý (bỏ qua
hay thêm vào cây) luôn.
Thứ hai, làm thế nào kiểm tra xem việc thêm một cạnh có tạo thành chu trình đơn
trong T hay không. Để ý rằng các cạnh trong T ở các bước sẽ tạo thành một rừng (đồ
thị không có chu trình đơn). Muốn thêm một cạnh (u, v) vào T mà không tạo thành
chu trình đơn thì (u, v) phải nối hai cây khác nhau của rừng T, bởi nếu u, v thuộc cùng
một cây thì sẽ tạo thành chu trình đơn trong cây đó. Ban đầu, ta khởi tạo rừng T gồm n
cây, mỗi cây chỉ gồm đúng một đỉnh, sau đó, mỗi khi xét đến cạnh nối hai cây khác
nhau của rừng T thì ta kết nạp cạnh đó vào T, đồng thời hợp nhất hai cây đó lại
thành một cây.
Nếu cho mỗi đỉnh v trên cây một nhãn Lab[v] là số hiệu đỉnh cha của đỉnh v
trong cây, trong trường hợp v là gốc của một cây thì Lab[v] được gán một giá trị âm.
Khi đó ta hoàn toàn có thể xác định được gốc của cây chứa đỉnh v bằng hàm GetRoot
dưới đây:
function GetRoot(vÎV):
ÎV; begin
end;
Vậy để kiểm tra một cạnh (u, v) có nối hai cây khác nhau của rừng T hay không?
ta có thể kiểm tra GetRoot(u) có khác GetRoot(v) hay không, bởi mỗi cây chỉ có duy
nhất một gốc.
Để hợp nhất cây gốc r1 và cây gốc r2 thành một cây, ta lưu ý rằng mỗi cây ở đây
chỉ dùng để ghi nhận một tập hợp đỉnh thuộc cây đó chứ cấu trúc cạnh trên cây thế nào
167
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
thì không quan trọng. Vậy để hợp nhất cây gốc r1 và cây gốc r2, ta chỉ việc coi r1 là
nút cha của r2 trong cây bằng 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 cây 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
cây lại thành một, thì gốc cây nào ít nút hơn sẽ bị coi là con của gốc cây kia.
Thuật toán hợp nhất cây gốc r1 và cây gốc r2 có thể viết như sau:
{Count[k]làsốđỉnhcủacâygốck}
procedure Union(r1, r2
Î V); begin
if Count[r1] < Count[r2] then {Hợp nhất thành cây gốc r2}
begin
Count[r2] := Count[r1] +
Count[r2]; Lab[r1] := r2;
end
else{Hợpnhấtthànhcâygốcr1}
begin
Count[r1] := Count[r1] +
Count[r2]; Lab[r2] := r1;
end;
end;
Khi cài đặt, ta có thể tận dụng ngay nhãn Lab[r] để lưu số đỉnh của cây gốc r, bởi
như đã giải thích ở trên, Lab[r] chỉ cần mang một giá trị âm là đủ, vậy ta có thể coi
Lab[r] = -Count[r] với r là gốc của một cây nào đó.
168
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
x:=Lab[r1]+Lab[r2];{-xlàtổngsốnútcủacảhaicây}
ifLab[r1]>Lab[r2]then{Câygốcr1ítnúthơncâygốcr2,hợpnhấtthànhcâygốcr2}
begin
Lab[r1]:=r2;{r2 làchacủar1}
Lab[r2]:=x;{r2 làgốccâymới,-Lab[r2]giờđâylàsốnúttrongcâymới}
end
else{Hợpnhấtthànhcâygốcr1}
begin
Lab[r1]:=x;{r1 làgốccâymới,-Lab[r1]giờđâylàsốnúttrongcâymới}
Lab[r2]:=r1;{chacủar2 sẽlàr1}
end;
end;
for"kÎVdoLab[k]:=-1;
r1 := GetRoot(u); r2 := GetRoot(v);
begin
<Kết nạp (u, v) vào cây, nếu đã đủ n - 1 cạnh thì thuật toán dừng>
Union(r1,r2);{Hợpnhấthaicâylạithànhmộtcây}
end;
169
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
end;
Xét về độ phức tạp tính toán, ta có thể chứng minh được rằng thao tác GetRoot
có độ phức tạp là O(lgn), còn thao tác Union là O(1). Giả sử ta đã có danh sách cạnh
đã sắp xếp rồi thì xét vòng lặp dựng cây khung, nó duyệt qua danh sách cạnh và với
mỗi cạnh nó gọi 2 lần thao tác GetRoot, vậy thì độ phức tạp là O(mlgn), nếu đồ thị có
cây khung thì m ³ n-1 nên ta thấy chi phí thời gian chủ yếu sẽ nằm ở thao tác sắp xếp
danh sách cạnh bởi độ phức tạp của HeapSort là O(mlgm). Vậy độ phức tạp tính toán
của thuật toán là O(mlgm) trong trường hợp xấu nhất. Tuy nhiên, phải lưu ý rằng để
xây dựng cây khung thì ít khi thuật toán phải duyệt toàn bộ danh sách cạnh mà chỉ một
phần của danh sách cạnh mà thôi.
Thuật toán Kruskal hoạt động chậm trong 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 nhất của Prim.
Thuật toán đó có thể phát biểu hình thức như sau:
Đơn đồ thị vô hướng G = (V, E) có n đỉnh được cho bởi ma trận trong số C. Qui
ước c[u, v] =
+¥ nếu (u, v) không là cạnh. Xét cây T trong G và một đỉnh v, gọi khoảng cách từ
v tới T là trọng số nhỏ nhất trong số các cạnh nối v với một đỉnh nào đó trong T:
Ban đầu khởi tạo cây T chỉ gồm có mỗi đỉnh {1}. Sau đó cứ chọn trong số các
đỉnh ngoài T ra một đỉnh gần T nhất, kết nạp đỉnh đó vào T đồng thời kết nạp luôn cả
cạnh tạo ra khoảng cách gần nhất đó. Cứ làm như vậy cho tới khi:
❖ Hoặc đã kết nạp được tất cả n đỉnh thì ta có T là cây khung nhỏ nhất
❖ Hoặc chưa kết nạp được hết n đỉnh nhưng mọi đỉnh ngoài T đều có khoảng
cách tới T là
+¥. Khi đó đồ thị đã cho không liên thông, ta thông báo việc tìm cây khung thất
bại.
170
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Về mặt kỹ thuật cài đặt, ta có thể bắt đầu từ một cây rỗng và khởi tạo d[1] := 0
còn d[v] := +¥ với "v ¹ 1. Tại mỗi bước chọn đỉnh đưa vào T, ta sẽ chọn đỉnh u nào
ngoài T và có d[u] nhỏ nhất. Khi kết nạp u vào T rồi thì các nhãn d[v] sẽ thay đổi:
d[v]mới := min(d[v]cũ, c[u, v]). Bước lặp đầu tiên sẽ kết nạp đỉnh 1 vào cây, từ bước
lặp thứ hai, trước khi kết nạp một đỉnh vào cây, ta lưu lại cạnh tạo ra khoảng cách gần
nhất từ cây tới đỉnh đó để cuối cùng in ra cây khung nhỏ nhất.
B.Ví dụ:
1. Ví dụ 1:
Cho G = (V, E) là đồ thị vô hướng liên thông có trọng số, với một cây khung T
của G, ta gọi trọng số của cây T là tổng trọng số các cạnh trong T. Bài toán đặt ra là
trong số các cây khung của G, chỉ ra cây khung có trọng số nhỏ nhất, cây khung như
vậy được gọi là cây khung nhỏ nhất của đồ thị (minimum spanning tree) và bài toán đó
gọi là bài toán cây khung nhỏ nhất. Sau đây ta sẽ xét hai thuật toán thông dụng để giải
bài toán cây khung nhỏ nhất của đơn đồ thị vô hướng có trọng số. Bạn có thể đưa vào
một số sửa đổi nhỏ trong thủ tục nhập liệu để giải quyết bài toán trong trường hợp đa
đồ thị.
❖ Dòng 1: Ghi hai số số đỉnh n (≤ 1000) và số cạnh m của đồ thị cách nhau ít nhất 1
dấu cách
❖ m dòng tiếp theo, mỗi dòng có dạng 3 số u, v, c[u, v] cách nhau ít nhất 1 dấu
cách thể hiện đồ thị có cạnh (u, v) và trọng số cạnh đó là c[u, v]. (c[u, v] là số
nguyên có giá trị tuyệt đối không quá 1000).
Output: file văn bản MINTREE.OUT ghi các cạnh thuộc cây khung và trọng số cây
khung
MINTREE.INP MINTREE.OUT
171
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
69 Minimum
121 spanning tree:
1 31 (2, 4) = 1
241 (3, 6) = 1
232 (2, 5) = 1
251 (1, 3) = 1
351 (1, 2) = 1
361 Weight= 5
452
562
Cách giải: áp dụng thuật toán Kruskal như trình bày ở trên.
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;
procedure input;
172
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
assign(f,fi);
reset(f);
readln(f,n,m);
close(f);
end;
var
i,j: longint;
x,y:longint;
begin
i:=l;
j:=r;
repeat
while a[i].c<x do
inc(i);
while x<a[j].c do
dec(j);
if not(i>j) then
begin
173
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
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
root[v]:=getroot(root[v]);
174
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
exit(root[v]);
end;
procedure kruskal;
var r1,r2:longint;
begin
con:=false;
count:=0;
for i:=1 to m do
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;
root[r1]:=r2;
175
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
end;
end;
end;
procedure output;
begin
assign(f,fo);
rewrite(f);
else
begin
count:=0;
w:=0;
for i:=1 to m do
begin
if a[i].kn then
begin
inc(count);
w:=w+a[i].c;
end;
end;
176
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
write(f,w);
end;
close(f);
end;
begin
input;
sort(1,m);
kruskal;
output;
end.
2.Ví dụ 2:
Nước Anpha đang lập kế hoạch xây dựng một thành phố mới và hiện đại. Theo
kế hoạch, thành phố sẽ có N vị trí quan trọng, được gọi là N trọng điểm và các trọng
điểm này được đánh số từ 1 tới N. Bộ giao thông đã lập ra một danh sách M tuyến
đường hai chiều có thể xây dựng được giữa hai trọng điểm nào đó. Mỗi tuyến đường
có một thời gian hoàn thành khác nhau.
Các tuyến đường phải được xây dựng sao cho N trọng điểm liên thông với nhau.
Nói cách khác, giữa hai trọng điểm bất kỳ cần phải di chuyển được đến nhau qua một
số tuyến đường. Bộ giao thông sẽ chọn ra một số tuyến đường từ trong danh sách ban
đầu để đưa vào xây dựng sao cho điều kiện này được thỏa mãn.
Do nhận được đầu tư rất lớn từ chính phủ, bộ giao thông sẽ thuê hẳn một đội thi
công riêng cho mỗi tuyến đường cần xây dựng. Do đó, thời gian để hoàn thành toàn bộ
các tuyến đường cần xây dựng sẽ bằng thời gian lâu nhất hoàn thành một tuyến đường
nào đó.
Yêu cầu: Giúp bộ giao thông tính thời gian hoàn thành các tuyến đường sớm nhất
thỏa mãn yêu cầu đã nêu.
177
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
1.1.2. Dữ liệu
M tiếp theo, mỗi dòng chứa ba số nguyên u, v và t cho biết có thể xây dựng tuyến
đường nối giữa trọng điểm u và trọng điểm v trong thời gian t. Không có hai tuyến
đường nào nối cùng một cặp trọng điểm.
Một số nguyên duy nhất là thời gian sớm nhất hoàn thành các tuyến đường thỏa
mãn yêu cầu đã nêu.
1.1.4. Ví dụ
57 3
122
151
251
143
132
532
344
Cách giải: Một bài toán cây khung nhỏ nhất cơ bản. Ở đây mình dùng thuật
toán Kruskal + Heap sort. Chi phí lớn nhất chính là cạnh thứ n-1 của cây khung khi
kết nạp vào.
Code:
Const
178
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
maxN =1000;
maxM =10000;
Type
TEdge =Record
u,v,c :SmallInt;
end;
Var
n,m,res :SmallInt;
E :Array[1..maxM] of TEdge;
procedure Enter;
var
i :SmallInt;
begin
Read(n,m);
for i:=1 to m do
end;
procedure Init;
var
i :SmallInt;
begin
179
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
end;
begin
Exit(x);
end;
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;
180
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
var
Key :TEdge;
child :SmallInt;
begin
Key:=E[root];
while (root*2<=leaf) do
begin
child:=root*2;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
procedure Greedy;
var
count,i,r1,r2 :SmallInt;
Tmp :TEdge;
begin
count:=0;
181
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
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;
Greedy;
182
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Write(res);
Close(Input); Close(Output);
End.
3.Ví dụ 3:
Nông dân John quyết định mang nước tới cho N (1 <= N <= 300) đồng cỏ của
mình, để thuận tiện ta đánh số các đồng cỏ từ 1 đến N. Để tưới nước cho 1 đồng cỏ
John có thể chọn 2 cách, 1 là đào ở đồng cỏ đó 1 cái giếng hoặc lắp ống nối dẫn nước
từ những đồng cỏ trước đó đã có nước tới.
Để đào một cái giếng ở đồng cỏ i cần 1 số tiền là W_i (1 <= W_i <= 100,000).
Lắp ống dẫn nước nối 2 đồng cỏ i và j cần 1 số tiền là P_ij (1 <= P_ij <= 100,000; P_ij
= P_ji; P_ii=0).
Tính xem nông dân John phải chi ít nhất bao nhiêu tiền để tất cả các đồng cỏ đều
có nước.
1.1.5. DỮ LIỆU
Dòng 1: Một số nguyên duy nhất là chi phí tối thiểu để cung cấp nước cho tất cả
các đồng cỏ.
1.1.7. VÍ DỤ
183
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
4
3
0222
2033
2304
2340
1.1.8. GIẢI THÍCH
Có 4 đồng cỏ. Mất 5 tiền để đào 1 cái giếng ở đồng cỏ 1, 4 tiền để đào ở đồng cỏ
2, 3 và 3 tiền để đào ở đồng cỏ 4. Các ống dẫn nước tốn 2, 3, và 4 tiền tùy thuộc vào
nó nối đồng cỏ nào với nhau.
Nông dân John có thể đào 1 cái giếng ở đồng cỏ thứ 4 và lắp ống dẫn nối đồng cỏ
1 với tất cả 3 đồng cỏ còn lại, chi phí tổng cộng là 3 + 2 + 2 + 2 = 9.
Cách giải: Dùng thuật toán Prim. Tạo sẵn một đỉnh ảo 0 là đỉnh bắt đầu để kết nạp
các cạnh. Tương ứng C[0, i] là chi phí để đặt giếng nước tại cánh đồng i. Kết quả là
tổng các D[i].
Code:
Const
oo =1000000001;
Var
n :Integer;
D :Array[0..300] of LongInt;
C :Array[0..300,0..300] of LongInt;
procedure Enter;
var
184
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
i,j :Integer;
begin
Read(n);
FillChar(Free,SizeOf(Free),true);
for i:=1 to n do
end;
procedure Optimize;
var
i,u,v :Integer;
min :LongInt;
begin
for i:=0 to n do
begin
min:=High(LongInt); u:=-1;
for v:=0 to n do
begin
min:=D[v]; u:=v;
end;
185
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Free[u]:=false;
for v:=1 to n do
end;
end;
procedure Escape;
var
i :Integer;
res :LongInt;
begin
res:=0;
Write(res);
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Optimize;
Escape;
Close(Input); Close(Output);
186
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
End.
4.Ví dụ 4:
Singapore sẽ tổ chức một cuộc đua xe Công Thức 1 vào năm 2008. Trước khi
cuộc đua diễn ra, đã xuất hiện một số cuộc đua về đêm trái luật. Chính quyền muốn
thiết kế một hệ thống kiểm soát giao thông để bắt giữ các tay đua phạm luật. Hệ thống
bao gồm một số camera đặt trên các tuyến đường khác nhau. Để đảm bảo tính hiệu quả
cho hệ thống, cần có ít nhất một camera dọc theo mỗi vòng đua.
Hệ thống đường ở Singapore có thể được mô tả bởi một dãy các nút giao thông
và các đường nối hai chiều (xem hình vẽ). Một vòng đua bao gồm một nút giao thông
xuất phát, tiếp theo là đường đi bao gồm ít nhất 3 tuyến đường và cuối cùng quay trở
lại điểm xuất phát. Trong một vòng đua, mỗi tuyến đường chỉ được đi qua đúng một
lần, theo đúng một hướng.
Chi phí để đặt camera phụ thuộc vào tuyến đường được chọn. Các số nhỏ trong
hình vẽ cho biết chi phí để đặt camera lên các tuyến đường. Các số lớn xác định các
nút giao thông. Camera được đặt trên các tuyến đường chứ không phải tại các nút giao
thông. Bạn cần chọn một số tuyến đường sao cho chi phí lắp đặt là thấp nhất đồng thời
vẫn đảm bảo có ít nhất một camera dọc theo mỗi vòng đua.
Viết chương trính tìm cách đặt các camera theo dõi giao thông sao cho tổng chi
phí lắp đặt là thấp nhất.
1.1.9. Dữ liệu
In ra 1 số nguyên duy nhất là tổng chi phí lắp đặt thất nhất tìm được.
187
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
1.1.11. Ví dụ
Kết quả bằng : Tổng trọng số của đồ thị – Tổng trọng số của cây khung lớn nhất.
Giải thích : Do tính chất của cây khung là khi thêm 1 cạnh ngoài cây vào ta thu được
đúng một chu trình đơn. Vì vậy chọn ra cây khung lớn nhất, những cạnh còn lại là
những cạnh nhỏ nhất. Lắp camera vào các cạnh còn lại này đảm bảo mọi chu trình
trong đồ thị đều phải đi qua một trong các cạnh này, tức là ta không bỏ lỡ một vòng
đua nào cả.
Code:
Const
maxN =10000;
maxM =100000;
Type
TEdge =Record
u,v,c :SmallInt;
end;
188
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Var
n :SmallInt;
m,sum,res :LongInt;
E :Array[1..maxM] of TEdge;
procedure Enter;
var
i :LongInt;
begin
Read(n,m); sum:=0;
for i:=1 to m do
with (E[i]) do
begin
Read(u,v,c); Inc(sum,c);
end;
end;
procedure Init;
var
i :SmallInt;
begin
end;
189
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
Exit(x);
end;
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;
var
190
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Key :TEdge;
child :LongInt;
begin
Key:=E[root];
while (root*2<=leaf) do
begin
child:=root*2;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
procedure Greedy;
var
Tmp :TEdge;
r1,r2 :SmallInt;
i,count :LongInt;
begin
count:=0; res:=0;
191
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
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);
Union(r1,r2);
end;
end;
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Init;
Greedy;
Write(sum-res);
Close(Input); Close(Output);
192
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
End.
5. Ví dụ 5:
Bác John dạo này lười đến nỗi không muốn bảo trì các con đường dẫn bác đến
thăm N (5 <= N <= 10,000) cánh đồng (đánh số từ 1 đến N) nữa. Mỗi cánh đồng là nơi
ở của một cô bò. Bác John có kế hoạch loại bỏ nhiều nhất P (N-1 <= P <= 100,000)
con đường sao cho các cánh đồng vẫn liên thông.
Ban phải xác định N-1 con đường cần giữ lại.
Đường nối hai chiều j nối giữa cánh đồng Sj và Ej (1 <= Sj <= N; 1 <= Ej <= N;
Sj # Ej) và cần Lj (0 <= Lj <= 1000) thời gian để di chuyển. Không có hai cánh đồng
nào được nối trực tiếp bởi nhiều hơn một con đường.
Đàn bò buồn vì hệ thống giao thông của chúng sắp bị rút gọn. Bạn phải thăm mỗi
cô bò ít nhất một lần trong ngày để động viên. Mỗi lần thăm cánh đồng i (dù chỉ đi
ngang qua), bạn phải trò chuyện với cô bò trong thời gian Ci (1 <= Ci <= 1000).
Bạn sẽ nghỉ lại đêm trên cùng một cánh đồng (bạn sẽ được chọn) cho đến khi đàn
bò đều đã hết bị suy sụp. Bạn sẽ trò chuyện với cô bò trong cánh đồng mà bạn nghỉ lại
ít nhất 2 lần vào buổi sáng thức dậy và vào buổi tối khi trở về nghỉ.
Giả dụ bác John theo lời khuyên của bạn giữ lại một số con đường và bạn sẽ chọn
cánh đồng tối ưu nhất để nghỉ lại, hãy xác định thời gian nhỏ nhất bạn cần để thăm tất
cả đàn bò ít nhất một lần trong ngày.
1.1.12. Dữ liệu
* Dòng 1: Một số nguyên duy nhất, tổng thời gian cần để thăm tất cả đàn bò (bao gồm
hai lần thăm cô bò ở nơi mà bạn nghỉ).
1.1.14. Ví dụ
10 235 356
20 2 4 12 4 5 12
6
Cách giải:
Nhìn hình vẽ, nhận thấy rằng với mỗi cây khung được chọn, mỗi cạnh trong cây
khung đều đi qua đúng hai lần và mỗi đỉnh đi qua đúng số cạnh kề với nó (chỉ tính
những cạnh được chọn). Ví dụ trong hình trên cạnh 1-2 được chọn và giả sử bắt đầu
bằng bất kì đỉnh nào thì cạnh này đi qua đúng hai lần, đi và về; đỉnh 1 sẽ thăm đúng
một lần (không kể trường hợp bắt đầu bằng 1 thì phải thăm hai lần), đỉnh 2 thăm đúng
3 lần (có 3 cạnh kề tô màu tím, và đường nhiên không tính đỉnh bắt đầu là 2). Riêng
đỉnh bắt đầu trong cây khung sẽ thăm thêm một lần là ban đầu đã thăm ở nó.Từ nhận
xét trên ta gán trọng số cạnh (u,v) từ w thành w * 2 + c[u] + c[v]. Khi đó khi ta chọn
cạnh này ta đã tính luôn 2 lần đi qua nó và số lần thăm u, v (các bạn có thể test tay để
nhận ra sự đúng đắn của thuật toán). Lúc này chỉ cần tìm cây khung nhỏ nhất và sau đó
chọn đỉnh bắt đầu nhỏ nhất.
Code:
Uses Math;
Const
maxN =10000;
maxM =100000;
Type
194
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
TEdge =Record
u,v,c :SmallInt;
end;
Var
n :SmallInt;
m :LongInt;
E :Array[1..maxM] of TEdge;
res :Int64;
procedure Enter;
var
i :LongInt;
begin
Read(n,m);
for i:=1 to m do
with (E[i]) do
begin
Read(u,v,c);
c:=2*c+A[u]+A[v];
end;
end;
195
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
procedure Init;
var
i :SmallInt;
begin
end;
begin
Exit(x);
end;
var
x :SmallInt;
begin
x:=Lab[r1]+Lab[r2];
if (Lab[r1]>Lab[r2]) then
begin
Lab[r1]:=r2; Lab[r2]:=x;
end
else
196
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
Lab[r1]:=x; Lab[r2]:=r1;
end;
end;
var
Key :TEdge;
child :LongInt;
begin
Key:=E[root];
while (root*2<=leaf) do
begin
child:=root*2;
E[root]:=E[child];
root:=child;
end;
E[root]:=Key;
end;
procedure Greedy;
197
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
var
Tmp :TEdge;
i,count :LongInt;
r1,r2 :SmallInt;
begin
count:=0; res:=0;
begin
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);
Union(r1,r2);
end;
end;
end;
198
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
procedure Escape;
var
i,minV :SmallInt;
begin
minV:=A[1];
Write(res+minV);
end;
Begin
Assign(Input,''); Reset(Input);
Assign(Output,''); Rewrite(Output);
Enter;
Init;
Greedy;
Escape;
Close(Input); Close(Output);
End.
199
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ngoài ra bạn có thể tham khảo thêm những cuốn sách sau đây:
200
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
1. Giới thiệu
1- Trong những năm gần đây, trong các kỳ thi học sinh giỏi loại cấu trúc dữ
liệu Segment Tree đã được sử dụng rất nhiều đặc biệt là trong những bài toán xử lý
trên dãy số, nó cũng là một loại cấu trúc dữ liệu trừu tượng, khó hiểu, tuy nhiên, tính
hiệu quả thì tương đối cao. Do cây IT được viết rất nhiều trên các tài liệu và khá hay,
vì vậy tôi cũng không dám thay đổi, sửa chữa gì thêm mà chỉ nêu sơ phần lý thuyết và
bổ sung một vài bài tập đơn giản cho các thầy cô mới giảng dạy hay học sinh mới học
tham khảo, nếu cần tham khảo rõ phần lý thuyết, thầy cô có thể vào địa chỉ
http://vnoi.info/wiki/algo/data-structures/segment-tree-extend để tham khảo rõ hơn.
Segment Tree là một cây. Cụ thể hơn, nó là một cây nhị phân đầy đủ (mỗi nút
là lá hoặc có đúng 2 nút con), với mỗi nút quản lý một đoạn trên dãy số. Với một dãy
số gồm N phần tử, nút gốc sẽ lưu thông tin về đoạn [1,N], nút con trái của nó sẽ lưu
thông tin về đoạn [1,⌊N/2⌋] và nút con phải sẽ lưu thông tin về đoạn [⌊N/2⌋+1,N].
Tổng quát hơn: nếu nút A lưu thông tin đoạn [i,j], thì 2 con của nó: A1 và A2 sẽ lưu
Ví dụ
Xét một dãy gồm 7 phần tử, Segment Tree sẽ quản lý các đoạn như sau:
201
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Cài đặt
Để cài đặt, ta có thể dùng một mảng 1 chiều, phần tử thứ nhất của mảng thể hiện nút
gốc. Phần tử thứ id sẽ có 2 con là 2∗id (con trái) và 2∗id+1 (con phải). Với cách cài
đặt này, người ta đã chứng minh được bộ nhớ cần dùng cho ST không quá 4∗N phần
tử.
Rõ ràng cách này có độ phức tạp là O(N∗Q) và không thể chạy trong thời gian cho
phép.
Với truy vấn loại 1, ta sẽ cập nhật thông tin của các nút trên cây ST mà đoạn nó
quản lý chứa phần tử i.
Với truy vấn loại 2, ta sẽ tìm tất cả các nút trên cây ST mà ðoạn nó quản lý nằm
trong [i,j], rồi lấy max của các nút này.
Cho một dãy gồm n phần tử có giá trị ban đầu bằng 0.
Cho m phép biến đổi, mỗi phép có dạng (u, v, k): tăng mỗi phần tử từ vị trí u đến vị trí
v lên k đơn vị.
Cho q câu hỏi, mỗi câu có dạng (u, v): cho biết phần tử có giá trị lớn nhất thuộc đoạn
[u, v]
Giới hạn
202
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
n, m, q <= 50000
k>0
Giá trị của một phần tử luôn không vượt quá 231-1
Input: QMAX.INP
Dòng 1: n, m
m dòng tiếp theo, mỗi dòng chứa u, v, k cho biết một phép biến đổi
Dòng thứ m+2: p
p dòng tiếp theo, mỗi dòng chứa u, v cho biết một phép biến đổi
Output: QMAX.OUT
Gồm p dòng chứa kết quả tương ứng cho từng câu hỏi.
QMAX.INP:
62
132
463
34
QMAX.OUT:
#include<bits/stdc++.h>
int n,m,i,u,v;
203
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
if (l>r) return;
if (l==r){it[k]=a[l]; return;}
built(l,m,k2+1); built(m+1,r,k2+2);
it[k]=max(it[k2+1],it[k2+2]);
lay(l,m,k+1); lay(m+1,r,k+2);
int main(){
freopen("qmax.inp","r",stdin);
freopen("qmax.out","w",stdout);
a[u]+=k; a[v+1]-=k;
204
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
built(1,n,0);
cin >> m;
while (m--){
Hàng ngày khi lấy sữa, N con bò của bác John (1 ≤ N ≤ 50000) luôn xếp hàng
theo thứ tự không đổi. Một hôm bác John quyết định tổ chức một trò chơi cho một số
con bò. Để đơn giản, bác John sẽ chọn ra một đoạn liên tiếp các con bò để tham dự trò
chơi. Tuy nhiên để trò chơi diễn ra vui vẻ, các con bò phải không quá chênh lệch về
chiều cao.
Bác John đã chuẩn bị một danh sách gồm Q (1 ≤ Q ≤ 200000) đoạn các con bò
và chiều cao của chúng (trong phạm vi [1, 1000000]). Với mỗi đoạn, bác John muốn
xác định chênh lệch chiều cao giữa con bò thấp nhất và cao nhất. Bạn hãy giúp bác
John thực hiện công việc này!
Dữ liệu: LINEUP.INP
Gồm Q dòng, mỗi dòng chứa 1 số nguyên, là chênh lệch chiều cao giữa con bò
thấp nhất và cao nhất thuộc đoạn tương ứng.
Ví dụ
LINEUP.INP:
63
15
46
22
LINEUP.OUT
#include<bits/stdc++.h>
if (l==r){mi[k]=ma[k]=a[l]; return;}
built(l,m,k2+1); built(m+1,r,k2+2);
mi[k]=min(mi[k2+1],mi[k2+2]);
ma[k]=max(ma[k2+1],ma[k2+2]);
lay(l,m,k+1); lay(m+1,r,k+2);
int main(){
freopen("lineup.inp","r",stdin);
freopen("lineup.out","w",stdout);
207
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
built(1,n,0);
while(m--){
- Dòng thứ hai là n số nguyên a1, a2, a3,..., an cách nhau một khoảng trắng (1 ≤ ai ≤ 109)
- Trong m dòng tiếp theo, tại dòng thứ i là hai số pi và ri của câu hỏi thứ i tương ứng,
hai số cách nhau một khoảng trắng. (1 ≤ pi ≤ ri ≤ n)
Ví dụ
MINR.INP
6 4
7 5 3 8 6 9
1 6
1 4
1 2
46
MINR.OUT
208
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
3
3
5
6
#include<bits/stdc++.h>
if (l==r){it[k]=a[l]; return;}
built(l,m,k2+1); built(m+1,r,k2+2);
it[k]=min(it[k2+1],it[k2+2]);
lay(l,m,k+1); lay(m+1,r,k+2);
int main(){
209
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
freopen("MINR.inp","r",stdin);
freopen("minr.out","w",stdout);
built(1,n,0);
while (q--){
lay(1,n,0); cout<<res<<"\n";
Cho một dãy số a1.. aN. Một nghịch thế là một cặp số u, v sao cho u < v và au >
av. Nhiệm vụ của bạn là đếm số nghịch thế.
Dữ liệu : NGHICHTHE.INP
Giới hạn
1 ≤ N ≤ 60000
1 ≤ ai ≤ 60000
210
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ví dụ
NGHICHTHE.INP
NGHICHTHE.OUT
#include<bits/stdc++.h>
update(k2+1,l,m); update(k2+2,m+1,r);
it[k]=it[k2+1]+it[k2+2];
lay(k+1,l,m); lay(k+2,m+1,r);
int main(){
freopen("nghichthe.inp","r",stdin);
freopen("nghichthe.out","w",stdout);
lay(0,1,maxN); update(0,1,maxN);
cout<<res;
212
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
TWO POINTERS
GIỚI THIỆU
Bài toán tìm kiếm một phần tử trong một dãy các phần tử là bài toán đơn giản và được
giải quyết với hai pương pháp cơ bản, đó là phương pháp “tìm kiếm tuần tự” với độ
phức tạp O(n) và phương pháp “tìm kiếm nhị phân” trên dãy đã sắp với độ phức tạp
O(log n). Bây giờ, chúng ta thử nâng độ khó cho bài toán tìm kiếm lên, không phải tìm
kiếm một phần tử mà tìm kiếm hai phần tử trong một dãy hay hai dãy phần tử (mỗi
phần tử trên một dãy). Nếu sử dụng phương pháp tìm kiếm tuần tự để giải quyết thì độ
phức tạp sẽ là O(n2). Còn nếu sử dụng phương pháp tìm kiếm nhị phân để giải quyết,
độ phức tạp là O(n log n).
Trong chuyên đề này, chúng ta đưa ra giải pháp tìm kiếm đồng thời hai phần tử với
một độ phức tạp O(n) trên dãy đã sắp.
Một kỹ thuật ít được thảo luận trong các bài toán tìm kiếm đó là kỹ thuật sử dụng hai
con trỏ (two pointer) để giải quyết bài toán tìm đồng thời hai đối tượng. Cách tiếp cận
của kỹ thuật này vẫn là duyệt trên các dãy đã sắp theo một đặc tính mà theo đó hai con
trỏ tìm kiếm có thể di chuyển đồng thời trong quá trình tìm kiếm.
Giải thuật tìm kiếm nhị phân là một loại giải thuật tối ưu hóa về số lần thử nghiệm
được thực hiện để đạt được vị trí tối ưu có kỹ thuật sử dụng hai con trỏ nhưng chưa tận
dụng tìm kiếm đồng thời.
Để làm rõ hơn các lập luận trên, chúng ta xem xét bài toán sau đây:
Cho hai dãy số A và B, các số được đánh số từ 1 đến n. Bạn được yêu cầu tìm
hai số, mỗi số từ mỗi dãy, sao cho tổng của hai số là X.
213
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Mỗi số sẽ được duyệt tìm trên một dãy, từ 1 ðến n, ứng với mỗi số trên dãy thứ nhất,
chúng ta tìm số thứ hai cho đến khi tìm được hai số hoặc không tìm được.
Nếu (tổng hai số tại i trên dãy A và tại j trên dãy B bằng X) thì kết
thúc tìm được.
Với giải pháp này, rõ ràng độ phức tạp của thuật toán là O(n2).
Giải pháp 2:
Trước tiên, ta sắp xếp dãy thứ hai theo thứ tự tăng dần. Thay vì phải tìm hai số ai và bj
thỏa điều kiện ai + bj = X. Nếu biết trước số ai trên dãy A, tìm số X – ai trên dãy B. Do
B đã sắp, dùng phương pháp tìm kiếm nhị phân
Như vậy, duyệt trên dãy A. Với mỗi số ai trên dãy A, chúng ta tìm kiếm nhị phân số
thứ 2 có giá trị là (X-ai) trên dãy B đã sắp.
For i : 1 .. n
If (tìm nhị phân số (X – ai)trên dãy B ) thì kết thúc tìm được.
Nhận xét
Thực ra, trong 2 giải pháp trên cũng đã sử dụng kỹ thuật hai con trỏ. Tuy nhiên, trong
giải pháp thứ hai, con trỏ thứ hai là tìm kiếm nhị phân nhưng tìm đi tìm lại nhiều lần
trên dãy thứ hai. Đó là do số thứ nhất phải duyệt tuần tự.
Cách tìm kiếm hai phần tử theo hai phương pháp trên là không đồng thời.
214
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Trong giải pháp này, chúng ta sử dụng kỹ thuật hai con trỏ thực hiện tìm kiếm đồng
thời hai phần tử trên hai dãy đã sắp với độ phức tạp O(n).
Để làm đơn giản vấn đề hơn, chúng ta xét lại bài toán chỉ trên một mảng. Sau đó, xét
trên hai mảng.
Cho một dãy đã sắp A, có N Số nguyên. Tìm một cặp số bất kỳ (i, j ) Có tổng bằng
số X.
Hạn chế: Dãy A Chứa khoảng 105 phần tử số nguyên với mỗi giá trị khoảng 109.
Tổ chức dữ liệu
- Hai biến con trỏ. Hai biến này luôn trỏ vào hai vị trí đang xét trên dãy xử lý.
Phương pháp
Giải pháp:
Chúng tôi sử dụng hai con trỏ, một ở đầu dãy, gọi là con trỏ trái và một ở cuối dãy, gọi
là con trỏ phải. Bắt đầu di chuyển, tại mỗi bước, chúng ta tính tổng giá trị tại hai vị trí
con trỏ. Trong trường hợp tổng giá trị lớn hơn X, chúng ta cần phải di chuyển con trỏ
phải sang trái một đơn vị để giảm giá trị tổng. Tương tự, nếu tổng giá trị nhỏ hơn X,
chúng ta di chuyển con trỏ trái, sang phải một đơn vị để tăng tổng giá trị đến gần với
số X. Tiếp tục di chuyển con trỏ cho đến khi tổng giá trị bằng X. hoặc hai con trỏ gặp
nhau.
If (tổng giá trị tại hai con trỏ = X) thì kết thúc tìm được
Else If (tổng giá trị tại hai con trỏ > X) thì
Bây giờ, chúng tai quay trở lại bài toán ban đầu với tìm trên hai dãy.
Cho hai dãy đã sắp A và B. Mỗi dãy có chiều dài N. Tìm hai số, mỗi số từ mỗi dãy, sao
cho tổng của hai số là X.
Hạn chế: Dãy A và B Chứa khoảng 105 Số nguyên , mỗi số có giá trị khoảng 109.
Giải pháp
Gọi i và j hai con trỏ chạy trên dãy AVà B tương ứng. đầu tiên cho con trỏ i
trỏ đến phần tử đầu tiên trên dãy a, và con trỏ j trỏ đến phần tử cuối của dãy
b.
di chuyển con trỏ i tăng dần trên dãy a, và giảm vị trí của con trỏ j trên dãy b nếu cần
thiết. Tại mỗi bước, chúng tôi kiểm tra a[i] + b[j] có bằng X không. Nếu thỏa ghi kết
quả và kết thúc tìm kiếm. ngược lại, nếu a [i] + b [j] > X, thì giảm j (nếu j > 0), bởi vì
chúng tôi cần phải giảm giá trị tổng xuống còn nếu a [i] + b [j] < X thì tăng vị trí con
trỏ i.
While (tổng giá trị tại i và j > X ) và (j chưa vượt quá đầu dãy b) giảm(j)
If (tổng giá trị tại i và j = x) thì kết thúc và tìm được cặp (i, j)
Tăng (i)
Cho hai dãy số nguyên đã sắp A và B ,,Mỗi dãy có chiều dài N và M tương
ứng. Yêu cầu tạo một dãy có giá trị được sắp mới của cả hai dãy A và B.
Hạn chế: Hai dãy A và B Chứa khoảng 105 số nguyên. Mỗi phần tử có giá trị
khoảng 109.
216
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Giải pháp: Vì hai dãy được cho theo thứ tự sắp xếp, chúng tôi chắc chắn có thể sử
dụng hai con trỏ để giải quyết.
1. Gọi i, j con trỏ chạy trên dãy AVà B tương ứng. Và một con trỏ k chỉ vị trí
ghi vào dãy kết quả. Ban đầu tất cả i, j và k trỏ vào vị trí đầu các dãy.
2. Duyệt trên hai dãy, nếu hai con trỏ chưa vượt quá dãy ( i <= N , j <= M),
Chọn số nhỏ nhất của cặp số ( Ai, Bj) Và ghi nó lên lên dãy kết quả tại vị trí
con trỏ k, Và tăng các con trỏ thích hợp.
Tăng(k);
if ( i <= n và j <= m )
else if ( i <= n )
else if ( j <= m )
217
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
II. DÙNG PHƯƠNG PHÁP 2 CON TRỎ GIẢI QUYẾT BÀI TOÁN TÌM
DÃY CON LIÊN TIẾP THỎA ĐIỀU KIỆN
Trong phần trên, chúng ta sử dụng hai con trỏ trong việc tìm kiếm đồng thời chỉ trên
hai phần tử duy nhất không quan tâm đến các phần tử khác trong dãy. Trong phần này,
chúng ta sẽ mở rộng kỹ thuật sử dụng hai con trỏ để tìm một đoạn, một dãy con liên
tiếp các phần tử, trong một dãy cho trước thỏa điều kiện.
Tương tự phương pháp sử dụng hai con trỏ trong phần trên, Tại mỗi bước, hai con trỏ
một trỏ vào phần tử đầu và một trỏ vào phần tử cuối của một dãy con đang xét.
Giải pháp
Một dãy gọi là dãy tăng khi phần tử đứng trước đều có giá trị nhỏ hơn phần tử
đi71ng sau bất kỳ.
Gọi l và r là hai con trỏ đầu và cuối của một dãy con liên tiếp. Giả sử, dãy con
đang xét thỏa điều kiện dãy tăng. Nếu a[r] < a[r+1] thì dãy con từ l .. r+1 là một dãy
con tăng. Ngược lại không phải là một dãy tăng. Đồng thời, dãy [i,R+1] không phải là
dãy tăng với mọi i : l .. r .
l=1; r=1;
While (r < n)
Tăng(r);
Else
l=r;
218
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Cho một dãy N Số nguyên ,,Tìm dãy con liên tiếp có tổng lớn nhất nhưng không
lớn hơn M.
Hạn chế: dãy có thể có không ít hơn 105 phần tử và mỗi số không âm và có thể lớn
đến 109.
Giải pháp:
Vì dãy đã cho có các phần tử dương, nên tổng tích lũy sẽ tăng lên khi bạn đi từ trái
sang phải trong dãy. Do đó, dãy tổng tích lũy là dãy đã sắp, bài toán có thể giải quyết
bằng phương pháp hai con trỏ.
Tạo mảng T, tổng tích lũy của dãy đã cho. Hiệu của hai giá trị tổng tích lũy tại
hai vị trí I và j là tổng giá trị dãy con liên tiếp từ i+1 đến j. Bài toán bây giờ là
tìm hai vị trí I và j sao cho hiệu hai tổng tích lũy tại i và j là lớn nhất nhưng
không quá M.
Đối với mỗi chỉ số i của mảng tổng tích lũy, chúng ta tìm nhị phân số j tương
ứng sao cho biểu thức sau là đúng.
Như phần trên đã nói, giải pháp này sẽ có độ phức tạp là O(n log n) cho việc tìm kiếm
hai số tại hai vị trí i và j..
Gọi l và r là hai con trỏ biểu thị chỉ số đầu và chỉ số cuối của dãy con liên tiếp
liên tiếp trên dãy tổng tích lũy. Hiệu (T[r] – T[l]) là tổng giá trị của dãy con liên
tiếp a[l+1] .. a[r].
Nếu T[r] – T[l] <= M thì ghi nhận dãy con nếu dãy con có tổng lớn hơn tổng đã
lưu; tăng r
219
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
while (l <= r)
ghi nhận dãy con [l,r] nếu dãy con có S[r] – S[l] lớn tổng
lớn hơn tổng đã lưu;
tăng r
tăng l
3. Dãy con liên tiếp nhỏ nhất chứa K phần tử khác nhau
Cho một dãy chứa N Số nguyên, Tìm chiều dài của một dãy con liên tiếp nhỏ nhất có
chứa ít nhất K phần tử khác nhau. Đầu ra "-1" nếu không tìm được.
Hạn chế: Số phần tử trong dãy là khoảng một triệu với mỗi giá trị trong số đó có giá
trị lớn đến 109.
Giải pháp:
Chúng ta sử dụng kỹ thuật hai con trỏ để giải quyết bài toán.
Gọi l và r là hai con trỏ trỏ vào đầu và cuối của dãy con liên tiếp. Mỗi lần di chuyển l
hoặc r, cập nhật lại các thông số của dãy con. Dựa vào điều kiện số phần tử khác nhau
trong dăy con (l,r) mŕ chúng ta di chuyển l hay r. Cụ thể như sau:
Tăng (r); Nếu a[r] chưa tồn tại trong dãy từ l đến r-1 thì tăng số phần tử
khác nhau trong dãy (l,r); Tăng số lần xuất hiện số a[r] trong dãy (l,r)
Ghi nhận kết quả (độ dài min của dãy (l,r)). Đồng thời, giảm số lần xuất
hiện a[l]. Nếu số lần xuất hiện của a[l] = 0 thì giảm số phần tử khác nhau
trong dãy con và tăng l.
220
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Trong giải pháp trên, cần tạo thêm một mảng đánh mã số của các số trong dãy và
mảng đếm số lần xuất hiện của mã số đó trong dãy.
Gọi C là số phần tử khác nhau trong dãy con liên tiếp từ l đến r.
Khi (l <=n)
Tăng(r);
Nếu a[r] chưa tồn tại trong dãy từ l đến r-1 thì tăng(C)
Nếu C >= K
Tăng (l)
Phương pháp two pointer là phương pháp vận dụng kỹ thuật tìm kiếm đồng thời hai
phần tử dựa vào hai cho trỏ duyệt chạy trên một dãy đã sắp.
Tại mỗi bước, hai con trỏ một trỏ vào phần tử đầu và một trỏ vào phần tử cuối của một
dãy con đang xét. Độ phức tạp của thuật toán tại mỗi bước chính là độ phức tạp của
việc kiểm tra dãy con có thỏa điều kiện không?
Với kỹ thuật duyệt đồng thời hai con trỏ như phần trên, độ phức tạp của kỹ thuật duyệt
này là O(n). Do đó, độ phức tạp của bài toán là (n x độ phức tạp tại mỗi bước). Nếu
chúng ta tổ chức dữ liệu tốt, tính toán trước (như trong phân tích bài 2 và 3) có thể tại
mỗi bước kiểm tra dãy con, độ phức tạp chỉ là O(1). Như vậy, độ phức tạp của bài toán
là O(n).
Lưu ý: Tất nhiên, không phải bài toán tìm dãy con liên tiếp nào cũng có thể dung
phương pháp two pointer.
221
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
IV.BÀI TẬP
Cho một dãy số nguyên n phần tử a1, a2, .. an. Tìm dãy con liên tiếp có dạng
sóng dài nhất. Một dãy gọi là có dạng sóng khi với mỗi 3 số kề nhau bất kỳ trong
dãy, số đứng giữa hoặc lớn hơn hoặc nhỏ hơn hai số hai bên.
Hạn chế: Số phần tử trong dãy là khoảng một triệu với mỗi giá trị trong số đó có giá
trị lớn đến 109 .
Cho một dãy số nguyên n phần tử a1, a2, .. an. Tìm dãy con liên tiếp dài nhất có
tổng bằng 0.
Hạn chế: Dãy A chứa khoảng 105 Số nguyên , mỗi số có giá trị khoảng 109.
Cho dãy số nguyên n phần tử a1 a2 … an . Tìm dãy con liên tiếp có tổng lớn
nhất?
Hạn chế: Dãy A chứa khoảng 105 Số nguyên , mỗi số có giá trị khoảng 109.
4. THAM QUAN
Điều kiện làm việc cho phép trong khoảng thời gian n ngày liên tục nhân viên có thể
đăng ký nghỉ phép hoặc nghỉ bù. Steve quyết định tận dụng khả năng hiếm có này để
nghỉ ngơi liên tục tất cả k ngày mà mình được quyền. Sau khi cân nhắc, Steve đăng ký
tham gia một tour du lịch. Mỗi ngày công ty du lịch sẽ đưa khách tới tham quan một
thành phố. Trong bảng giới thiệu chương trình tham quan mỗi thành phố được ghi
tương ứng với một số nguyên dương không vượt quá 109, ngày thứ i sẽ tham thành phố
ai. Các thành phố khác nhau tương ứng với các số nguyên khác nhau. Một thành phố
có thể được tới nhiều lần. Steve có thể tham gia tour du lịch bắt đầu từ ngày tùy chọn
và dĩ nhiên, muốn thăm được càng nhiều thành phố khác nhau càng tốt.
Yêu cầu: Cho n, k và ai (1 ≤ k ≤ n ≤ 105). Hãy xác định ngày bắt đầu kỳ nghỉ để nghỉ
đủ k ngày và số lượng các thành phố Steve có thể tới thăm là lớn nhất. Ngày đầu tiên
222
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
trong chương trình của Công ty được đánh số là 1. Nếu tồn tại nhiều cách lựa chọn thì
đưa ra ngày sớm nhất.
Kết quả: Đưa ra file văn bản EXCURS.OUT một số nguyên – ngày được chọn để bắt
đầu tham quan.
Ví dụ:
EXCURS.INP EXCURS.OUT
73 2
1213121
Input
Gồm nhiều dòng ( 1000). Mỗi dòng ghi một xâu n ký tự (1 n 1000).
Output
Gồm nhiều dòng, dòng thứ i là kết quả của xâu thứ i, gồm hai số pi và qi, pi là
độ dài xâu con tìm được và qi vị trí ký tự đầu xâu con tìm được.
Nếu có nhiều xâu con dài nhất có độ dài bằng nhau thì xuất xâu có vị trí đầu tiên nhỏ
nhất. Hai xâu con
Ví dụ
ABCBJHEPE 6 3
AAA 11
Quà sinh nhật của Jimmy là một bộ khối lập phương xếp hình. Jimmy xếp thành n
Jimmy rất có cảm tình với số nguyên k, vì vậy dãy liên tục các tháp được coi là hài
hòa nếu chúng có độ cao trung bình là k (1 ≤ k ≤ 109).
Yêu cầu: Cho n, k và ai, i =1 ÷ k. Hãy xác định dãy tháp hài hòa dài nhất, chỉ ra tháp
đầu tiên và độ dài của dãy tìm được. Nếu tồn tại nhiều dãy cùng độ dài thì chỉ ra dãy
tháp có vị trí đầu nhỏ nhất.
Kết quả: Đưa ra file văn bản CUBICS.OUT trên một dòng 2 số nguyên: độ dài của
dãy tìm được và số thứ tự của tháp đầu tiên hoặc hai số 0 nếu không tồn tại dãy.
Ví dụ:
CUBICS.INP CUBICS.OUT
53 32
1
2
3
4
6
7. HÁI NẤM
224
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Trước mặt Mario là một dãy n cây nấm xếp thành một hàng dài, mỗi cây nấm có một
giá trị riêng, là một số nguyên dương không vượt quá 1000. Mario không cần phải hái
hết nấm mà chỉ cần đạt tổng giá trị càng gần 100 càng tốt và chỉ được hái liên tục các
cây nấm cạnh nhau. Nếu có hai khả năng gần 100 tương đương nhau ( ví dụ 98 và 102)
Mario sẽ chọn phương án có giá trị lớn hơn.
Yêu cầu: Cho giá trị n cây nấm theo trình tự từ trái sang phải. Hãy xác định tổng giá
trị nấm Mario hái được.
Dữ liệu: Vào từ file văn bản MUSHROOM.INP gồm dòng đầu là số n (n <= 5.105), n
dòng sau, mỗi dòng chứa một số nguyên, dòng thứ i xác định giá trị cây nấm i.
Kết quả: Đưa ra file văn bản MUSHROOM.OUT một số nguyên – tổng giá trị nấm
Mario hái được.
Ví dụ:
MUSHROOM.INP MUSHROOM.OUT
1 110
2
3
5
8
13
21
34
55
79
http://www.spoj.com/problems/HOTELS/
There are N hotels along the beautiful Adriatic coast. Each hotel has its value in Euros.
Sroljo has won M Euros on the lottery. Now he wants to buy a sequence of
consecutive hotels, such that the sum of the values of these consecutive hotels is as
great as possible - but not greater than M.
Input
In the first line of the input there are integers N and M (1 ≤ N ≤ 300 000, 1 ≤ M < 231).
In the next line there are N natural numbers less than 106, representing the hotel values
in the order they lie along the coast.
Output
Print the required number (it will be greater than 0 in all of the test data).
Example
Input input
5 12 49
21345 7356
output Output
8
12
http://codeforces.com/problemset/problem/716/B
B. Complete the Word
226
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
ZS the Coder loves to read the dictionary. He thinks that a word is nice if there exists
a substring (contiguous segment of letters) of it of length 26 where each letter of
English alphabet appears exactly once. In particular, if the string has length strictly
less than 26, no such substring exists and thus it is not nice.
Now, ZS the Coder tells you a word, where some of its letters are missing as he forgot
them. He wants to determine if it is possible to fill in the missing letters so that the
resulting word is nice. If it is possible, he needs you to find an example of such a word
as well. Can you help him?
Input
The first and only line of the input contains a single string s (1 ≤ |s| ≤ 50 000), the word
that ZS the Coder remembers. Each character of the string is the uppercase letter of
English alphabet ('A'-'Z') or is a question mark ('?'), where the question marks denotes
the letters that ZS the Coder can't remember.
Output
If there is no way to replace all the question marks with uppercase letters such that
the resulting word is nice, then print - 1 in the only line.
Otherwise, print a string which denotes a possible nice word that ZS the Coder
learned. This string should match the string from the input, except for the question
marks replaced with uppercase English letters.
Examples
input
ABC??FGHIJK???OPQR?TUVWXY?
output
ABCDEFGHIJKLMNOPQRZTUVWXYS
227
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
input
WELCOMETOCODEFORCESROUNDTHREEHUNDREDANDSEVENTYTWO
output
-1
input
??????????????????????????
output
MNBVCXZLKJHGFDSAQPWOEIRUYT
input
AABCDEFGHIJKLMNOPQRSTUVW??M
output
-1
Note
In the second sample case, there are no missing letters. In addition, the given string
does not have a substring of length 26 that contains all the letters of the alphabet, so
the answer is - 1.
In the third sample case, any string of length 26 that contains all letters of the English
alphabet fits as an answer.
PHỤ LỤC
228
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
1.1.15. Trong phần phụ lục này, chúng tôi xin lấy bài viết về kỹ thuật sử dụng hai
con trỏ của tác giả Dương Thanh Phong đã đăng trên trang blogs của tác giả bổ xung
vào chuyên đề này để thấy rõ hơn mức độ hiệu quả của kỹ thuật sử dụng hai con trỏ
trong giải thuật.
1.1.16.
Two pointers là kĩ thuật đơn giản, code ngắn, nhưng hiệu quả. Nó ít được biết đến vì
nó rất ít được dùng khi làm ứng dụng và trong chương trình dạy. Không mất thời gian,
ta bắt đầu bằng một ví dụ: Bạn được giao nhiệm vụ phân tích trên đóng dữ liệu, dữ liệu
là dãy số tự nhiên có N phần tử a0, a1, …, a(N-1) , bạn phải tính có bao nhiêu dãy con
liên tiếp thỏa mãn tổng của chúng lớn hơn hoặc bằng S(S>0). Chẳng hạn với dãy số
{1, 2, 5, 4, 2}, S=8. Dãy con liên tiếp là dãy được trích từ dãy ban đầu và các phần tử
được lấy ra phải liên tiếp nhau. Ta có các dãy con sau thỏa mãn {1, 2, 5}, {1, 2, 5, 4},
{1, 2, 5, 4, 2}, {2, 5, 4}, {2, 5, 4, 2}, {5, 4}, {5, 4, 2}. Tui chắc chắn bạn đã nghĩ ra
cách để tìm ra có bao nhiêu dãy con như thế. Bạn hãy code ra, chạy thử trên bộ dữ liệu
này input . Nếu code của bạn chạy trong 1s mà cho ra kết quả là 302572338891thì bạn
ko cần đọc tiếp bài viết này, và bạn có thể cho mình xin code tham khảo J. Nếu code
của bạn không thể chạy cho dữ liệu lớn như vậy hoặc chạy sai kết quả, thì bạn cứ tiếp
tục đọc nhé. Có phải cách của bạn là: Với cách này chạy ra kết quả đúng, nhưng trong
trường hợp xấu nhất nó mất thời gian O(N^2), khó mà ra kết quả trong 1s với N=10^6.
Vấn đề ở đoạn code trên chính là nó cộng lại nhiều lần. Sau khi vòng lặp thứ nhất i=0,
nó đã tính Run=a0+a1+…+a(N-1). Đến i=1, nó lại cộng Run=a1+a2+…+a(N-1). Ta phải
tìm cách giảm thiểu số lần tính lại nhưng vẫn đảm bảo kết quả đúng. Ta muốn số lần
cộng vào biến Run được giảm xuống, vì thế giá trị của biến Run ở lần chạy trước phải
được sử dụng lại ở lần chạy sau, có như thế thì mới giảm số lần chạy được. Bạn thử
suy nghĩ một lát rồi hãy tiếp tục đọc. Và đây là lời giải của tui cho bài toán này:
229
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ý tưởng đoạn code trên là với mỗi chỉ số end, ta tìm chỉ số start lớn nhất sao
cho a[start]+a[start+1]+…+a[end] >=S. Như thế sẽ có start+1 chuỗi con thỏa mãn
điều kiện có chỉ số cuối là end. Các dãy đó là {a0, a1, .., a[end]}, { a1,a2 .., a[end]},
{a[start], a[start+1], .., a[end]}. Vì thế ta cộng dồn vào kết quả start+1.
Nhìn vào đoạn code thứ hai, ta thấy vẫn có 2 vòng lặp chạy lồng nhau. Nhưng trên
thực tế đoạn code này chỉ chạy trong (2*N) cho tất cả các trường hợp, thay vì chạy
trong (N^2) như đoạn code thứ nhất. Ta bắt đầu phân tích:
Ý tưởng:
Ý tưởng của kĩ thuật này là có hai biến chạy song song nhau, ở đây là start và end. Số
lần chạy của biến này không phụ thuộc vào biến kia. Và yêu cầu bài toán thường là tìm
dãy con liên tiếp có tính chất: nếu ta có dãy con thỏa mãn A, thì thêm vào một phần
tử cũng vẫn thỏa mãn A. Biến startcó nhiệm vụ giữ chỉ số đầu của dãy con,
biến end giữ chỉ số cuối của dãy con. Tùy bài toán mà ta cho biến start chạy rồi tìm
230
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
biến end tương ứng, hoặc cho biến end chạy rồi tìm biến start tương ứng. Trong ví dụ
trên là biến end chạy và ta tìm biến start tương ứng thỏa tính
chất a[start]+a[start+1]+…+a[end] >=S. Trước khi kết thúc ta xét thêm một ví dụ:
Một bản nhạc hơi dài có N (N=10^6) nốt nhạc, độ cao mỗi nốt nhạc là hi (từ 0 đến 20).
Ta cần tìm ra đoạn nhạc có chiều dài nhỏ nhất mà trong đó độ chênh lệch độ cao giữa
nốt cao nhất và nốt thấp nhất phải lớn hơn hoặc bằng H (từ 0 đến 20). Nhiệm vụ trước
tiên là tìm ra chiều dài nhỏ nhất đó, sau khi có chiều dài nhỏ nhất thì việc in ra các dãi
con là dễ dàng. Nhận xét: bài toán cũng thuộc dạng tìm dãy con liên tiếp. Nếu ta có
một dãy thỏa điều kiện độ chênh lệch độ cao giữa nốt cao nhất và nốt thấp nhất phải
lớn hơn hoặc bằng H, ta thêm vào một phần tử vào dãy này, thì ta có một dãy mới
cũng thỏa tính chất đó (diễn đạt khác đi [max{ai, x} -min{ai, x}] >= [max{ai} -min{ai
}] >= H ). Cách giải thì tương tự ví dụ trên, nên sẽ không được giải thích
lại.
231
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Tổng kết: Nếu bạn chưa bao giờ dùng kĩ thuật này, tui nghĩ bạn cũng chưa thật sự
hiểu nó. Bạn hãy tự giải lại bài toán trên. Bài viết cũng chỉ gợi ý tưởng cho bạn, chứ
thực sự mình cũng chưa đủ trình độ để có thể viết một bài cho bạn hiểu liền. Nếu hứng
thú bạn có thể thực hành nhiều hơn về kĩ thuật này
tại http://codeforces.com/problemset/tags/two%20pointers
https://tp-iiita.quora.com/The-Two-Pointer-Algorithm
2. The Two pointer Algorithm !!
https://ilovealgorithms.wordpress.com/2012/11/05/the-two-pointer-algorithm/
3. TWO POINTERS Người viết: Dương Thành Phong (phần phụ lục)
232
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
k :longint;
p :qword;
begin
assign(input,fi);
reset(input);
k:=0;
while not seekeof() do
begin
inc(k);
while not seekeoln() do
begin
inc(n[k]);
read(p);
a[k,n[k]]:=p;
end;
readln;
end;
close(input);
end;
procedure sang;
var i,j :longint;
begin
for i:=2 to trunc(sqrt(maxc)) do
if g[i]=0 then
begin
j:=i*I;
while j<=maxc do
begin
g[j]:=i;
inc(j,i);
end;
end;
235
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
procedure xuli;
var i,j :longint;
begin
for i:=1 to n[1] do
for j:=1 to n[2] do
if (a[1,i]=a[2,j]) and kt(a[1,i]) then f[i,j]:=f[i-1,j-1]+1
else f[i,j]:=max(f[i-1,j],f[i,j-1]);
end;
procedure xuat;
begin
assign(output,fo);
rewrite(output);
writeln(f[n[1],n[2]]);
close(Output);
end;
begin
nhap;
sang;
xuli;
xuat;
end.
237
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
f[i][1][1]=min(f[i-1][1][1],
f[i-1][1][0]+c[i][1]);
}
for(int i=2; i<=m; i++)
for(int j=2; j<=n; j++)
{
f[i][j][0]=min(f[i][j-1][0],
f[i][j-1][1]+c[i][j]);
f[i][j][1]=min(f[i-1][j][1],
f[i-1][j][0]+c[i][j]);
}
cout<<min(f[m][n][0],f[m][n][1])<<"\n";
}
}
Bài 4: LNACS spoj – VOI2010 Dãy con chung không liền kề dài nhất
http://vn.spoj.com/problems/LNACS/
Hýớng giải quyết
Bài này làm quy hoạch động tương tự bài dãy con chung dài nhất. Tuy nhiên có một
chút khác ở công thức.
Gọi f[i][j] là độ dài dãy con chung dài nhất của 2 dãy a[1…i] và b[1…j]. Ta có:
nếu a[i]!=b[j] thì: f[i][j]=max(f[i-1][j], f[i][j-1],f[i-1][j-1]).
nếu a[i]==b[j] thì: f[i][j]=max(f[i-1][j], f[i][j-1],f[i-1][j-1],f[i-2][j-2]+1).
Về phần khởi tạo:
f[0][0] =0;
f[1][j] = 1 nếu a[1]=b[j].
f[1][j] = f[1][j-1] nếu a[1]!=b[j].
f[i][1] = 1 nếu a[i]==b[1].
f[i][1] = f[i-1][1] nếu a[i]!=b[1].
code
uses math;
239
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
const maxm=10000;
maxn=1000;
var m,n:longint;
a,b:array[0..maxn] of longint;
f:array[-1..maxn,-1..maxn] of longint;
procedure docfile;
var i:longint;
begin
readln(m,n);
for i:=1 to m do readln(a[i]);
for i:=1 to n do readln(b[i]);
end;
procedure xuli;
var i,j:longint;
begin
for i:=1 to m do
begin
f[i,0]:=0;
if a[i]=b[1] then f[i,1]:=1 else f[i,1]:=f[i-1,1];
end;
for j:=1 to n do
begin
f[0,j]:=0;
if a[1]=b[j] then f[1,j]:=1 else f[1,j]:=f[1,j-1];
end;
for i:=2 to m do
begin
for j:=2 to n do
begin
if a[i]=b[j] then
begin
f[i,j]:=f[i-2,j-2]+1;
240
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
241
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Đa số các thuật toán đều tập trung vào văn bản và các con số, chúng được thiết kế và
xử lí sẵn trong phần lớn các môi trường lập trình. Đối với các bài hình học thì tình
huống khác hẳn, ngay cả các phép toán sơ cấp trên điểm và đoạn thẳng cũng là một
thách thức về tính toán.
Các bài toán hình học thì dễ hình dung một cách trực quan nhưng chính điều đó lại có
thể là một trở ngại. Nhiều bài toán có thể giải quyết ngay lập tức bằng cách nhìn vào
một mảnh giấy nhưng lại đòi hỏi một chương trình không đơn giản
Trong các bài toán tin học thuộc loại hình học có 3 đối tượng cơ bản là: Điểm, đoạn
thẳng và đa giác
- Đoạn thẳng: là cặp điểm được nối với nhau bằng một phần của đường thẳng
- Đa giác: là dãy các điểm mà 2 điểm liên tiếp nối với nhau bởi đoạn thẳng và điểm
đầu nối với điểm cuối tạo thành đường gấp khúc khép kín
Type Point=record
x, y: integer; end;
Line=Record
P1,p2: Point; end;
Var Polygon: Array[0..Nmax] of Piont;
1. Vị trí tương đối của điểm so với đường thẳng, tia và đoạn thẳng
242
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Yêu cầu:
a. Kiểm tra M có thuộc đường thẳng đi qua 2 điểm A,B hay không?
Dữ liệu vào từ file văn bản BAITOAN1.INP gồm 1 dòng duy nhất chứa tọa độ của
các điểm M, A, B.
Kết quả ghi ra file văn bản BAITOAN1.OUT là kết quả của đề bài
Ví dụ:
BAITOAN1.INP BAITOAN1.OUT
Phương pháp
243
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Bài toán 2: Cho 2 đường thẳng có phương trình a1x+b1y+c1=0 và a2x+b2y+c2=0. Tìm
giao điểm (nếu có) của hai đường thẳng trên.
Dữ liệu vào từ file BAITOAN2.INP gồm các hệ số a1, b1, c1, a2, b2, c2.
Kết quả ghi ra file BAITOAN2.OUT là vị trí tương đối của hai đường thẳng.
Ví dụ:
BAITOAN2.INP BAITOAN2.OUT
Phương pháp
- Nếu D=0 và ((Dx0) hoặc (Dy≠0)) thì kết luận hai đường thẳng song song
- Nếu D≠ 0 thì kết luận 2 đường thẳng cắt nhau tại điểm có tọa độ Dx Dy
;
D D
Bài toán 3: Cho 2 đoạn thẳng AB, CD với A(xa;ya), B(xb;yb), C(xc;yc), D(xd,yd) .
Tìm giao điểm (nếu có) của 2 đoạn thẳng.
Dữ liệu vào từ file BAITOAN3.INP gồm tọa độ các điểm A(xa;ya), B(xb;yb),
C(xc;yc), D(xd,yd)
Kết quả ghi ra file BAITOAN3.OUT là giao điểm của hai đoạn thẳng AC và CD (nếu
có).
Ví dụ:
244
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
BAITOAN3.INP BAITOAN3.OUT
Phương pháp
B2: Kiểm tra M có thuộc đồng thời cả 2 đoạn AB và CD hay không. Nếu có đó là
giao điểm cần tìm, ngược lại kết luận không có.
Bài toán 4: Cho tia AM chứa điểm B (khác A) và đoạn thẳng CD với A(xa;ya),
B(xb;yb), C(xc;yc), D(xd;yd). Tìm giao điểm (nếu có) của tia AM với đoạn thẳng CD.
Dữ liệu vào từ file BAITOAN4.INP gồm tọa độ các điểm A(xa;ya), B(xb;yb),
C(xc;yc), D(xd,yd)
Kết quả ghi ra file BAITOAN4.OUT là giao điểm của hai đoạn thẳng AC và CD (nếu
có).
Ví dụ:
BAITOAN4.INP BAITOAN4.OUT
Phương pháp
B2. Kiểm tra N có thuộc tia AM và đoạn thẳng CD hay không. Nếu có đó là giao điểm
cần tìm, ngược lại kết luận không có.
Phương pháp: Đây là một trong số dạng bài toán hình học đơn giản nhất. Việc giải bài
toán dạng này chủ yếu sử dụng các kiến thức hình học cơ bản (đã trình bày đầy đủ
trong phần trên)
Cho N điểm, hãy kiểm tra xem có bao nhiêu bộ 3 điểm thẳng hàng.
- N dòng tiếp theo, mỗi dòng ghi tọa độ của một điểm.
- k dòng tiếp theo mỗi dòng là tọa độ của 3 điểm thẳng hàng.
Ví dụ:
THANGHANG.INP THANGHANG.OUT
6 3
11
12
22
Cho n đường thẳng AiBi (1<=i<=n) phân biệt với Ai, Bi là các điểm cho trước.
Yêu cầu: Hãy cho biết các cặp đường thẳng đôi một cắt nhau.
246
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Dữ liêu vào từ file văn bản DT.INP gồm n dòng (n không biết trước). Dòng thứ i ghi
4 số thực XAi, YAi, XBi, YBi. Các số trên cùng một dòng ghi cách nhau ít nhất một dấu
cách.
Kết quả ghi ra file văn bản DT.OUT là kết quả của bài toán.
Ví dụ:
DT.INP DT.OUT
-1 -1 8 9
Ý tưởng:
B1. Mỗi đường thẳng được đặc trưng bởi 3 thông số a, b, c được xác định:
B2. Hai đường thẳng cắt nhau khi: D<>0 ( với D=a1*b2-a2*b1)
Cho đa giác không tự cắt (để cho dễ ở đây ta xét đa giác lồi) A1A2...AN với các đỉnh
Ai(xi, yi) nguyên. Với điểm A(XA, YA) cho trước.
Lưu ý: Tọa độ các đỉnh của đa giác theo chiều kim đồng hồ
Yêu cầu: Hãy xác định xem A có nằm trong đa giác đã cho hay không
- Dòng đầu là số N
Kết quả: ghi ra tệp DAGIAC.OUT điểm A có nằm trong đa giác hay không
247
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ví dụ:
DAGIAC.INP DAGIAC.OUT
00
50
55
05
65
Ý tưởng:
- Kiểm tra xem điểm A có trùng với các đỉnh của đa giác
- Kiểm tra xem điểm A có nằm trên các cạnh của đa giác
- Tìm giao điểm nếu có của tia Ax (Ax//Ox và Ax hướng theo phần dương trục hoành)
với các cạnh của đa giác. Cụ thể:
+ Giả sử điểm A(X0, Y0), chọn điểm B(Xb, Yb) với Xb=X0+1 (vì điểm B thuộc tia
Ax và theo chiều dương của trục Ox, ta có thể chọn Xb=X0 cộng với một giá trị
khác thay vì chọn 1), Yb=Y0
Nếu tia AB có cắt đoạn thẳng CD (xem lại phương pháp ở phần trên), thì tăng
số giao điểm lên 1;
Nếu đỉnh của đa giác thuộc tia AB thì tăng số giao điểm lên 1 (điều kiện đỉnh
i thuộc tia AB là (a[i].y=a[n+2].y)and(a[n+2].x<=a[i].x);
+ Đếm số giao điểm, nếu số giao điểm lẻ thì A thuộc đa giác ngược lại thì A
không thuộc đa giác.
248
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Lưu ý: Kiểm tra các đỉnh có nằm trên tia MN hay không
Tư tưởng cho bài toán này nói qua thì rất đơn giản và dễ hiểu: Từ điểm cần kiểm
tra ta kẻ một tia bất kỳ, nếu tia đó giao với đa giác một số chẵn lần thì có nghĩa là nó
nằm ngoài đa giác, một số lẻ lần thì nó nằm trong đa giác.
Nhưng trên thực tế có rất nhiều trường hợp cần phải được giải quết triệt để:
Cho một đa giác lồi, hãy xác định xem một điểm có nằm trong đa giác đó hay không?
Ta hãy tận dụng tính “lồi” của đa giác. Vì là đa giác lồi nên nó chỉ giao với một
đường thẳng bất kỳ tại không quá hai điểm (chính xác thì là 2 hoặc là 0). Chú ý, “giao”
ở đây là khi ta đi vòng quanh đa giác, ta đi từ một bên của đường thẳng sang bên kia
của nó.
Đến đây, hẳn nhiều bạn cũng đã rõ thuật toán. Thay bằng việc ta đi vòng quanh
đa giác, ta chỉ tìm hai cạnh của đa giác cắt đường thẳng chứa điểm cần xét. Tất nhiên
trường hợp không đoạn nào như vậy là tầm thường. Cũng có trường hợp có nhiều hơn
hai đoạn như vậy nếu đường thẳng y = t.y đi qua đỉnh của đa giác. Nhưng cũng chẳng
đáng chú ý, ta chọn một trong hai đoạn bất kỳ chứa đỉnh đó thôi. Sau khi chọn được
hai cạnh của đa giác rồi, ta xét xem có bao nhiêu cạnh (trong hai cạnh) cắt tia cần xét.
249
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Để đơn giản, ta cũng trọn đường thẳng là đường y = t.y, và tia có hướng cùng chiều
với chiều dương của trục Ox (Với cách trọn bất kỳ cũng không thực sự phức tạp hơn,
bạn đọc hãy thử coi nó như là một trò giải trí?)
y = t.y
Chú ý rằng chi phí chủ yếu cho thuật toán này lại là phép tìm kiếm. Vì vậy, nếu ta
áp dụng phép tìm kiếm tuần tự vào đây thì đúng là vô nghĩa. Bằng cách chia đa giác
làm hai nửa, phân các nhau bởi hai điểm thấp “nhất” và “cao” nhất (hai đỉnh tô đậm
trên hình vẽ). Ta chỉ phải tìm kiếm nhị phân (xin đọc phần “cấu trúc dữ liệu và giải
thuật”) trên hai nửa này thôi, mỗi nửa sẽ tìm ra một cạnh thoả mãn. Ta tìm điểm i có
toạ độ lớn nhất nhưng nhỏ hơn t.y, cạnh (p[i], p[i + 1]) sẽ là cạnh cần tìm. Như vậy,
chi phí thuật toán cho bài toán này tỷ lệ với LogN. Đến đây, mọi việc gần như đã được
giải quyết, các công việc còn lại là bài tập thực hành của bạn đọc.
Thuật toán chẳng có ý nghĩa gì khi câu hỏi “một điểm có nằm trong đa giác hay
không?” chỉ được dùng có một lần. Nhưng trong trường hợp phải trả lời nhiều lần câu
hỏi như vậy thì thuật toán thực sự đã phát huy tác dụng.
Phương pháp Giả sử cho đa giác có N đỉnh và tọa độ các đỉnh lưu vào mảng a. Để
tính diện tích đa giác ta làm như sau:
250
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
A[n+1].x:=a[1].x; A[n+1].y:=a[1].y
n
S= a i 1.x a i .x a i 1. y a i . y / 2
i 1
Lưu ý: Có thể áp dụng công thức khác để tính diện tích đa giác trong các trường hợp
đặc biệt:
- Nếu đa thức là tam giác (n=3) thì diện tích tính theo công thức
S p p a p b p c với p=(a+b+c)/2
- Nếu đa giác là hình chữ nhật (n=4) có các cạnh là a,b thì diện tích là: S=a.b
- Nếu đa giác là hình vuông (n=4) có cạnh là a thì diện tích là: S= a2
Cho N đa giác lồi A1A2…AN với các đỉnh Ai(xi;yi) có tọa độ nguyên. Hãy tính diện
tích đa giác trên
- Dòng 2: chứa 2xN số nguyên dương x1y1x2y2…xNyN là tọa độ các đỉnh của đa giác.
Mỗi số ghi cách nhau một dấu cách.
Kết quả ghi ra file DIENTICH.OUTgồm một dòng duy nhất chứa diện tích đa giác
(có hai chữ số ở phần thập phân)
Ví dụ:
DIENTICH.INP DIENTICH.OU
T
251
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
5 32.00
-8-80010-24-5
0
Ý tưởng:
Trong mặt phẳng tọa độ trực chuẩn, cho N hình chữ nhật có các cạnh song song với
trục tọa độ. Mỗi hình chữ nhật được xác định bởi tọa độ đỉnh dưới bên trái và tọa độ
đỉnh trên bên phải của nó. Hãy đưa ra dãy các hình chữ nhật theo thứ tự tăng dần diện
tích.
- Dòng 1: Chứa số N
- Dòng i+1 ( 1 I N): ghi 4 số nguyên x1, y1, x2, y2 lần lượt là tọa độ đỉnh dưới bên
trái và đỉnh trên bên phải của hình chữ nhật i (các số ghi trên một dòng cách nhau ít
nhất một dấu cách)
Kết quả: Ghi ra file HCN.OUT dãy các hình chữ nhật sau khi được sắp xếp.
Ví dụ:
HCN.INP HCN.INP
5 -3-40-2
-3-40-2 -6-2-30
-6-400 -6-4 00
-6-2-30 -6 0 07
252
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
0077 0 0 77
-6007
Ý tưởng:
B2. Tính diện tích hình chữ nhật theo công thức: S= x2 x1 y2 y1
Dạng 3: Xác định diện tích phủ bởi các hình chữ nhật
Phương pháp
Giả sử có N hình chữ nhật. Để tính diện tích phủ bởi N hình chữ nhật ta làm như sau:
B1: Sử dụng a, b lần lượt là các mảng lưu hoành độ và tung độ các đỉnh hình chữ nhật
(mỗi hình chữ nhật chỉ cần lưu tọa độ 2 đỉnh đối diện nhau qua tâm hình chữ nhật)
B3. Lần lượt kiểm tra các hình chữ nhật có tọa độ đỉnh trên bên phải là (xi+1;yi+1) và
tạo độ đỉnh dưới bên phải là (xi;yi) với 1 i n- 1. Nếu hình chữ nhật này thuộc một
trong các hình chữ nhật ban đầu thì cộng thêm vào phần diện tích đang cần tìm diện
tích của hình chữ nhật con này.
Trong mặt phẳng tọa độ trực chuẩn, cho N hình chữ nhật có các cạnh song song với
trục tọa độ. Mỗi hình chữ nhật được xác định bởi tọa độ đỉnh dưới bên trái và đỉnh trên
bên phải của nó. Hãy tính diện tích phần mặt phẳng bị phủ bởi các hình chữ nhật trên.
- Dòng 1: Chứa số N
- Dòng i+1 (1 i N): ghi 4 số nguyên x1, y1, x2, y2 lần lượt là tọa độ đỉnh dưới bên
trái và đỉnh trên bên phải của hình chữ nhật i
Các số ghi trên một dòng cách nhau ít nhất một dấu cách
253
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Kết quả: Ghi ra file DT_HCN.OUT diện tích phần mặt phẳng bị phủ bởi hình chữ
nhật trên.
Ví dụ:
DT_HCN.INP DT_HCN.OUT
2 9
0124
1032
3 11
0124
1032
-1 2 1 4
Ý tưởng:
B1. Lập mảng X[1...2N], Y[1..2N] lần lượt chứa hoành độ, tung độ các hình chữ nhật
B2. Lưu tọa độ ban đầu các hình chữ nhật vào mảng a
B4. Lần lượt kiểm tra các hình chữ nhật có tọa độ đỉnh dưới bên trái (xi;yi) và tọa độ
đỉnh trên bên phải (xi+1;yi+1) với 1 i n- 1. Nếu hình chữ nhật này thuộc một trong
các hình chữ nhật ban đầu thì cộng thêm vào phần diện tích đang cần tìm diện tích của
hình chữ nhật con này.
Ví dụ 2. Phủ S
Trên mặt phẳng tọa độ, một hình chữ nhật với các cạnh song song với các trục tọa độ
được xác định bởi 2 điểm đối tâm: đỉnh góc trên bên trái và đỉnh góc dưới bên phải.
Cho N hình chữ nhật song song với các trục tọa độ . Phủ S của các hình chữ nhật có
diện tích nhỏ nhất chứa N hình chữ nhật đã cho
254
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Trong N dòng tiếp theo, mỗi dòng ghi 4 số là tọa độ là tọa độ của 2 đỉnh đối tâm của
hình chữ nhật, các số này là các số nguyên có trị tuyệt đối không quá 100
- Dòng 1 ghi tọa độ hai đỉnh đối tâm của phủ S các hình chữ nhật
- Dòng 2 ghi diện tích các phần hình S không nằm trong hình chữ nhật nào trong N
hình đã cho
Ví dụ:
PHU_CN.IN PHU_CN.OU
P T
2 -2 0 5 4
-2 0 1 3 28
2254
Ý tưởng:
B1. Xác định hình chữ nhật H nhỏ nhất bao tất cả các hình chữ nhật ban đầu bằng
cách: Gọi minx, maxx lần lượt là hoành độ nhỏ nhất và lớn nhất trong các hoành độ
các đỉnh hình chữ nhật đã cho; miny, maxy lần lượt là tung độ nhỏ nhất và lớn nhất
trong các tung độ đã các đỉnh của hình chữ nhật đã cho. Khi đó hình H có tọa độ đỉnh
dưới trái là (minx;miny) và đỉnh trên phải là ( maxx;maxy). Đó là phủ S cần tìm
B2. Tính diện tích S phủ bởi các hình chữ nhật
Trên mặt phẳng cho N hình tròn. Tính diện tích phần mặt phẳng bị phủ bởi các hình
tròn trên.
255
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Từ dòng 2 trở đi mỗi dòng chứa 3 số nguyên dương là tọa độ (x,y) của tâm và bán
kính của từng hình tròn (các trên cùng một dòng ghi cách nhau ít nhất 1 dấu cách)
Kết quả ghi ra file PHU_HT.OUT một dòng duy nhất chứa diện tích cần tìm (lấy 2
chữ số thập phân).
Ví dụ:
PHU_HT.INP PHU_HT.OUT
3 95.72
005
115
701
256
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ý tưởng:
B1. Tìm hình chữ nhật nhỏ nhất có các cạnh song song với các trục tọa độ và chứa
toàn bộ N hình tròn.
B2. Chia hình chữ nhật này thành lưới các ô vuông có cạnh 0.1 đơn vị, với mỗi ô thuộc
hình chữ nhật kiểm tra xem ô này có thuộc vào hình tròn (đã cho) nào đó hay không,
nếu có thì tăng diện tích cần tính lên 0.01 đơn vị.
Dạng 4: Xác định đa giác nhỏ nhất bao tất cả các điểm, đa giác đã cho
Phương pháp Cho N điểm A1,A2,…,AN trên mặt phẳng. Để xác định một đa giác
không tự cắt chứa một số điểm đã cho và bao tất cả điểm còn lại ta làm như sau:
B1: Tìm điểm có tung độ nhỏ nhất. Điểm đó sẽ là đỉnh của đa giác
B2: Giả sử ta đã chọn được điểm PM. Tìm điểm Pi sao cho góc hợp bởi PMPi và trục
hoành là nhỏ nhất và đồng thời góc này phải lớn hơn góc hợp bởi PMPM- 1 và trục
hoành. Điểm Pi sẽ là một đỉnh của đa giác
Lưu ý: Với bài toán tìm đa giác bao nhau thì cần ghi nhớ đa giác a bao đa giác b khi
mọi điểm trong đa giác b đều nằm trong đa giác a.
Cho N điểm A1,A2,…,AN trên mặt phẳng. Các điểm đều có tọa độ nguyên và không có
3 điểm bất kì trong chúng thẳng hàng. Hãy viết chương trình thực hiện các công việc
sau đây: Xác định một đa giác không tự cắt có đỉnh là một số điểm trong các điểm đã
cho và chứa tất cả các điểm còn lại và có chu vi nhỏ nhất. Hãy tính diện tích đa giác
này.
- Dòng 1: Chứa số N
257
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
- Dòng i+1 ( 1≤ i ≤ N ): ghi 2 chữ số nguyên xi, yi là tọa độ Ai. Các số trên cùng một
dòng cách nhau một khoảng trẳng.
- Dòng 1: ghi 3 số K, C, S với K là số đỉnh đa giác tìm được, C là chu vi, S là diện tích
của nó
Ví dụ:
5 4 15.12 14.00
01 44
44 04
04 01
40 40
22
Ý tưởng:
B2. Giả sử ta chọn được điểm PM. Tìm một điểm Pi sao cho góc hợp bởi PMPi và trục
hoành là nhỏ nhất và đồng thời góc này phải lớn hơn góc hợp bởi PMPM- 1 và trục
hoành. Điểm Pi sẽ là một đỉnh của đa giác.
- Với 2 đa giác bất kì luôn có một đa giác mà mọi điểm của nó nằm trong đa giác kia
- Các cạnh của chúng không có điểm chung. Bài toán đặt ra là: Với mỗi đa giác i, có
bao nhiêu đa giác bao nó ? (i nằm trong bao nhiêu đa giác)
258
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Dữ liệu vào: Ghi trong tập tin văn bản DAGIAC.INP gồm:
- Trên N dòng tiếp theo: dòng thứ i+1 ghi thông tin về đa giác có số hiệu thứ i. Bao
gồm số đầu tiên Si là số đỉnh của đa giác ( Si=3), Si cặp số nguyên tiếp theo lần lượt là
hoành độ và tung độ các đỉnh của đa giác.
Các số trên cùng dòng cách nhau ít nhất một dấu cách
4 0
4 1 1 15 1 15 8 1 8 2
493964643 1
4 3 2 11 2 11 7 3 7 3
Ý tưởng:
3848565
B1. Sử dụng các mảng a, vt, kq
(với a[i] lưu giá trị hoành độ nhỏ nhất của các đỉnh của đa giác thứ i, vt[i] chỉ đa giác
thứ i, mảng kq lưu kết quả)
B2. Thực hiện sắp xếp các đa giác theo thứ tự tăng dần của các giá trị hoành độ của
đỉnh của đa giác
B3. Do theo điều kiện bài toán là với 2 đa giác bất kì luôn có một đa giác mà mọi điểm
của nó nằm trong đa giác kia nên KQ[vt[i]]=i- 1 (hoặc KQ[i]:=vt[i]-1).
Cho N hình chữ nhật trên mặt phẳng mà các cạnh song song với các trục tọa độ. Biết
hình chữ nhật i bao hình chữ nhật j nếu cả 4 đỉnh của hình chữ nhật j đều nằm trong
hình chữ nhật i hoặc nằm trên cạnh hình chữ nhật i. Một dãy các hình chữ nhật được
gọi là hình chữ nhật bao nhau chiều dài k ( k≥ 1) nếu dãy này bao gồm các hình chữ
259
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
nhật H1, H2,…,Hk sao cho hình chữ nhật i bao hình chữ nhật i+1 với i=1…(k- 1). Hãy
tìm số k lớn nhất nói trên
- N dòng tiếp theo, dòng thứ i ghi 4 số nguyên x1,y1,x2,y2 ( - 10000< x1,y1,x2,y2
<10000), lần lượt là hoành độ, tung độ của các đỉnh trái trên, phải dưới của hình chữ
nhật.
Kết quả ghi ra file văn bản HCN.OUT gồm một dòng chứa số nguyên duy nhất là số
k tìm được hoặc ghi số -1 nếu không tồn tại số k thỏa điều kiện đề bài
Ví dụ:
HCN.INP HCN.OUT
HCN.INP HCN.OUT
7 4
5 -1
6675
1522
1432
2433
1532
4361
2433
5574
1552
1341
5684
6685
Ý tưởng:
B1. Lập hàm kiểm tra hình chữ nhật j bao hình chữ nhật i thỏa mãn điều kiện:
(x1[j]<=x1[i]) and(y1[j]>=y1[i]) and (x2[j]>=x2[i]) and ( y2[j] <=y2[i]).
B2. Với mỗi hình chữ nhật i (i=1..n) ta kiểm tra xem có bao nhiêu hình chữ nhật j
(j=1..n) bao hình chữ nhật j, sau đó ta lưu số lượng hình chữ nhật j bao hình chữ nhật i
vào mảng kq (giả sử có k hình chữ nhật j bao hình chữ nhật i thì kq[i]=k //k tính bằng
260
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
cách: khởi tạo k:=0; nếu kiểm tra (sử dụng hàm kiểm tra ở trên) đúng thì tăng k lên 1
đơn vị)
B3. Tìm giá trị lớn nhất trong mảng kq, nếu tất cả các giá trị của mảng kq đều bằng 1
thì xuất ra -1.
Hình chữ nhật A bao hình chữ nhật B nếu mọi điểm thuộc hình chữ nhật B đều nằm
trong hoặc thuộc hình chữ nhật A.
Trong mặt phẳng Oxy cho n hình chữ nhật có các cạnh song song với các trục tọa độ
Yêu cầu:
Câu 1. Tìm số hình chữ nhật bao nhau nhiều nhất (số lượng hình phải lớn hơn 1).
Câu 2. Tìm hiệu diện tích của hình chữ nhật nhỏ nhất bao tất cả các hình chữ nhật đã
cho với diện tích của phần mặt phẳng bị phủ bởi các hình chữ nhật đã cho.
- n dòng tiếp theo: Mỗi dòng chứa 4 số dạng a, b, c, d (a,b,c,d là các số nguyên) với
(a;b) là tọa độ đỉnh góc trên trái và (c,d) là tọa độ của đỉnh góc dưới phải hình chữ
nhật.
- Dòng đầu: cho biết tập các hình chữ nhật bao nhau nhiều nhất theo dạng k l m n…
với ý nghĩa – hình chữ nhật thứ k thuộc hình chữ nhật thứ l, hình chữ nhật thứ l thuộc
hình chữ nhật thứ m, hình chữ nhật thứ m thuộc hình chữ nhật thứ n… nếu không có
thì ghi từ -1
- Dòng thứ 2: chứa con số biểu diễn hiệu diện tích hình chữ nhật nhỏ nhất bao tất cả
hình chữ nhật đã cho với diện tích của phần mặt phẳng bị phủ bởi các hình chữ nhật
đã cho.
261
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ví dụ:
3 31 6 6215
0044 4 3285 11
5064 3255
0022 1032
3 -1 0335
1032 9 3 0 10 7
0335 3355
3255
Ý tưởng:
Câu 1.
B1. Dùng mảng a để lưu các hình chữ nhật ban đầu.
B2. Sử dụng mảng dt, vt để lưu diện tích và vị trí các hình chữ nhật ban đầu.
B3. Sắp xếp mảng dt theo thứ tự không giảm (lưu ý nếu hoán đổi dt thì phải hoán đổi
vị trí, ví dụ: nếu hoán đổi dt[i] và dt[j] thì phải hoán đổi vt[i] và vt[j], mảng vt rất quan
trọng, đó cũng chính là đáp án của bài toán).
B4. Sử dụng mảng tg để lưu các hình chữ nhật sau khi đã sắp xếp theo diện tích (lưu ý
mang tg[i]:=a[vt[i]]).
B5. Sử dụng hàm kt(i,j) để kiểm tra xem hình chữ nhật i có nằm trong hình chữ nhật j
(việc kiểm tra này đã biết, lưu ý hình chữ nhật ở đây là mảng tg) hay không. Ta sử
dụng mảng hai chiều kq, nếu kt(i,j) đúng thì kq[i,j]:=j; đồng thời inc(d); tức là:
262
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
begin
d:=1;
for j:=i+1 to n do
if kt(i,j) then
begin
inc(d);
kq[i,j]:=j;
end;
Lưu ý: Vì đề yêu cầu tìm số hình chữ nhật bao nhau nhiều nhất nên d phải lớn nhất và
d phải lớn hơn 1.
Write(vt[k],‘ ’);
For i:=1 to n do
Câu 2.
B2. Tính diện tích S2 phủ bởi các hình chữ nhật (đã biết).
263
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Mọi số tự nhiên đều có thể biểu diễn được dưới dạng nhị phân. Đối với máy tính, các
giá trị số được lưu bởi các biến dưới dạng chuỗi nhị phân có độ dài tương ứng với kiểu
biến đó. Trong ngôn ngữ Pascal, một số kiểu phổ biến như: byte : độ dài 8 BIT –
integer: độ dài 2*8 = 16 BIT –longint : độ dài 4*8 = 32 BIT Các BIT của biến được
đánh số từ phải sang trái bắt đầu từ 1.
1/ Bộ nhớ: Xử lý BIT có thể được dùng làm mảng đánh dấu. Thay vì sử dụng 1 biến
Boolean chỉ đánh dấu được 1 phần tử là True hay False, ta có thể xử lý BIT để đánh
dấu 8 BIT tương ứng với 8 phần tử.
2/ Tốc độ: Các phép xử lý BIT có tốc độ nhanh hơn các phép xử lý khác. Ví dụ: Hai
phép (x div 2) và (x shr 1) là như nhau nhưng phép (x shr 1) có tốc độ nhanh hơn.
Trong các bài toán đòi hỏi việc thay đổi trạng thái nhiều lần thì người ta vẫn hay dùng
xử lý BIT để cải thiện tốc độ chương trình.
1/ Cộng BIT (or): Kết quả bằng tổng giá trị 2 BIT. Trường hợp 2 BIT đều có giá trị
bằng 1 thì kết quả là 1.Ví dụ: x1 = 9 (00001001) x2 = 18 (00010010) x1 or x2 = 27
(00011011)
2/ Đảo BIT (not): Đảo BIT 0 thành BIT 1 và BIT 1 thành BIT 0. Ví dụ: x = 13
(00001101) not (x1) = 242 (11110010)
3/ Nhân BIT (and): Kết quả bằng tích giá trị 2 BIT.Ví dụ: x1 = 9 (00001001) x2 =
12 (00001100) x1 and x2 = 8 (00001000)
4/ Loại trừ BIT (xor): Kết quả là 0 nếu 2 BIT có cùng giá trị, là 1 nếu 2 BIT khác
giá trị.Ví dụ: x1 = 9 (00001001) x2 = 18 (00010010) x1 xor x2 = 27 (00011011)
5/ Dịch sang trái k BIT (shl): Ví dụ: x = 27 (00001101) x shl 2 = 108 (01101100)
264
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
7/ Lấy giá trị BIT: Cho biết BIT thứ k của số x có giá trị bao nhiêu:
Function GetBIT(k , x : LongInt) : LongInt; begin GetBIT := (x shr (k-1)) and 1; end;
8/ Gán giá trị BIT: Gán giá trị c cho BIT thứ k của số x: Procedure SetBIT(c , k :
LongInt; var x : LongInt); begin if c = 1 then x := x or (1 shl (k-1)) else x := x and
(not (1 shl (k-1))); end; Số bù 2: là số âm được biểu diễn trên máy tính.
Lấy phủ định của a sau đó cộng vào bit cuối bên phải của a bit 1: (NOT a) + 1
Tổng quát, đặt m = 2k.p (với p là số lẻ). Hay nói cách khác, k là vị trí của bít 1 bên
phải nhất của m. Trong cây BIT, nút có số hiệu m sẽ là nút gốc của một cây con gồm
2k nút có số hiệu từ m- 2k+1 đến m.
Ví dụ:
Trong cây BIT, nút gốc đại diện cho tất cả các nút con của nó. Ý nghĩa của từ đại
diện ở đây thường dùng là nút gốc lưu tổng giá trị của các nút con. Vì vậy khi tính
toán, ta chỉ cần truy xuất nút gốc là đủ mà không cần thiết phải truy xuất đến các nút
con. Xét ví dụ:
Cho mảng gồm n phần tử a1, a2, …, an. Hãy tính tổng Am = a1 + a2 + … + am (m ≤
n).
Thay vì sử dụng vòng lặp từ 1 đến m để truy xuất từng phần tử ai một (độ phức tạp
O(m)), ta sử dụng cấu trúc BIT như sau:
- t1 = a1
- t2 = a1 + a2
- t3 = a3
- t4 = a1 + a2 + a3 + a4
- t5 = a5
- t6 = a5 + a6
- t7 = a7
- t8 = a1 + a2 + a3 + a4+ a5 + a6 + a7 + a8
-…
- ... (tiếp tục như vậy theo cách xây dựng cây BIT)
266
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
* Để tính A15 (m=15), thay vì phải duyệt từ a1 đến a15, ta chỉ cần tính t8 + t12 +
t14 + t15 .
Tổng quát với m bất kỳ, biểu diễn m thành dạng nhị phân, sau đó lần lượt xóa các
bít 1 của m theo thứ tự từ phải sang trái, tại mỗi bước trung gian chính là chỉ số nút
cần truy xuất trong cây BIT.
2) Xóa bít 1 bên phải nhất còn 1100 -> truy xuất nút 12
3) Xóa bít 1 bên phải nhất còn 1000 -> truy xuất nút 8
Thao tác truy xuất các nút như trên được gọi là getBIT. May mắn là ta có một công
thức rất đơn giản để xóa bít 1 bên phải dùng phép toán AND. Thủ tục getBIT như sau:
int getBIT(int m)
int result = 0;
Vấn đề còn lại là làm thế nào để xây dựng được cây BIT như trên? Cách thực hiện
là ban đầu khởi tạo các nút của cây BIT là 0. Sau đó ứng với mỗi giá trị am thì cập
nhật các nút cha liên quan trong cây.
267
Trại hè Phương Nam lần IV năm 2017 Kỷ yếu hội thảo môn Tin học
Ví dụ:
- Cập nhật giá trị a5 -> cần cập nhật các nút t5, nút cha t6, nút cha t8, nút cha
t16,….
- Cập nhật giá trị a9 -> cần cập nhật các nút t9, nút cha t10, nút cha t12, nút cha
t16,…
- Cập nhật giá trị a4 -> cần cập nhật các nút t4, nút cha t8, nút cha t16,…
Tổng quát với m bất kỳ, biểu diễn m thành dạng nhị phân, nếu cộng 1 vào bít bên
phải nhất của m thì ta được nút cha của m.
2) Cộng 1 vào bít phải nhất thành 0110 -> cập nhật nút 6
3) Cộng 1 vào bít phải nhất thành 0111 -> cập nhật nút 8
4) Cộng 1 vào bít phải nhất thành 10000 -> cập nhật nút 16.
Thao tác cập nhật các nút từ con đến cha như trên được gọi là updateBIT. Ta cũng có
một công thức rất đơn giản để cộng 1 vào bít 1 bên phải nhất dùng phép toán AND.
Thủ tục updateBIT như sau:
{ t[m] += value; }
268
Binary Heap
BINARY HEAP
Phạm Ngọc Bách – Trường Chuyên Hoàng Lê Kha Tây Ninh
MỞ ĐẦU
Cấu trúc Heap được dùng để biểu diễn hàng đợi ưu tiên (Priority Queue), có
nhiều dạng cấu trúc Heap khác nhau như: Binary Heap, Fibonacci Heap, Relax
Heap,… Nhưng chuyên đề tập trung vào Binary Heap và ứng dụng cấu trúc này trong
giải bài toán thực tế.
1. BINARY HEAP
Về hình thức, Heap như một cấu trúc vun đống, trong đó đỉnh là cực trị, tùy
theo bài toán yêu cầu mà ta có thể lưu cực trị bé nhất hoặc lớn nhất. Mục này trình bày
cực trị lớn nhất. Binary Heap là cấu trúc cây nhị phân nhưng được biểu diễn khéo léo
trên mảng một chiều gọi là H. Trong đó, đỉnh cực trị là H[1], phần tử thứ i có thể có
hai con là 2i và 2i+1 và H[i] ≥ max(H[2i], H[2i+1]). Ở hình họa, giá trị lớn nhất
H[1]=18 và lớn hơn H[2] và H[3], tương tự cho bất cứ vị trí i nào đó.
Như vậy, để lấy giá trị lớn nhất thì đơn giản chỉ cần xuất H[1], độ phức tạp
O(1). Nhưng vấn đề là mỗi khi ta thêm một phần tử (push) hay xóa cực trị (H[1])
(pop) thì cực trị mới là gì.
Thêm một phần tử x: push(x)
procedure swap(iH,jH:longint);
var t:longint;
begin
t:=H[iH]; H[iH]:=H[jH]; H[jH]:=t;
1
269
Binary Heap
end;
procedure upHeap(iH:longint);
begin
if (iH=1) or (H[iH] < H[iH div 2]) then exit;
swap(iH,iH div 2);
upHeap(iH div 2);
end;
procedure push(x:longint);
begin
if nH<maxn then
begin
inc(nH); H[nH]:=x;
upHeap(nH);
end;
end;
Thủ tục upHeap(iH) có chức năng so sánh H[iH] với cha H[iH div 2], nếu lớn
hơn thì đổi chỗ và gọi đệ quy upHeap(iH div 2). Độ phức tạp O(logN), do thao tác
upHeap nhị phân (iH div 2). Chi phí bộ nhớ O(N), do biểu diễn trên mảng một chiều
và N bằng số phần tử của Heap nH.
Xóa phần tử cực trị: pop()
procedure downHeap(iH:longint);
var jH:longint;
begin
jH:=iH*2;
if jH<nH then
if (H[jH+1]>H[jH]) then jH:=jH+1; //Làm trội
if (jH<=nH) and (H[iH]<H[jH]) then
begin
swap(iH,jH);
downHeap(jH);
end;
end;
function pop:longint;
var t:longint;
begin
t:=H[1]; H[1]:=H[nH]; dec(nH);
downHeap(1);
exit(t);
end;
Thủ tục downHeap(iH), iH có hai con là iH*2 và iH*2+1, chọn con có giá trị
lớn hơn gán cho jH, so sánh H[iH] với H[jH], nếu bé hơn thì đổi chỗ và gọi đệ quy
downHeap(jH). Độ phức tạp thao tác pop bằng O(logN), do tại iH ta chỉ lựa chọn một
trong hai con iH*2 và iH*2+1.
2
Binary Heap
2. VÍ DỤ 1: IO
Cho trước một danh sách rỗng. Người ta xét hai thao tác trên danh sách đó:
Thao tác "+V" (ở đây V là một số tự nhiên 109): Nếu danh sách đang có ít
hơn 15000 phần tử thì thao tác này bổ sung thêm phần tử V vào danh sách; Nếu
không, thao tác này không có hiệu lực.
Thao tác "-": Nếu danh sách đang không rỗng thì thao tác này loại bỏ tất cả
các phần tử lớn nhất của danh sách; Nếu không, thao tác này không có hiệu lực.
Vấn đề đặt ra là cho trước một dãy không quá 100 000 thao tác, hãy xác định
những giá trị số nào còn lại trong danh sách theo giá trị giảm dần.
IO.INP
Gồm nhiều dòng, mỗi dòng ghi một thao tác. Thứ tự các thao tác trên các dòng
được liệt kê theo đúng thứ tự sẽ thực hiện.
IO.OUT
Dòng 1: Ghi số lượng những giá trị còn lại trong danh sách.
3
Binary Heap
3. KẾT HỢP HEAP VỚI DIJKSTRA
Trong Dijkstra có hai việc chúng ta đặc biệt quan tâm:
a) Lấy u để d[u] nhỏ nhất.
b) Cập nhật d[v] theo d[u].
Với a), nếu dùng vòng lặp duyệt qua tất cả các đỉnh sẽ tốn O(N), nhưng nếu
biểu diễn trong Heap thì u:=pop, chỉ có O(logN). Nhưng việc biểu diễn Heap có khác
so với mục 1) trên, Heap chỉ lưu tên đỉnh đồ thị, do đó để so sánh giữa hai node i và j
trong Heap thực chất là so sánh d[H[i]] với d[H[j]].
Với b), d[v] có thể thay đổi bất cứ lúc nào, do đó phải cập nhật lại Heap, nhưng
cần xác định xem v nằm ở vị trí nào trong Heap, do đó tổ chức thêm pos[v] trả về vị
trí (position) của v trong Heap. Thao tác cập nhật, thực chất là upHeap(pos[v]) sẽ tốn
O(logN).
Do đó các chương trình con bị thay đổi như sau:
procedure swap(iH,jH:longint);
var t:longint;
begin
t:=H[iH]; H[iH]:=H[jH]; H[jH]:=t;
pos[H[iH]]:=iH;
pos[H[jH]]:=jH;
end;
procedure upHeap(iH:longint);
begin
if (iH=1) or (d[H[iH]] > d[H[iH div 2]]) then exit;
swap(iH,iH div 2);
upHeap(iH div 2);
end;
procedure downHeap(iH:longint);
var jH:longint;
begin
jH:=iH*2;
if jH<nH then
if (d[H[jH+1]]<d[H[jH]]) then jH:=jH+1;
if (jH<=nH) and (d[H[iH]]>d[H[jH]]) then
begin
swap(iH,jH);
downHeap(jH);
end;
end;
procedure push(x:longint);
begin
inc(nH); H[nH]:=x; pos[x]:=nH;
upHeap(nH);
4
Binary Heap
end;
function pop:longint;
var t:longint;
begin
if nH = 0 then exit(0);
t:=H[1];
H[1]:=H[nH]; dec(nH); pos[H[1]]:=1;
downHeap(1);
exit(t);
end;
procedure update(v:longint);
begin
if pos[v] = 0 then push(v)
else upHeap(pos[v]);
end;
Còn một lưu ý: Heap được tổ chức để lấy cực trị bé nhất cho phù hợp với d[u]
bé nhất. Đề bài, chương trình và bộ test được đính kèm trong phụ lục.
4. VÍ DỤ 2: SPY
Trùm gián điệp trốn trong 1 căn hầm được đào sâu xuống mặt đất M tầng, mỗi
tầng có N phòng. Các phòng được ngăn cách bằng các cửa rất khó phá. Các phòng có
cửa xuống phòng ngay phía dưới và 2 phòng ở 2 bên. Từ trên mặt đất có N cửa xuống
N phòng tầng 1. Trùm gián điệp ở tầng dưới cùng (tầng M) phòng thứ N (phòng ở bên
phải nhất). Mỗi cửa được làm bằng một kim loại khác nhau với độ dày khác nhau nên
việc phá cửa cần thời gian khác nhau.
Bạn hãy tìm cách đi từ mặt đất xuống phòng của trùm gián điệp nhanh nhất
không hắn thoát mất.
SPY.INP
Dòng 1 ghi M và N
Dòng 2 đến 2M + 1, dòng chẵn ghi N số, dòng lẻ ghi N - 1 số là chi phí để phá
cửa.
SPY.OUT
Ghi ra 1 số là thời gian nhỏ nhất để đến được phòng của Trùm gián điệp
Thí dụ
SPY.INP SPY.OUT
42 44
99 10
1
5
Binary Heap
10 99
1
99 10
1
10 99
1
Giới hạn
1 ≤ M ≤ 2222
1 ≤ N ≤ 100
Chi phí của các cánh cửa thuộc [0, 1000].
Gợi ý: Chúng ta xem mỗi ô như một đỉnh trong đồ thị, một đỉnh sẽ kề với 4 ô kề cạnh,
ta có thể chuyển bài toán thành tìm đường đi ngắn nhất từ 1 đỉnh (M,N) đến mọi đỉnh
bằng thuật toán Dijkstra, với d[u,v] là đường đi ngắn nhất từ (M,N) đến (u,v). Kết hợp
với Heap, mỗi node trong Heap sẽ lưu tọa độ ô (u,v), để biết được vị trí của ô (u,v)
trong Heap ta lưu thêm pos[u,v]. Đề bài, chương trình và bộ test được đính kèm trong
phụ lục.
KẾT LUẬN
Cấu trúc Heap được ứng dụng trong giải nhiều bài toán, đặc biệt là kết hợp với
thuật toán Dijkstra trong bài toán tìm đường đi ngắn nhất, làm giảm độ phức tạp tính
toán. Kinh nghiệm học tốt nhất là nắm chắc kiến thức về Binary Heap và Dijkstra, sau
đó mới kết hợp cả hai và chỉ cần lưu ý thêm: Heap lưu tên đỉnh và pos[v] lưu vị trí
của đỉnh v trong Heap.
PHU LỤC
Tất cả nội dung của chuyên đề được chia sẻ tại địa chỉ sau:
https://drive.google.com/open?id=0B6ZzQE6AlCKDd0Nra3EzbFJPNG8
6
VẬN DỤNG THUẬT TOÁN TÌM KIẾM NHỊ PHÂN
ĐỂ GIẢI QUYẾT CÁC BÀI TOÁN THI HỌC SINH GIỎI
Nguyễn Trọng Nghĩa
THPT Chuyên Tiền Giang
1. LÝ THUYẾT CHUNG
Thuật toán tìm kiếm nhị phân là một trong những thuật toán được áp dụng nhiều
trong khoa học cũng như trong thực tế. Trong các kỳ thi học sinh giỏi các cấp của môn Tin
học thì bài toán tìm kiếm nhị phân là một trong những bài toán thường được các tác giả
chọn làm đề bài của mình. Hôm nay chúng ta cùng nhau tìm hiểu thuật toán này.
Đầu tiên chúng ta cùng tìm hiểu khái niệm bài toán tìm kiếm và thuật toán tìm kiếm
tuyến tính.
1.1. Bài toán tìm kiếm
Bài toán xác định vị trí của một phần tử trong một dãy liệt kê sắp thứ tự thường gặp
trong nhiều trường hợp khác nhau. Chẳng hạn chương trình kiểm tra chính tả của các từ,
tìm kiếm các từ này trong một cuốn từ điển, mà từ điển chẳng qua cũng là một dãy liệt kê
sắp thứ tự của các từ. Các bài toán thuộc loại này được gọi là các bài toán tìm kiếm.
Bài toán tìm kiếm tổng quát được mô tả như sau: xác định vị trí của phần tử X trong
một dãy liệt kê các phần tử phân biệt A1, A2, ..., An hoặc xác định rằng nó không có mặt
trong dãy liệt kê đó. Lời giải của bài toán trên là vị trí của số hạng của dãy liệt kê có giá trị
bằng X (tức là i sẽ là nghiệm nếu X = Ai hoặc là 0 nếu X không có mặt trong dãy liệt kê).
1.2. Thuật toán tìm kiếm tuyến tính
Tìm kiếm tuyến tính hay tìm kiếm tuần tự là bắt đầu bằng việc so sánh X với A1; khi
X = A1, nghiệm là vị trí A1, tức là 1; khi X A1, so sánh X với A2. Nếu X = A2, nghiệm là
vị trí của A2, tức là 2. Khi X A2, so sánh X với A3. Tiếp tục quá trình này bằng cách tuần
tự so sánh X với mỗi số hạng của dãy liệt kê cho tới khi tìm được số hạng bằng X, khi đó
nghiệm là vị trí của số hạng đó. Nếu toàn dãy liệt kê đã được kiểm tra mà không xác định
được vị trí của X, thì nghiệm là 0.
Mã giả đối với thuật toán tìm kiếm tuyến tính được cho dưới đây:
procedure tìm kiếm tuyến tính (X: integer, A1, A2, ..., An: integer phân biệt)
i := 1
while (i n and X Ai)
i := i + 1
if i n then Vitri := i
else Vitri := 0
{Vitri là chỉ số dưới của số hạng bằng X hoặc là 0 nếu không tìm được X}
Ví dụ: Xét bài toán sau: Cho một dãy gồm N = 10 số nguyên A[1..N] như sau:
1 2 3 4 5 6 7 8 9 10
2 4 5 6 9 21 22 30 31 33
Đối với thuật toán tìm kiếm tuần tự thì giá trị X = 21 được tìm thấy sau 6 phép so
sánh.
1
275
Độ phức tạp tính toán của thuật toán trên được tính như sau:
Trường hợp tốt nhất là O(1): khi A[1] = X; trường hợp xấu nhất là O(N) khi không
tìm được X trong dãy.
Độ phức tạp chung của thuật toán là O(N).
Với dãy số trên có cách nào tìm kiếm nhanh hơn không? Tại sao thực hiện được
cách này?
Ta thấy dãy liệt kê A ở trên có các số hạng đã được sắp xếp theo thứ tự tăng dần.
Chẳng hạn, nếu các số hạng là các số thì chúng được sắp từ số nhỏ nhất đến số lớn nhất
hoặc nếu chúng là các từ hay xâu ký tự thì chúng được sắp theo thứ tự từ điển. Thuật toán
thứ hai này gọi là thuật toán tìm kiếm nhị phân. Nó được tiến hành bằng cách so sánh phần
tử cần xác định vị trí với số hạng ở giữa dãy liệt kê. Sau đó dãy này được tách làm hai dãy
liệt kê con nhỏ hơn có kích thước như nhau, hoặc một trong hai dãy con ít hơn dãy con kia
một số hạng. Sự tìm kiếm tiếp tục bằng cách hạn chế tìm kiếm ở một dãy liệt kê con thích
hợp dựa trên việc so sánh phần tử cần xác định vị trí với số hạng giữa dãy liệt kê.
Trong ví dụ trên, bằng thuật toán tìm kiếm nhị phân mà ta tìm được giá trị X = 21
chỉ sau 3 phép so sánh.
Chúng ta cùng nhau tìm hiểu cặn kẽ về thuật toán Tìm kiếm nhị phân và các biến
thể của nó.
1.3. Tìm kiếm nhị phân
1.3.1 Tìm kiếm nhị phân đơn giản
Ý tưởng:
Sử dụng tính chất dãy A đã sắp xếp tăng, ta tìm cách thu hẹp nhanh phạm vi tìm
kiếm bằng cách so sánh X với số hạng ở giữa dãy (Agiữa), khi đó chỉ xảy ra một trong ba
trường hợp:
Nếu Agiữa= X => tìm được chỉ số, kết thúc;
Nếu Agiữa < X => do dãy A đã được sắp xếp tăng nên việc tìm kiếm thu hẹp chỉ
xét từ Agiữa+1 AN;
Nếu Agiữa > X => do dãy A đã được sắp xếp tăng nên việc tìm kiếm thu hẹp chỉ
xét từ A1 Agiữa-1.
So sánh phần tử giữa với X, nếu nó lớn hơn hoặc bằng X ta sẽ xác nhận kết quả
tạm thời.
Tìm trong đoạn đầu để có nghiệm tốt.
4
1.3.3. Tìm kiếm nhị phân trên các đối tượng có nhiều thông tin (các bản ghi)
Những bài tập đòi hỏi phải tìm kiếm các bản ghi nhiều trường thông tin thường rất
phức tạp và dễ nhầm lẫn trong quá trình cài đặt chương trình. Để đơn giản ta sử dụng một
hàm để định nghĩa việc so sánh hai bản ghi với nhau.
1.3.3.1. Xây dựng hàm so sánh hai bản ghi với nhau
Giả sử có hai bản ghi X và Y gồm hai trường p, q trong đó trường p ưu tiên trước
được viết như sau:
Function sosanh(X,Y: Banghi): shortint;
Begin
If (X.p < Y.p) then exit(-1);
If (X.p = Y.p) and (X.q < Y.q) then exit(-1);
If (X.p = Y.p) and (X.q = Y.q) then exit(0);
Exit(1);
End;
Hàm trên cho kết quả -1 nếu bản ghi X nhỏ hơn bản ghi Y; cho giá trị 0 nếu X bằng
Y và cho giá trị 1 nếu X lớn hơn Y.
1.3.3.2. Tìm kiếm nhị phân dựa trên dãy các bản ghi
Thay phép so sánh trực tiếp bằng hàm so sánh.
Đoạn chương trình minh họa:
Function Tknp_record(X: longint): longint;
Var d, c, g: Longint;
Begin
d := 1;
c := N;
While d <= c Do
Begin
g := (d + c) Div 2;
if Sosanh(A[g],X) = 0 then exit(g);
if Sosanh(A[g],X) = -1 then
d := g+1
else c := g-1;
End;
Exit(0);
End;
Bài 2. BCFRIEND
http://www.spoj.com/PTIT/problems/BCFRIEND/
Theo quan niệm của người Á Đông cổ, mỗi cá nhân khi sinh ra đều ứng với một
ngôi sao, được gọi là sao chiếu mệnh. Các hoạt động của cá nhân đều bị chi phối bởi ngôi
sao này, kể cả quá trình kết bạn – hẹn hò. Theo thuyết Âm dương – Ngũ hành, hai người
chỉ có thể tạo lập mối quan hệ bền vững khi các sao chiếu mệnh của họ không có các thuộc
tính tương khắc. Qua hàng nghìn năm quan sát và chiêm nghiệm, các chiêm tinh gia đã ghi
nhận được N sao và hầu hết các tính chất tương sinh – tương khắc giữa chúng. Để có thể
nhanh chóng đáp ứng nhu cầu kiểm tra độ tương hợp của các sao, hiệp hội ABS
(Association of Broker for Single) tạo lập cơ sở dữ liệu ghi nhận tính chất của tất cả các
sao đã khảo sát được. Trong cơ sở dữ liệu này, các sao được đánh số từ 1 tới N; sao
thứ i có một giá trị Si thể hiện khả năng thích nghi của sao gọi là độ thích nghi. Hai sao
khác nhau có thể có cùng độ thích nghi. Thông qua độ thích nghi của các sao, người ta xác
định khả năng tương hợp của chúng. Khả năng tương hợp của 2 sao được tính bằng tổng 2
độ thích nghi của chúng.
Bài toán: Cho số nguyên dương N, dãy s1, s2, …, sn là độ thích nghi của các sao và
số nguyên B. Hãy xác định số lượng T các cặp sao (i, j) mà si + sj = B, với 1≤i<j≤ n.
Ví dụ: trong 5 sao với độ thích nghi là 3, 5, 6, 5, 3 thì có 4 cặp có khả năng tương
hợp bằng 8.
DỮ LIỆU:
5 9
Dòng đầu tiên ghi 2 số nguyên n, B (2 ≤ n ≤ 10 , |B| ≤ 10 )
Mỗi dòng trong N dòng tiếp theo ghi một số nguyên là độ thích nghi của một sao,
độ thích nghi có trị tuyệt đối bé hơn 231.
Hai số trên cùng dòng cách nhau ít nhất một dấu cách.
KẾT QUẢ:
Gồm một dòng là số nguyên T duy nhất
Ví dụ:
bcfriend.inp bcfriend.out
5 8 5 5 4
3 6 3
Hướng dẫn:
Có thể sort lại rồi, với mỗi giá trị Ai, tìm kiếm nhị phân xem có bao nhiêu giá trị Aj
thỏa mãn Ai+Aj = B
Bài 3. Xếp trứng
Cho N quả trứng được đưa vào dây chuyền theo thứ tự (quả trứng thứ i có thể
tích là ai). Ở cuối dây chuyền đã có sẵn M thùng chứa trứng. Các thùng này nhận
6
trứng theo quy tắc: chứa trứng cho đến khi đầy thì chuyển sang thùng khác. Hãy tính
sức chứa K tối thiểu của mỗi thùng để M thùng này có thể chứa hết trứng theo quy
trình trên.
Dữ liệu
Dòng đầu: Ghi 2 số nguyên n, m (0 < n, m ≤ 109)
Các dòng tiếp theo: dãy ai (0 < ai ≤ 106).
Kết quả
Một số duy nhất là số k tìm được.
Ví dụ:
DỮ LIỆU KẾT QUẢ GIẢI THÍCH
5 3 4 12 Thùng 1: a1, a2
6 8 Thùng 2: a3, a4
5 9
Thùng 3: a5
Gợi ý:
Gọi sum là tổng các ai, dmax là giá trị ai lớn nhất, vậy kết quả bài toán nằm trong
đoạn [dmax .. sum]. Dãy ai được chọn phải theo thứ tự, với mỗi giá trị x của phép tìm kiếm
nhị phân ta kiểm tra xem có thể xếp vào đúng M thùng được không.
Function Check(x: longint): Boolean;
Var dem, i: longint;
s: int64;
Begin
dem := 1; s := 0;
for i := 1 to n do
if s+a[i] <= x then s := s+a[i]
else
begin
if a[i] > x then
exit(false);
inc(dem);
if dem > M then exit(false);
s := a[i];
end;
Check := true;
End;
Đoạn chương trình tìm kiếm nhị phân trong trường hợp tìm giá trị nhỏ nhất:
Procedure solve;
Var Dau, Cuoi, Giua: longint;
Begin
Res := sum;
{Chat nhi phan ket qua}
Dau := dmax;
Cuoi := sum;
while (dau < cuoi) do
begin
Giua := (Dau+Cuoi) div 2;
if Check(Giua) then
7
begin
Res := Giua;
Cuoi := Giua-1;
end
else Dau := Giua+1;
end;
End;
Độ phức tạp thuật toán là O(Nlog(sum))
Bài 4. WIRES - Dây dẫn
Cho n đoạn dây điện (1 n 105 ). Đoạn dây thứ i có độ dài li cm ( 0 li 105 ). Cần
phải cắt các đoạn đã cho thành các đoạn sao cho có được k đoạn dây bằng nhau có độ dài
nguyên. Có thể không cần cắt hết các đoạn dây đã cho. Mỗi đoạn dây bị cắt có thể có phần
còn thừa khác 0.
Yêu cầu: Xác định độ dài lớn nhất của đoạn dây có thể nhận được. Nếu không có cách cắt
thì đưa ra số 0.
Dữ liệu: Vào từ file văn bản WIRES.INP có dạng:
Dòng đầu tiên chứa hai số nguyên n, k
Dòng thứ i trong n dòng sau chứa số nguyên li .
Kết quả: Đưa ra file văn bản WIRES.OUT, kết quả trên một dòng dưới dạng số nguyên.
Ví dụ:
WIRES.INP WIRES.QUT
4 11 547 200
802 539
743
Lời giải O(nL)
Ta thử lần lượt độ dài cần tìm ( x1 ) từ nhỏ đến lớn (hoặc lớn về nhỏ), sau đó tiến
hành kiểm tra xem có cắt được k đoạn có độ dài x1 không?
Const MAX = 100000; fi = 'wires.inp'; fo = 'wires.out';
var n, k, res: longint;
L: array[1..MAX]of longint;
procedure ReadFile;
var f: text; i: longint;
Begin
assign(f, fi); reset(f);
Readln(f, n, k);
for i := 1 to n do Readln(f, L[i]);
close(f);
End;
function Check(x1: longint): boolean;
var i, count: longint;
Begin
count := 0;
for i := 1 to n do
count := count + L[i] div x1;
Check := count >= k;
End;
8
procedure Try;
var i, x1, lmax: longint; sum: int64;
Begin
sum := 0;
for i := 1 to n do sum := sum+ L[i];
lmax := sum div k;
res := 0;
for x1 := 1 to lmax do
if Check(x1) then res := x1;
End ;
procedure WriteResult;
var f: text;
Begin
assign(f,fo); ReWrite(f);
Writeln(f,res);
close(f);
End ;
BEGIN
ReadFile;
Try;
WriteResult;
END.
Trong chương trình trên, hàm Check sẽ kiểm tra xem với độ dài x1 có thể có cách
cắt được thành k đoạn hay không? Chi phí kiểm tra là O(n).
Số lượng thử x1 phụ thuộc vào độ dài của các đoạn dây mà độ dài các đoạn dây có
thể lên tới 109.
Lời giải O(nlogL)
Ta có nhận xét sau: nếu độ dài x1 có thể cắt được thành k đoạn thì đương nhiên ta
có thể cắt được thành k đoạn có độ dài x1 1 . Từ nhận xét này ta sẽ giảm thiểu số trường
hợp phải thử bằng thuật toán tìm kiếm nhị phân, cụ thể thay thủ tục Try bằng thủ tục
bsearch_Try như sau:
procedure bsearch_Try;
var i, x1, lmin, lmax:longint;
sum: longint;
Begin
sum := 0;
for i := 1 to n do sum := sum+ L[i];
lmax := sum div k;
lmin := 1;
while lmax > lmin do
Begin
x1 :=(lmax+lmin) div 2;
if Check(x1) then
Begin
res := x1;
lmin := x1+1;
End
else lmax := x1 - 1;
End;
End;
9
Bài 5. NKSGAME - Trò chơi với dãy số (Đề thi HSG QG bảng B – 2007-2008).
Hai bạn học sinh trong lúc nhàn rỗi nghĩ ra trò chơi sau đây. Mỗi bạn chọn trước
một dãy số gồm n số nguyên. Giả sử dãy số mà bạn thứ nhất chọn là: b1, b2, ..., bn còn dãy
số mà bạn thứ hai chọn là c1, c2, ..., cn. Mỗi lượt chơi mỗi bạn đưa ra một số hạng trong dãy
số của mình. Nếu bạn thứ nhất đưa ra số hạng bi (1 ≤ i ≤ n), còn bạn thứ hai đưa ra số hạng
cj (1 ≤ j ≤ n) thì giá của lượt chơi đó sẽ là |bi+cj|.
Ví dụ: Giả sử dãy số bạn thứ nhất chọn là 1, -2; còn dãy số mà bạn thứ hai chọn là
2, 3. Khi đó các khả năng có thể của một lượt chơi là (1, 2), (1, 3), (-2, 2), (-2, 3). Như vậy,
giá nhỏ nhất của một lượt chơi trong số các lượt chơi có thể là 0 tương ứng với giá của lượt
chơi (-2, 2).
Yêu cầu: Hãy xác định giá nhỏ nhất của một lượt chơi trong số các lượt chơi có thể.
Dữ liệu: vào từ file văn bản NKSGAME.INP
Dòng đầu là số nguyên dương (1 ≤ n ≤ 105)
Dòng thứ hai chứa các số là dãy b (|bi| ≤ 109)
Dòng thứ hai chứa các số là dãy c (|ci| ≤ 109)
Kết quả: ghi ra file văn bản NKSGAME.OUT giá trị nhỏ nhất tìm được
Ví dụ:
NKSGAME.INP NKSGAME.OUT
2 0
1 -2
2 3
Ràng buộc: 60% số test ứng với 60% số điểm có 1 ≤ n ≤ 1000
Hướng dẫn giải : Bài toán có thể giải bằng 2 cách:
Cách 1: Sử dụng 2 vòng lặp lồng nhau để tìm giá trị nhỏ nhất. Độ phức tạp là O(N2)
Cách 2: Sử dụng phương pháp tìm kiếm nhị phân như sau:
Sort lại 2 dãy tăng dần.
Với mỗi phần tử thứ i của dãy B tìm phần tử đầu tiên lớn hơn hoặc bằng (-B[i])
trong C, gọi vị trí đó là k, cập nhật res = min(abs(C[k]+B[i]), abs(C[k-1]+B[i]),
abs(C[k+1]+B[i]));
Độ phức tạp là O(NlogN).
Chương trình minh họa:
Uses Math;
Const fi = 'nksgame.inp'; fo = 'nksgame.out'; maxN = 100001;
Type mang = Array[0..maxN] of Longint;
Var n, ans, i, j, x: Longint;
b, c: mang;
Procedure QSort(Var m: mang; L, R: Longint);
Var i, j, x, temp: Longint;
Begin
i := L; j := R;
x := m[(L+R) div 2];
10
Repeat
While m[i] < x do Inc(i);
While x < m[j] do Dec(j);
If i <= j then
Begin
temp := m[i]; m[i] := m[j]; m[j] := temp;
Inc(i); Dec(j);
End;
Until i > j;
If L < j then QSort(m, L, j);
If i < R then QSort(m, i, R);
End;
Function TimNP_Chinhxac(x: Longint): Longint;
Var mid, L, R: Longint;
Begin
L := 1;
R := n;
Repeat
mid := (L+R) div 2;
If c[mid] = x then Exit(mid);
If c[mid] > x then R := mid-1 Else L := mid+1;
Until L > R;
Exit(0);
End;
Function TimNP_gandung(x: Longint): Longint;
Var L, R, mid: Longint;
Begin
L := 1;
R := n;
Repeat
mid := (L+R) div 2;
If c[mid] < x then
Begin
If (c[mid] < x) and (c[mid+1] > x) then Exit(mid+1);
L := mid+1;
End;
If c[mid] > x then
Begin
If (c[mid] > x) and (c[mid-1] < x) then Exit(mid);
R := mid-1;
End;
Until L > R;
Exit(0);
End;
Procedure Xuly;
Var i, j, temp, k: Longint;
Begin
If (b[1] >= 0) and (c[1]>= 0) then
Begin Writeln(b[1]+c[1]); Exit; End;
If (b[n] <= 0) and (c[n]<= 0) then
Begin Writeln(Abs(b[n]+c[n])); Exit; End;
ans := MaxLongint;
For i := 1 to n do
Begin
If TimNP_chinhxac(-b[i]) <> 0 then
Begin
Writeln(0); Exit;
End;
k := TimNP_gandung(-b[i]);
11
temp := Min(Abs(b[i]+c[k-1]), Abs(b[i]+c[k]));
{C[k] > -B[i] và C[k-1] < -B[i] do TimNP_chinhxac ở trên}
ans := Min(ans, temp);
End;
Writeln(ans);
End;
Begin
Assign(input, fi); Reset(input);
Assign(output, fo); Rewrite(output);
Readln(n);
ans := MaxLongint;
For i := 1 to n do Read(b[i]);
Readln;
For i := 1 to n do Read(c[i]);
b[0] := -MaxLongint; c[0] := -MaxLongint;
b[n+1] := MaxLongint; c[n+1] := MaxLongint;
QSort(b, 1, n); QSort(c, 1, n);
Xuly;
Close(input);
Close(output);
End.
Bài 6. BCSEQ1 – Đoạn số có tổng bằng nhau
Một đoạn số có tổng bằng nhau trong một dãy số là một nhóm các số theo đúng thứ
tự ban đầu trong dãy mà nếu nhóm với nhau thì sẽ cho ra cùng một giá trị tổng. Ví dụ với
dãy: 2 5 1 3 3 7 thì ta có thể nhóm thành: (2 5) (1 3 3) (7) cùng cho giá trị tổng là 7.
Chú ý: đoạn đặc biệt chứa tất cả các phần tử của dãy cũng được coi là một đoạn có
tổng bằng nhau với chính giá trị tổng các số của dãy đó.
Yêu cầu: viết chương trình nhận vào các dãy số nguyên dương và trả về giá trị tổng
nhỏ nhất có thể của một đoạn tổng bằng nhau trong dãy.
Dữ liệu vào
Dòng đầu tiên chứa một số nguyên 1 ≤ t ≤ 1000 là số lượng bộ test. Mỗi bộ test bao
gồm:
Dòng đầu tiên chứa thứ tự bộ test và số M (1≤ M ≤ 10000) là số phần tử của dãy.
Các dòng tiếp theo mỗi dòng ghi 10 số của dãy phân cách bởi 1 dấu cách. Dòng
cuối cùng có thể có ít hơn 10 số. (Các số trong dãy đều nhỏ hơn 20000).
Dữ liệu ra
Với mỗi bộ test, in ra trên một dòng gồm số thứ tự bộ test và tổng nhỏ nhất có thể
đạt được của các đoạn số có tổng bằng nhau.
Ví dụ:
Bcseq1.inp Bcseq1.out
3 1 7
1 6 2 21
2 5 1 3 3 7 3 2
2 6
1 2 3 4 5 6
3 20
1 1 2 1 1 2 1 1 2 1
1 2 1 1 2 1 1 2 1 1
12
Hướng dẫn:
Đầu tiên xây dựng mảng A, với A[i] là tổng của A[1]..A[i] tức là
A[i] := A[i-1]+A[i].
Ta nhận xét, nếu N là số phần tử thì ta chỉ có thể chia tối đa [1..n] nhóm. Vậy chỉ có
thể chia thành X nhóm thì tổng của mảng A chia hết cho X. Từ đó ta sẽ thử các trường hợp
đó.
Việc còn lại là xây dựng hàm kiểm tra, có thể chia thành X nhóm hay không. Để xử
lí được vấn đề này, như ở bước đầu, tôi đề nghị xây dựng mảng A, chỉ cần dùng tìm kiếm
nhị phân để kiểm tra là được.
Đầu tiên đứng ở vị trí 0 (gọi là H), tìm kiếm nhị phân tìm vị trí (gọi là vị trí k) mà
A[k] - A[H] = tổng đoạn con được chia, rồi từ vị trí đó cứ tiếp tục tìm như vậy. Nếu tìm có
đủ X lần (số lượng đoạn được chia) thì hàm trả về TRUE, còn không thì thoát ngay hàm và
trả về FALSE.
Chương trình tham khảo:
const fi = 'bcseq1.inp';
fo = 'bcseq1.out';
nmax = 10000;
type data = longint;
var f, g: text;
A: array[0..nmax+1] of data;
n, test: data;
function tknp(dau, cuoi, x: data): data;
var giua: data;
begin
while dau <= cuoi do
begin
giua := (dau+cuoi) div 2;
if A[giua] = x then
exit(giua)
else
if A[giua] > x then
cuoi := giua-1
else
dau := giua+1;
end;
exit(0);
end;
13
procedure xuli;
var i, j: data;
begin
i := a[n];
if A[n] mod i = 0 then
if check(A[n] div i, i) then
begin
writeln(g, test, ' ', A[n] div i);
exit;
end;
i := A[n] div 2;
if A[n] mod i = 0 then
if check(A[n] div i, i) then
begin
writeln(g, test, ' ', A[n] div i);
exit;
end;
for i := trunc(sqrt(A[n])) downto 2 do
if A[n] mod i = 0 then
if check(A[n] div i, i) then
begin
writeln(g, test, ' ', A[n] div i);
exit;
end;
writeln(g, test, ' ', A[n]);
end;
procedure docdl;
var i, j, sl: data;
begin
assign(f, fi); reset(f); assign(g, fo); rewrite(g);
read(f, sl);
A[0] := 0;
for i := 1 to sl do
begin
read(f, test, n);
for j := 1 to n do
begin
read(f, A[j]);
A[j] := A[j-1] + A[j];
end;
xuli;
end;
close(f);
close(g);
end;
Begin docdl; End.
Trên đây tôi đã trình bày một số dạng bài của kỹ thuật “tìm kiếm nhị phân” thông
qua các ví dụ và bài tập minh họa. Những bài được đưa vào thường áp dụng được ngay cấu
trúc tìm kiếm nhị phân đã nêu. Nhưng trong thực tế có nhiều bài chúng ta phải vận dụng
linh hoạt kỹ thuật trên, việc thay đổi giá trị của hai biến đầu và cuối sau mỗi lần kiểm tra
sẽ quyết định tính đúng đắn của thuật toán, nếu không kiểm soát được hai biến này chương
trình có thể dẫn tới lặp vô hạn, đây là điều mà các học sinh rất ngại khi cài đặt bằng kỹ
thuật trên.
Muốn học tốt Tin học thì phải biết vận dụng những kết quả và những suy luận,
chứng minh từ toán học làm cơ sở cho việc tìm kiếm sẽ làm cho những bài toán tin có
14
những giải thuật đơn giản, thời gian chạy chương trình được rút ngắn lại, tìm được kết quả
nhanh chóng và đáng tin cậy hơn. Do đó luyện tập với nhiều bài tập vẫn là cách tốt nhất để
rút ra cấu trúc tìm kiếm nhị phân cho riêng mình.
Tôi viết tham luận này nhằm mục đích cùng trao đổi với Quý Thầy Cô giảng dạy
chuyên đề bồi dưỡng học sinh giỏi Tin học về việc “hệ thống” các kiến thức, một vài kỹ
năng, ứng dụng toán học vào lập trình giải quyết các bài toán tin. Vì kiến thức và thời gian
còn nhiều hạn chế nên chắc rằng tham luận còn có thiếu sót, tôi chân thành đón nhận sự
góp ý của Quý Thầy Cô. Xin chân thành cảm ơn.
Tiền Giang, tháng 7 năm 2017.
15
SỞ GIÁO DỤC VÀ ĐÀO TẠO SÓC TRĂNG
TRƢỜNG THPT CHUYÊN NGUYỄN THỊ MINH KHAI
CHUYÊN ĐỀ
290
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
Có những bài toán hình học giải trên giấy thì rất đơn giản nhưng thể hiện nó trên
máy tính thì cần những chương trình không đơn giản chút nào. Chính vì đều đó làm
cho chuyên đề "Hình học tính toán" trở thành một chuyên đề hay và nó góp phần
không nhỏ cho sự thành công của học sinh trong đội tuyển học sinh giỏi.
Hi vọng nhận được những ý kiến đóng góp của quý thầy cô về nội dung, chất lượng
và hình thức trình bày để cho chuyên đề hoàn thiện hơn.
1 TỔNG QUAN
1.1 Mục tiêu
Sau khi học chuyên đề này, chúng ta nắm được những kiến thức sau:
- Hiểu khái niệm về các đối tượng hình học cơ bản.
- Nắm được thuật toán thực hiện các bài toán hình học cơ bản.
- Nắm được thuật toán giải một số bài toán giải một số bài toán hình học cơ
bản: tính diện tích đa giác, bài toán tìm bao lồi.
- Hiểu được sai số làm tròn.
Các kiến thức cơ bản cần thiết để học chương này bao gồm:
- Kiến thức toán học: công thức tính khoảng cách giữa 2 điểm, tính góc, đa
giác, phương trình đường thẳng, vị trí tương đối giữa hai đoạn thẳng.
- Kĩ thuật lập trình và lập trình đệ quy.
2 NỘI DUNG
2.1 ĐIỂM, ĐƯỜNG THẲNG VÀ ĐOẠN THẲNG; GIAO CÁC ĐƯỜNG
THẲNG VÀ ĐOẠN THẲNG
2.1.2 Dữ liệu lưu trữ các đối tượng hình học cơ bản
Điểm (Point):
Trong hình học, chúng ta xét trong hệ Đề các Oxy, thì một điểm có toạ độ: (x,y).
Chính vì thế ta lưu tọa độ một điểm trong một bản ghi Record:
Kiểu nguyên:
Type
Point = Record
x , y : longint ;
End ;
Kiểu thực:
Type
Point = Record
x ,y : Real ;
End ;
Chính vì vậy khi xét tới toạ độ của P(x,y) thì ta xét P.x, P.y
Chúng ta biết khoảng cách giữa hai điểm P(x1,y1)và Q(x2,y2) trong mặt phẳng:
Function Khoang_Cach(P,Q: Point) : Real ;
Begin
Khoang_Cach:=Sqrt(Sqr(P.x-Q.x)+Sqr(P.y-Q.y));
End ;
Đường thẳng (line):
Trong hình học, chúng ta có phương trình của một đường thẳng trong mặt phẳng:
Ax+By+C =0. Chúng ta coi A, B, C là biểu diễn cho đường thẳng đó. Nếu một đường
thẳng (d):
Ax + By + C = 0, đi qua 2 điểm A (x1,y1) và B(x2,y2) thì nó có:
A:= y1- y2;
B:= x2 - x1;
C:= -(A.x1+B.y1).
Chính vì thế chúng ta dùng thủ tục xác định A, B, C của một đường thẳng đi qua 2
điểm như sau:
Procedure Xac_DinhABC(P, Q: Point, var A , B , C: Longint );
Begin
A := P.y-Q.y ;
B := Q.x-P.x ;
C := -(A*P.x+B*P.y ) ;
End ;
Và chúng ta có thể coi đường thẳng là một kiểu:
Type
Lines = Record
a , b , c : Longint;
End ;
Chú ý:
* ″Lines″ chứ không phải là ″Line″, vì trong Pascal có thủ tục Line để vẽ đường
thẳng. Thông thường chúng ta làm bài hình học để quan sát trực quan thì chúng ta
thường biểu diễn lên hình vẽ. Chính vì thế chúng ta cần phải tránh những sai sót
không đáng có.
* Sở dĩ chúng ta phải khai báo A, B, C trong Longint vì nếu chúng ta lưu A, B, C
trong Longint thì lúc chúng ta tính toán phương trình không tràn bộ nhớ số học.
Đoạn thẳng:
Đoạn thẳng là một phần của đường thẳng, bị giới hạn x, y. Chúng ta xét đoạn thẳng
thông thường cho đi qua 2 điểm. Chính vì thế toạ độ x, y bị giới hạn trong khoảng đó.
Góc :
Góc định hướng α giữa hai vectơ u =(xu; yu) và v =(xv; yv):
xu yv xv yu
sin α =
x 2
u yu2 xv2 yv2
xu xv yv yu
cos α =
x 2
u yu2 xv2 yv2
Đa giác:
Type
Polygon = array[1..n] of Point;
2.1.3 Vị trí tương đối của điểm so với đường thẳng và đoạn thẳng
Bài toán 1: Cho điểm M(xM,yM), A(xA,yA), B(xB,yB). Yêu cầu:
a) Kiểm tra M có thuộc đường thẳng đi qua 2 điểm A, B hay không?
b) Kiểm tra M có thuộc đoạn thẳng AB hay không
c) Kiểm tra M có thuộc tia AB hay không
Biên soạn: Lâm Thanh Phương Trang 4
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
Phƣơng pháp:
Đặt F(X,Y) = (yA-yB)X + (xB-xA)Y + (xAyB - xByA)
- Điểm M thuộc đường thẳng AB khi F(xM,yM) = 0
- Điểm M thuộc đoạn thẳng AB khi:
F(xM,yM)=0 và Min(xA,xB)xM Max(xA,xB) và Min(yA,yB) yM Max(yA,yB)
Point = Record
x ,y : longint ;
End ;
var A, B, M:Point;
Procedure Nhap;
begin
readln(M.x,M.y);
readln(A.x,A.y);
readln(B.x,B.y);
end;
{=============}
Function F(x,y:longint):longint;
begin
F:= (A.y-B.y)*x+(B.x-A.x)*y +(A.x*B.y - B.x*A.y);
end;
{=============}
Procedure kiemtra;
begin
{====Kiem tra M thuoc duong thang====}
if F(M.x,M.y)=0 then writeln('M thuoc duong thang AB')
else writeln('M khong thuoc duong AB');
{====Kiem tra M thuoc doan thang====}
if (F(M.x,M.y)=0) and (min(A.x,B.x)<=M.x) and (M.x<=max(A.x,B.x))
and (min(A.y,B.y)<=M.y) and (M.y<=max(A.y,B.y))
then writeln('M thuoc doan thang AB')
else writeln('M khong thuoc doan thang AB');
{====Kiem tra M thuoc tia====}
if (F(M.x,M.y)=0) and ((M.x-A.x)*(B.x-A.x)>=0)
and ((M.y-A.y)*(B.y-A.y)>=0)
then writeln('M thuoc tia AB')
else writeln('M khong thuoc tia AB');
end;
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
kiemtra;
close(input); close(output);
End.
Ví dụ 1:
BT2.INP BT2.OUT
123 1
2 -1 3 -1.80 -0.60
Ví dụ 2:
BT2.INP BT2.OUT
123 2
5 10 8
Ví dụ 3:
BT2.INP BT2.OUT
123 3
246
Phƣơng pháp:
B1. Tính D = a1b2 - a2b1, Dx = c2b1 - c1b2, Dy = a2c1 - a1c2
B2. Xét 3 khả năng:
+ Nếu D=Dx=Dy=0 thì kết luận 2 đường thẳng trùng nhau
+ Nếu D=0 và ((Dx0) hoặc (Dy0)) thì kết luận 2 đường thẳng song song
+ Nếu D0 thì kết luận 2 đường thẳng cắt nhau tại điểm có (Dx/D, Dy/D)
Chương trình:
program bt2;
uses crt;
const maxn=100;
fi='BT2.INP';
fo='BT2.OUT';
Type
Lines = Record
a , b , c : Longint;
End ;
var L1,L2:Lines;
D,Dx,Dy:real;
Procedure nhap;
begin
readln(L1.a,L1.b,L1.c);
readln(L2.a,L2.b,L2.c);
end;
procedure tinhtoan;
begin
D:=L1.a*L2.b-L2.a*L1.b;
Dx:=L2.c*L1.b-L1.c*L2.b;
Dy:=L2.a*L1.c-L1.a*L2.c;
if (D=0)and(Dx=0)and(Dy=0) then write(3)
else
if (D=0)and((Dx<>0)or(Dy<>0)) then write(2)
else
begin
writeln(1);
writeln(Dx/D:0:2,' ',Dy/D:0:2);
end;
end;
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
tinhtoan;
close(input); close(output)
End.
01 1
23 1.00 2.00
03
21
Ví dụ 2:
BT3.INP BT3.OUT
01 0
23
11
21
Phƣơng pháp:
Bước 1. Tìm giao điểm M của 2 đường thẳng AB và CD
Bước 2. Kiểm tra M có thuộc đồng thời cả 2 đoạn AB và CD hay không. Nếu có đó là
giao điểm cần tìm, ngược lại kết luận không có.
Chƣơng trình:
program bt3;
uses math;
const
fi='BT3.INP';
fo='BT3.OUT';
Type
Point = Record
x ,y : Real ;
End ;
Lines = Record
a , b , c : Real;
End ;
var
A, B, P, Q, M:Point;
L1,L2:Lines;
D,Dx,Dy:real;
Procedure nhap;
begin
readln(A.x,A.y);
readln(B.x,B.y);
readln(P.x,P.y);
readln(Q.x,Q.y);
end;
{===Kiem tra M thuoc doan ==}
Function Ktra(M,A,B:Point):boolean;
begin
if (min(A.x,B.x)<=M.x) and (M.x<=max(A.x,B.x))
and (min(A.y,B.y)<=M.y) and (M.y<=max(A.y,B.y))
then Ktra:=true else Ktra:=false;
end;
{=====Xac dinh he so a,b, c cua phuong trinh duong thang=====}
Procedure Xac_DinhABC(P, Q: Point; var a , b , c: real);
Begin
a := P.y-Q.y ;
b := Q.x-P.x ;
c := -(A*P.x+B*P.y ) ;
End ;
{===Xac dinh D, Dx, Dy===}
procedure xac_dinh_D_Dx_Dy(A,B,P,Q:Point; Var D,Dx,Dy:real);
begin
Xac_DinhABC(A,B,L1.a,L1.b,L1.c);
Xac_DinhABC(P,Q,L2.a,L2.b,L2.c);
D:=L1.a*L2.b-L2.a*L1.b;
Dx:=L2.c*L1.b-L1.c*L2.b;
Dy:=L2.a*L1.c-L1.a*L2.c;
end;
{===Tim giao diem neu co ===}
Biên soạn: Lâm Thanh Phương Trang 10
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
procedure xuat;
begin
xac_dinh_D_Dx_Dy(A,B,P,Q,D,Dx,Dy);
M.x:= Dx/D;
M.y:=Dy/D;
if(D<>0)and(ktra(M,A,B))and (ktra(M,P,Q))
then
begin
writeln(1);
writeln(M.x:0:2,' ',M.y:0:2);
end
else writeln(0);
end;
{==============}
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
xuat;
close(input); close(output);
End.
2.2 ĐA GIÁC
Ví dụ :
BT4.INP BT4.OUT
5 32.00
-8 -8 0 0 1 0 -2 4 -5 0
Giới hạn:
N là số nguyên không quá 100.
Tọa độ các điểm là số nguyên nằm trong phạm vi từ -106 đến 106.
Phƣơng pháp:
Lưu toạ độ các đỉnh đa giác vào mảng A. Chia đa giác thành các hình thang bằng cách
chiếu các cạnh xuống trục hoành (hoặc trục tung). Hình thang được xác định bởi cạnh
A[i]A[i+1] có diện tích là Abs(S) với:
S = (A[i].x - A [i+1].x).(A[i].y + A[i+1].y) /2
Sau khi gán đỉnh A[n+1] = A[1], ta tính diện tích toàn phần của đa giác như sau:
S: = 0;
For i: =1 to n do
S := S + (A[i].x - A [i+1].x).(A[i].y + A[i+1].y);
S: = (1/2) * Abs(S);
Chƣơng trình:
program bt4;
const maxn=100;
fi='BT4.INP';
fo='BT4.OUT';
type point=record
x,y:longint; end;
var A:array[1..maxn+1] of point;
n,i:longint; s:real;
Procedure Nhap;
begin
readln(n);
for i:=1 to n do read(A[i].x,A[i].y);
end;
procedure Dientich;
begin
s:=0;
A[n+1].x:=A[1].x;
A[n+1].y:=A[1].y;
for i:=1 to n do
s:=s+(A[i+1].x-A[i].x)*(A[i+1].y+A[i].y)/2;
writeln(abs(s):0:2);
end;
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
dientich;
close(input); close(output);
Biên soạn: Lâm Thanh Phương Trang 12
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
End.
For i: =1 to n do
S := S + (A[i].x - A [i+1].x).(A[i].y + A[i+1].y);
S: = (1/2) * Abs(S);
Bước 3: Gọi S2 tổng diện tích các tam giác tạo bởi các điểm MA1A2, MA2
A3….MAN A1. Việc tính S2 ta có thể áp dụng công thức tích diện tích đa giác S.
Bước 4: So sánh S và S2 nếu S=S2 thì điểm M nằm trong đa giác ngược lại
thì nằm ngoài đa giác.
Chƣơng trình:
program bt5;
const
fi='BT5.inp';
fo='BT5.out';
nmax=1000;
type
diem=record
x,y: real;
end;
var
a:array[1..nmax] of diem;
i,n: longint;
s,s2:real;
m:diem;
procedure nhap;
begin
readln(n);
for i:=1 to n do
readln(a[i].x,a[i].y);
readln(m.x,m.y);
end;
function stg(c,d,e: diem) : real;
var tam: real;
begin
tam:=0;
tam:= tam + (c.x - d.x)*(c.y + d.y);
tam:= tam + (d.x - e.x)*(d.y + e.y);
tam:= tam + (e.x - c.x)*(e.y + c.y);
stg:= abs(tam)/2;
end;
procedure tinh;
begin
a[n+1]:= a[1];
s:=0; s2:=0;
for i:=1 to n do
s:=s + (a[i].x-a[i+1].x)*(a[i].y+a[i+1].y);
s:= abs(s)/2;
for i:=1 to n do
s2:=s2+ stg(a[i],a[i+1],m);
Biên soạn: Lâm Thanh Phương Trang 14
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
end;
procedure xuat;
begin
if abs(s-s2)< 0.0000000001 then write(1) else write(0);
end;
begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
tinh;
xuat;
close(input); close(output);
end.
x ,y : longint ;
End ;
begin
readln(n);
fillchar(p,Sizeof(p),0);
for i:=1 to n do
readln(p[i].x,p[i].y);
end;
{=so sanh hai so thuc}
Function sosanh( a1,a2:real):boolean ;
begin
sosanh:=abs(a1-a2)<= eps;
end;
min:= 1;
for i := 2 to n do
if (p[i].y < p[min].y) or (p[i].y = p[min].y) and (p[i].x < p[min].x)
Biên soạn: Lâm Thanh Phương Trang 16
Chuyên đề hình học tính toán Tổ Tin học - Trường THPT Chuyên NTMK
then
min:=i; m := 0;
p[n + 1] := p[min]; {de phat hien diem ket thuc}
{m la so diem tren bao}
minangle:=0.0;
repeat
Inc(m);
t:=p[m];
p[m]:=p[min];
p[min]:=t;
{min:=n+1;}v:=minangle; minangle:=360.0;
for i := m + 1 to n + 1 do
begin
if goc(p[m], p[i]) > v then
if goc(p[m], p[i]) < minangle then {trong nhung goc lon hon goc da chon thi chon
goc nho nhat}
begin
min:=i;
minangle := goc(p[m],p[min]);
L[m] := i;
end;
end;
until min= n + 1;
end;
procedure xuat;
begin
writeln(m);
for i:=1 to m do
writeln(p[l[i]].x,' ',p[l[i]].y);
end;
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
nhap;
tinhtoan;
xuat;
close(input); close(output);
End.
3 BÀI TẬP
3.1 Trên mặt phẳng cho một tam giác và một đoạn thẳng. Tính độ dài của đoạn thẳng
nằm trong tam giác.
3.2 Trên mặt phẳng cho N điểm. Tìm hai điểm xa nhất trong N điểm đó.
3.3 Trên mặt phẳng cho N điểm. Tìm hai điểm gần nhất trong N điểm đó.
3.4 Cho hai hình chữ nhật với các cạnh song song với hệ trục tọa độ. Mỗi hình chữ
nhật xác định bởi tọa độ hai đỉnh đối. Tìm diện tích phần mặt phẳng được phủ bởi hai
hình chữ nhật và diện tích phần chung của chúng.
3.5 DIỆN TÍCH N HÌNH CHỮ NHẬT (Tên file: SHCN.PAS)
Trên mặt phẳng tọa độ cho N hình chữ nhật (HCN) có diện tích khác 0 và có các
cạnh song song với các trục tọa độ. Mỗi HCN được mô tả bằng bộ bốn số nguyên
(x1,y1) và (x2,y2) biểu thị tọa độ nguyên của hai đỉnh đối diện.
Yêu cầu: xác định diện tích phần mặt phẳng bị các HCN phủ.
Dữ liệu: tệp văn bản SHCN.INP Dòng đầu tiên: số tự nhiên 1 < N
<= 1000.
Dòng thứ i trong N dòng tiếp theo, mỗi dòng chứa 4 số nguyên x1, y1, x2, y2 cách
nhau qua dấu cách.
Kết quả: tệp văn bản SHCN.OUT chứa tổng đơn vị diện tích trên mặt phẳng bị
các HCN phủ.
Ví dụ:
SHCN.INP SHCN.OUT
5 35
0024
0246
5337
4165
7390
3.6 BÀI DIỆN TÍCH HÌNH HÌNH CHỮ NHẬT 2 (Tên file: HCN2.PAS)
Cho N hình chữ nhật (2<N<500) có các cạnh song song với hai trục toạ độ và
toạ độ các đỉnh đều nguyên. Các hình chữ nhật được đánh số từ 1 đến N.
Yêu cầu: Hãy tìm hai hình chữ nhật mà phần giao nhau của chúng có diện tích
lớn nhất.
Dòng thứ i trong N dòng tiếp theo mô tả hình chữ nhật thứ i, chứa 4 số nguyên
x1, y1, x2, y2 trong đó (x1, y1) là toạ độ đỉnh trái dưới và (x2, y2) là toạ độ đỉnh phải
trên của các hình chữ nhật (-1000<x1< x2 <1000;-1000<y1< y2 <1000).
Kết quả: xuất ra file HCN2.OUT gồm 1 dòng duy nhất, chứa 2 số nguyên dương
cho biết chỉ số của hình chữ nhật tìm được.
Ví dụ:
HCN2.INP HCN2.OUT
3 12
1155
-5 -5 5 5
10 10 1000 1000
3.7 HÌNH CHỮ NHẬT LỒNG NHAU (Tên file: HCN3.PAS)
Cho N hình chữ nhật có các cạnh song song với hai trục toạ độ và toạ độ các đỉnh
đều nguyên. Các hình chữ nhật được đánh số từ 1 đến N. Hình chữ nhật thứ i được cho
bởi 4 số nguyên xi1, yi1, xi2, yi2 trong đó (xi1, yi1) là toạ độ đỉnh trái dưới và (xi2, yi2 ) là
toạ độ đỉnh phải trên của các hình chữ nhật . Ta nói hình chữ nhật thứ i nằm trong hình
chữ nhật thứ j nếu trên mặt phẳng toạ độ, mọi điểm của hình chữ i đều thuộc hình chữ
nhật j.
Yêu cầu: Với N hình chữ nhật cho trước, hãy tìm k hình chữ nhật với chỉ số i1, i2,
...., ik sao cho hình i1 nằm trong hình i2, hình i2 nằm trong hình i3,..., hình ik-1 nằm trong
hình ik và k là lớn nhất. Biết rằng hai hình chữ nhật bất kì trong N hình chữ nhật đã
cho hoặc rời nhau hoặc một trong hai hình nằm trong hình còn lại.
Dữ liệu: Vào từ file văn bản HCN3.INP:
Dòng đầu tiên chứa số nguyên dương N (2<N<100)
N dòng tiếp theo, dòng thứ i chứa 4 số nguyên xi1, yi1, xi2, yi2
Kết quả: Ghi ra file văn bản HCN3.OUT số k tìm được.
Ví dụ:
HCN3.INP HCN3.OUT
3 2
1174
3166
2254
3.8 QBPOINT - Bộ ba điểm thẳng hàng (http://vn.spoj.com/problems/QBPOINT/)
Trong các cuộc thi tin học, sự xuất hiện của những bài toán hình học làm đội tuyển
CBQ khá lúng túng. Do đó thầy Thạch quyết định cho đội tuyển luyện tập các bài toán
hình học. Bắt đầu từ điểm, thầy đưa ra bài toán:
Cho n điểm trong mặt phẳng Oxy, hãy đếm số bộ 3 điểm thằng hàng
Input
Dòng thứ nhất ghi số N là số điểm trên mặt phẳng.
N dòng tiếp theo, mỗi dòng ghi tọa độ của một điểm.
Output
Một số duy nhất là số bộ 3 điểm thẳng hàng.
Example
Input:
6
00
01
02
11
20
22
Output:
3
Giới hạn:
1 ≤ N ≤ 2000.
Tọa độ các điểm có trị tuyệt đối không quá 10000.
MỤC LỤC
LỜI NÓI ĐẦU ............................................................................................................................ 1
CHUYÊN ĐỀ . HÌNH HỌC TÍNH TOÁN ................................................................................ 2
1 TỔNG QUAN .................................................................................................................... 2
1.1 Mục tiêu ....................................................................................................................... 2
1.2 Kiến thức cơ bản cần thiết ........................................................................................... 2
1.3 Nội dung cốt lõi ........................................................................................................... 2
2 NỘI DUNG ........................................................................................................................ 2
2.1 ĐIỂM, ĐƯỜNG THẲNG VÀ ĐOẠN THẲNG; GIAO CÁC ĐƯỜNG THẲNG VÀ
ĐOẠN THẲNG...................................................................................................................... 2
2.1.1 Đối tượng hình học cơ bản ................................................................................... 2
2.1.2 Dữ liệu lưu trữ các đối tượng hình học cơ bản ..................................................... 2
2.1.3 Vị trí tương đối của điểm so với đường thẳng và đoạn thẳng .............................. 4
2.1.4 Giao của các đường thẳng .................................................................................... 6
2.1.5 Giao của các đoạn thẳng ....................................................................................... 8
2.2 ĐA GIÁC ................................................................................................................... 11
2.2.1 Diện tích đa giác ................................................................................................. 11
2.2.2 Điểm thuộc đa giác ............................................................................................. 13
2.2.3 Đa giác không tự cắt (bao lồi) ............................................................................ 15
3 BÀI TẬP ........................................................................................................................... 17
TÀI LIỆU THAM KHẢO ........................................................................................................ 21
- Ý tưởng của thuật toán Tarjan: Tìm kiếm theo chiều sâu (DFS) bắt đầu từ một
đỉnh tùy ý, và sau đó tìm kiếm sâu dần trên bất kỳ các đỉnh nào chưa được xét để tìm “cung
chốt”. Việc tìm kiếm sẽ không xét đến bất kỳ đỉnh nào đã được xét trước đó, quá trình tìm
kiếm tạo nên các cây con của cây tìm kiếm, gốc của những cây con đó chính là gốc của các
thành phần liên thông mạnh nếu có.
- Cài đặt thuật toán Tarjan: Dựa trên thứ tự duyệt đến, gọi Color[u] là màu đỉnh
u, nếu Color[u] là white (màu trắng) thì đỉnh u chưa được thăm, nếu là Gray (màu xám)
thì đỉnh u đã được thăm nhưng chưa duyệt xong, nếu là Black (màu đen) thì đỉnh u đã bị
xóa khỏi nhánh cây DFS. Gọi Number[u] là thứ tự duyệt đến của đỉnh u, Low[u] là giá trị
Number[.] nhỏ nhất trong các đỉnh mà có thể đến được từ một đỉnh v nào đó của nhánh
DFS gốc u bằng một cung.
+ Nếu đỉnh v đã thăm thì Low[u]=Min(Low[u], Number[v])
+ Nếu đỉnh v chưa thăm thì Low[u]=Min(Low[u], Low[v])
+ Nếu Low[u]>=Number[u] thì cung (u,v) là cung chốt. => Các đỉnh trong cây
DFS gốc u là các thành phần song liên thông của đồ thị.
- Thuật toán:
Procedure Tarjan(u);
Begin
Time:=Time+1; Number[u]:=Time;
Low[u]:=+∞; Color[u]:=Gray;
Push(u);//đẩy u vào stack
For vV; (u,v)E do
If Color[v]=Gray then //đỉnh v đã thăm
Begin
Low[u]:=Min(Low[u],Number[v]);
1. Bài toán tìm thành phần liên thông mạnh của đồ thị:
Bài 1: Bảo vệ
Một thành phố có N địa điểm chiến lược và M con đường một chiều giữa các địa
điểm. Là thị trưởng của thành phố, bạn sẽ phải bảo vệ an toàn cho N địa điểm này.
Để có thể bảo vệ cho các địa điểm, bạn phải xây dựng các đồn cảnh sát tại một vài
địa điểm. Đồn cảnh sát tại địa điểm i có thể bảo vệ cho địa điểm j nếu i = j hoặc cảnh sát có
thể đi tuần tới địa điểm j từ i và có thể quay trở lại đồn tại địa điểm i.
Để có thể xây dựng được các đồn cảnh sát cần phải mất chi phí, do địa hình mỗi địa
điểm là khác nhau nên chi phí xây dựng đồn cũng có thể khác nhau.
Bạn phải xác định số tiền nhỏ nhất để xây dựng các đồn cảnh sát để có thể bảo vệ
được tất cả N địa điểm, hơn nữa bạn phải đưa ra có bao nhiêu cách xây dựng để đảm bảo
chi phí nhỏ nhất đó.
INPUT: SECURITY.INP
Dòng 1 chứa số nguyên dương N (1 ≤ N ≤ 105)
Dòng 2 chứa N số nguyên, trong đó số nguyên thứ i là chi phí để xây dựng đồn cảnh
sát tại địa điểm i (chi phí ≤ 109).
Dòng 3 chứa số nguyên M (0 ≤ M ≤ 3*105)
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
const int oo = INT_MAX;
const long long e=1000000007;
int n, m;
vector<int> a[N];
stack<int> st1, st2;
int cp[N], Num[N], Low[N];
int id, mi;
long long sl, kq1, kq2;
void nhap(){
ifstream f("security.inp");
f>>n;
for (int i=1; i<=n;i++) f>>cp[i];
f>>m;
for (int i=1; i<=m; i++) {
void Tarjan(int u) {
id=id+1;
Num[u]=Low[u]=id; //thu tu duyet den dinh u
st1.push(u);//them u vao stack
for (int i=0; int v=a[u][i]; i++) // duyet cac canh ke u
if (Num[v]>0) Low[u]=min(Low[u], Num[v]); // v da tham
else { // dinh v chua tham
Tarjan(v);
Low[u]=min(Low[u], Low[v]);
}
if (Low[u]>=Num[u]){ // u la chot vung lien thong manh
int v;
mi=oo;
sl=0;
st2=st1;
do { //tinh chi phi nho nhat
v=st1.top();
mi=min(mi, cp[v]);
st1.pop();
Num[v]=Low[v]=oo; // v ra khoi vung lien thong manh
} while (v!=u);
// tinh so dinh = min
do {
v=st2.top();
st2.pop();
//Num[v]=Low[v]=oo; // remove v from graph
if (cp[v]==mi) sl=sl+1;
} while (v!=u);
}
}
main(){
nhap();
for (int i=1; i<=n; i++)
a[i].push_back(0);
for (int i=1; i<=n; i++) Num[i]=0;
kq2=1; kq1=0;
for (int k=1; k<=n; k++)
if (Num[k]==0) {
id=0;
Tarjan(k);
kq1=kq1+mi; // cong don gia nho nhat
}
ofstream g("security.out");
g <<kq1 <<" "<<kq2%e; // chia lay du
g.close();
}
2. Bài toán tìm thành phần song liên thông của đồ thị:
Bài 2. Đường đua dài nhất
Mạng giao thông của thành phố Cao Lãnh có N nút giao thông. Giữa hai nút giao
thông có tối đa một đường phố hai chiều nối trực tiếp giữa chúng. Nhân dịp chào mừng kỷ
niệm 30 năm ngày thành lập, Lãnh đạo thành phố quyết định tổ chức một cuộc đua xe đạp.
Đường đua xe đạp sẽ xuất phát từ một nút bất kỳ, qua một số nút khác và trở lại nút ban
đầu sao cho không có nút nào (trừ nút xuất phát) đường đua qua đó hai lần. Thật ngạc nhiên,
mạng lưới giao thông của thành phố cho phép lập nhiều đường đua xe đạp như vậy tuy
nhiên: mỗi một đường phố sẽ thuộc không quá một đường đua thỏa mãn điều kiện nêu trên.
Hãy tìm đường đua có số đường phố khác nhau đi qua nhiều nhất.
INPUT: MAXCYCLE.INP
Dòng đầu ghi hai số nguyên N, M là số nút giao thông và số đường phố trong thành
phố (N≤5000, M≤100000)
M dòng tiếp theo, mỗi dòng ghi hai số nguyên u, v thể hiện hai nút của một đường
phố
OUTPUT: MAXCYCLE.OUT
Ghi một số nguyên duy nhất là độ dài (số lượng đường phố khác nhau) của đường
đua dài nhất.
Ví dụ:
MAXCYCLE.INP MAXCYCLE.OUT
78 4
34
14
13
71
27
75
56
62
Hướng dẫn thuật toán:
Tìm các thành phần song liên thông, trong mỗi thành phần song liên thông ta đếm
số đỉnh của nó. Kết quả bài toán là thành phần song liên thông có nhiều đỉnh nhất.
Chuyên đề: Thuật toán Tarjan và ứng dụng Trang: 5/16
TRẠI HÈ PHƯƠNG NAM - LẦN THỨ IV – CÀ MAU NĂM 2017
#include <bits/stdc++.h>
using namespace std;
const int nmax = 5000;
int n,m,num[nmax],low[nmax],id = 0,maxx;
vector <int> a[nmax];
struct canh
{
int u,v;
};
stack <canh> s;
void nhap(){
ifstream f("MAXCYCLE.inp");
f >> n >> m;
for(int i=1; i<=m; i++){
int u,v;
f >> u >> v;
a[u].push_back(v);
a[v].push_back(u);
}
f.close();
}
ofstream g("MAXCYCLE.out");
g<< maxx;
g.close();
}
#include <bits/stdc++.h>
using namespace std;
const int N=1e6; // de cho 1e4;
vector<int> a[N];
int aa[N], bb[N], fa[N], fb[N], num[N], low[N];
int n, m, k,kk, l,ll, id;
long dem1=0, dem2=0;
void nhap(){
ifstream f("ABNET.INP");
int p, q;
f>>n>>m>>k>>l;
}
void Tarjan(int u){
id=id+1;
num[u]=low[u]=id;
fa[u]=aa[u];
fb[u]=bb[u];
int v;
for (int i=0; v=a[u][i]; i++){
//a[v][u]=0;
for(int j=0; j<a[v].size(); j++){
if (a[v][j]==u) a[v].erase(a[v].begin()+j);
}
#include <bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int nmax = 1e5+5;
vector <int> adj[nmax];
void DFS(int u)
{
dd[u] = 1;
for (int i=0; i<adj[u].size(); i++)
{
int v = adj[u][i];
if (dd[v] == 0)
{
d[v] = d[u] + 1;
cha[v] = u;
DFS(v);
}
}
}
void dinh_chieu()
{
for (int i=1; i<=m; i++)
{
int u = dsc[i].fi;
int v = dsc[i].se;
if (cha[v] == u) adj1[u].push_back(v);
else if (cha[u] == v) adj1[v].push_back(u);
else if (d[u] < d[v]) adj1[v].push_back(u);
else adj1[u].push_back(v);
}
}
void DFS1(int u)
{
dd[u] = 1;
id++;
num[u] = id;
low[u] = id;
for (int i=0; i<adj1[u].size();i++)
{
int v=adj1[u][i];
if (dd[v] == 0)
{
DFS1(v);
low[u] = min(low[u],low[v]);
}
else if (dd[v] == 1) low[u] = min(num[v],low[u]);
}
if (low[u] == num[u]) dem++;
}
void tajan(){
memset(dd,0,sizeof(dd));
void ghi(){
if (dem != 1) cout << 0;
else
{
cout << 1 << endl;
for (int u=1; u<=n; u++)
for (int i=0; i<adj1[u].size(); i++)
{
int v = adj1[u][i];
cout << u << " " << v << endl;
}
}
}
main(){
freopen("oneway.inp","r",stdin);
freopen("oneway.out","w",stdout);
doc();
DFS(1);
dinh_chieu();
tajan();
ghi();
}
KẾT LUẬN
Hiểu rõ được cơ chế hoạt động của phương pháp tìm kiếm theo chiều sâu (DFS)
được thể hiện thông minh bằng thuật toán Tarjan cho ta cách cài đặt rất ngắn gọn, rõ ràng.
Những cải tiến nhỏ trong thuật toán có thể đem lai nhiều điều thú vị, giải quyết được nhiều
lớp bài toán khác nhau, phần nào cho thấy được tầm quan trọng của thuật toán Tarjan.
Với khả năng và năng lực còn hạn chế, các bài giải có thể chưa sáng, chưa tối ưu và
bài tập trong chuyên đề này được tổng hợp từ nhiều nguồn tài liệu với hy vọng thông qua
chuyên đề này tôi đã truyển tải đến đồng nghiệp một phần nào đó cách sử dụng thuật toán
Tarjan.
Thành phố Cao Lãnh, ngày 30/5/2017
Người viết
Huỳnh Tấn Thông
[] Tải bộ test:
https://drive.google.com/file/d/0Bz7R_T5dzekpaG52SWtvNUIxeXM/view?usp=sharing