Trong các chuyên đề ôn thi học sinh giỏi thì các chuyên đề mô hình thiết kế thuật toán chia để trị cùng với mô hình Quay lui, mô hình Tham lam, Quyhoạch động tạo thành bộ công cụ quan tr
Trang 11 MỞ ĐẦU
1.1 Lý do chọn đề tài.
Trong các chuyên đề ôn thi học sinh giỏi thì các chuyên đề mô hình thiết
kế thuật toán chia để trị cùng với mô hình Quay lui, mô hình Tham lam, Quyhoạch động tạo thành bộ công cụ quan trọng hỗ trợ thiết kế các thuật toán giảiquyết một số lượng lớn các bài tập một cách hiệu quả Trong đó, nổi bật vẫnphải kể tới một mô hình thiết kế dựa trên nguyên lí của thuật toán tìm kiếm nhịphân
Được đánh giá là một trong những thuật toán phổ biến nhất trong lĩnh vựcTin học, mô hình tìm kiếm nhị phân có hiệu quả đặc biệt về thời gian chạy cũngnhư sự rõ ràng về cách thức thiết kế thuật toán Có thể nói, tìm kiếm nhị phânkhông chỉ đơn thuần là thuật toán tìm kiếm mà còn là một mô hình tư duy thiết
kế thuật toán phổ biến Mô hình này dựa trên nguyên lí hoạt động của thuật toántìm kiếm nhị phân, đó là tìm kiếm một phần tử trên một dãy giá trị đã được sắpxếp bằng cách chia nhỏ miền tìm kiếm, sau mỗi lượt không gian tìm kiếm đượcthu gọn giảm bớt một nửa
Quá trình dạy tại các lớp mũi nhọn và ôn thi HSG cấp Tỉnh, tôi đã vận dụng
mô hình tìm kiếm nhị phân để giúp các em nhìn nhận một bài toán từ nhiều góc
độ khác nhau Qua đó có thể dễ dàng nhận ra việc áp dụng mô hình này để giải
bài toán nào đó Vì vậy, tôi đã chọn đề tài “Vận dụng mô hình tìm kiếm nhị phân và sử dụng các hàm tìm kiếm trong thư viện của ngôn ngữ lập trình C+ + giúp học sinh giải quyết tối ưu một số dạng toán tìm kiếm nhằm nâng cao hiệu quả bồi dưỡng học sinh giỏi” làm sáng kiến kinh nghiệm của mình trong
năm học 2021 – 2022 để trao đổi với đồng nghiệp Đây là một phương pháp tôi
đã thực hiện rất hiệu quả tại ngôi trường THPT Triệu Sơn 3, đồng thời cũng hyvọng cách làm này sẽ được hoàn thiện, bổ sung và nhân rộng trong các trườngTHPT khác trong Tỉnh
1.2 Mục đích nghiên cứu.
- Phát triển kĩ năng thiết kế thuật toán theo mô hình chia để trị, tức là giáoviên cần quan tâm kĩ tới khả năng nhận dạng bài toán, khả năng nhìn ra mô hìnhtìm nhị phân trong bài toán
- Vận dụng mô hình tìm kiếm nhị phân để đưa ra được thuật toán phù hợp.Bên cạnh đó, cần quan tâm tới yêu cầu học sinh đánh giá độ phức tạp thuật toán
- Tìm hiểu và vận dụng các hàm tìm kiếm trong thư viện của ngôn ngữ lậptrình C++ để giải quyết các bài toán tìm kiếm
1.3 Đối tượng nghiên cứu.
- Mô hình tìm kiếm nhị phân
- Các hàm tìm kiếm trong thư viện của ngôn ngữ lập trình C++
- Một số dạng bài tập thi HSG các cấp
- Sự tư duy, ý thức học tập của học sinh ôn thi học sinh giỏi
Trang 21.4 Phương pháp nghiên cứu.
- Điều tra khả năng tiếp cận môn Tin học lập trình của giáo viên THPT
- Điều tra khả năng tiếp cận môn Tin học lập trình của học sinh THPT
- Tham khảo tài liệu, nghiên cứu các đề thi, các bài toán thiên về tư duytoán học cơ bản chuyển về bài toán lập trình,
- Phương pháp thực nghiệm trên đối tượng là học sinh THPT
2 Nội dung sáng kiến kinh nghiệm.
2.1 Cơ sở lí luận của sáng kiến kinh nghiệm.
Trong bối cảnh toàn ngành GD-ĐT đang nỗ lực đổi mới phương pháp dạyhọc theo hướng phát huy tính tích cực chủ động của học sinh trong hoạt động
học tập Điều 24.2 của Luật giáo dục đã nêu rõ: “Phương pháp giáo dục phổ thông phải phát huy tính tích cực, tự giác, chủ động, sáng tạo của học sinh, phù hợp với đặc điểm của từng lớp học, môn học; bồi dưỡng phương pháp tự học, rèn luyện kỹ năng vận dụng kiến thức vào thực tiễn, tác động đến tình cảm, đem lại niềm vui, hứng thú học tập cho học sinh ”.
Như vậy, chúng ta có thể thấy định hướng đổi mới phương pháp dạy học
đã được khẳng định, không còn là vấn đề tranh luận Cốt lõi của việc đổi mớiphương pháp dạy học ở trường phổ thông là giúp học sinh hướng tới việc họctập chủ động, chống lại thói quen học tập thụ động Với một số nội dung trong
đề tài này, học sinh có thể tự học, tự rèn luyện thông qua một số bài tập, dạngbài tập cụ thể
2.2 Thực trạng của vấn đề cần giải quyết
Qua thực tế giảng dạy ở trường THPT Triệu Sơn 3, tôi nhận thấy khi họcđến chương trình tin học lớp 11 đa số học sinh đều cho rằng đây là môn học khónhất trong các môn học, nhiều em còn sợ môn học này
Mô hình tìm kiếm nhị phân thường được dạy ngay sau giai đoạn học sinh họcxong phần kĩ thuật lập trình cơ bản Thời điểm này, học sinh đã có thể sử dụngngôn ngữ lập trình cùng với các công cụ có sẵn trong thư viện để thể hiện thuậttoán Tuy nhiên, khả năng tự mình nhìn nhận, đánh giá và thiết kế được mộtthuật toán cho bài toán mới còn hạn chế
Khi gặp các bài toán phải sử dụng kiểu dữ liệu lớn nhiều em lúng lúng.Việc giải các bài toán với kiểu dữ liệu lớn thực sự cần thiết cho các em khi làmcác bài toán lập trình trong chương trình Tin học phổ thông nói riêng và việcgiải quyết các bài toán thực tế nói chung
Thực tế cho thấy, các đề thi HSG của tỉnh Thanh Hóa và các tỉnh đều cócác bài tập tìm kiếm với số lớn Nếu học sinh không hiểu rõ và nhận biết tốt thìthường khi giải các bài tập này hay gặp nhiều sai sót hoặc không xử lý hết testtheo yêu cầu
Trang 32.3 Các giải pháp giải quyết vấn đề
2.3.1 Tổng quan về mô hình tìm nhị phân
Dựa trên thuật toán tìm kiếm nhị phân, mô hình thiết kế thuật toán dựa trêndạng này thường có phát biểu tổng quát dạng: Tìm kiếm một giá trị thoả mãnđiều kiện nào đó trên một miền giá trị được sắp thứ tự
Nhận dạng bài toán: Tìm một giá trị trên miền giá trị đã được sắp xếp.
Định hướng một số công việc cụ thể khi phân tích bài toán:
+ Lặp lại quá trình tìm kiếm với miền giá trị mới
Mặc dù thuật toán tìm kiếm nhị phân có nhiều cách cài đặt, ngoài ra thư việnSTL C++ cũng có các hàm hỗ trợ nhưng việc tự mình cài đặt và hiểu được môhình là rất quan trọng trong việc vận dụng thiết kế thuật toán theo mô hình tìmkiếm nhị phân
Dưới đây là một cách cài đặt phổ biến trên C++:
int solve(int a[], int x, int L, int R)
//tìm giá trị x trên dãy a[] từ chỉ số L tới chỉ số R
ans = mid;
break;
} if(a[mid]<x) L = mid+1;
else R = mid-1;
}
return ans;
}
Trang 42.3.2 Độ phức tạp thuật toán.
Người ta chứng minh được độ phức tạp tính toán của thuật toán tìm kiếmnhị phân trong trường hợp tốt nhất là O(1), trong trường hợp xấu nhất làO(logN) Mô hình tìm kiếm nhị phân chỉ thực hiện được với dãy đã được sắpxếp, nên nếu dãy chưa sắp xếp thì cần phải tính đến thời gian cho việc sắp xếplại dãy trước khi áp dụng tìm kiếm nhị phân
2.3.3 Các dạng bài tập tìm nhị phân
Dưới đây là một số dạng phát biểu chung của những bài toán có thể giảiquyết bằng mô hình tìm nhị phân
2.3.3.1 Dạng 1: Tìm kiếm nhị phân trên dãy (mảng) có sẵn.
Ở dạng này ta cần tìm phần tử có giá trị bằng x trên dãy a[] đã sắp xếp.Xét các ví dụ sau:
- Dòng thứ hai ghi N số nguyên A1, A2,… , A N(|A i|≤ 109)
- Q dòng tiếp theo mỗi dòng ghi một số nguyên X
85
2-15
Ý tưởng giải thuật 1: Tìm kiếm tuần tự Độ phức tạp là O(n)
Ý tưởng giải thuật 2: Tìm vị trí phần tử x trên dãy A đã sắp xếp tăng dần Nếu
không tồn tại x trong A, in ra -1
+ Tìm kiếm nhị phân: Vì dãy này đã được sắp xếp theo tiêu chí tăng dần, nên ta
cứ chia đôi ra tìm x Đầu tiên ta tìm vị trí ở giữa, so sánh x với giá trị ở vị trígiữa, nếu x bằng thì coi như đã tìm thấy Nếu x lớn hơn giá trị giữa thì ta phải
Trang 5tìm phía bên phải của vị trí giữa, ngược lại thì tìm phía bên trái của vị trí giữa.
Độ phức tạp là O(log N)
Cài đặt hàm tìm kiếm trên C++
int bsearch(int a[], int l, int r, int x)
https://leetcode.com/problems/find-first-and-last-position-of-element-Cho dãy A được sắp xếp tăng dần A1, A2,… , A N Có Q truy vấn, mỗi truy vấn
là một số nguyên k: Với mỗi k, hãy in ra số đầu tiên bé nhất có giá trị lớn hơnhoặc bằng k gọi là P
- Dòng thứ hai ghi N số nguyên A1, A2,… , A N(|Ai|≤ 109)
- Q dòng tiếp theo mỗi dòng ghi một số nguyên x
Trang 6Ý tưởng giải thuật 1: Tìm kiếm tuần tự Độ phức tạp là O(n)
Ý tưởng giải thuật 2 cải tiến : Tìm x đầu tiên nhỏ nhất có giá trị >=x
- ans = -1
- Khi L<=R
o giữa = (L+R)/2;
o Nếu a[giữa]<x thì tìm x trong [giữa+1, R]
o ngược lại tìm x trong [L, giữa-1], ans = giữa
- return ans
Cài đặt hàm tìm kiếm trên C++
int lower(int a[], int l, int r, int x)
https://leetcode.com/problems/find-first-and-last-position-of-element-Cho dãy A được sắp xếp tăng dần A1, A2,… , A N Có Q truy vấn, mỗi truy vấn
là một số nguyên k: Với mỗi k, hãy in ra số đầu tiên nhỏ nhất có giá trị > k gọi làP
Trang 7- Dòng thứ hai ghi N số nguyên A1, A2,… , A N(|A i|≤ 109
4
48
Ý tưởng giải thuật tìm nhị phân:
chốt cũng là một nghiệm tiềm năng ans = mid
Thu hẹp miền tìm kiếm là nửa đầu dãy tức là [L, mid-1]
o Ngược lại:
Tìm kiếm trên miền [mid+1, R]
Cài đặt hàm tìm kiếm trên C++
int upper(int a[], int l, int r, int x)
Trang 8Sử dụng một số hàm tìm kiếm có sẵn trong thư viện STL của C++
- Hàm binary_search: là hàm tìm kiếm nhị phân có độ phức tạp là O(logn)
Cấu trúc của hàm như sau:
binary_search(a+1, a+n+1, x) có nghĩa là tìm x trong dãy a (đã được sắp xếp
tăng dần) từ vị trí 1 đến vị trí n Nếu có kết quả trả về true, ngược lại trả về false
binary_search(a, a+n, x): có nghĩa là tìm x trong dãy a (đã được sắp xếp tăng
dần) từ vị trí 0 đến vị trí n-1 Nếu có kết quả trả về true, ngược lại trả về false
- Hàm lower_bound(a+1, a+n+1, x) là cho vị trí đầu tiên của ô nhớ có giá trị
lớn hơn bằng x của mảng a(a[1],a[2]…., a[n])
- Hàm upper_bound(a+1, a+n+1, x) là cho địa chỉ đầu tiên của ô nhớ có giá trị
lớn hơn x của mảng a(a[1], a[2],…a[n])
2.3.3.1.2 Một số bài tập vận dụng trong các đề thi học sinh giỏi
Bài tập 1: Count x ( Nguồn Đề thi HSG tỉnh Lâm đồng 2015)
Cho dãy A gồm N phần tử A1, A2,… , A Nđã được sắp xếp tăng dần và Qtruy vấn, mỗi truy vấn gồm một số nguyên x yêu cầu đếm số lượng phần tử cógiá trị là x?
- Q dòng cuối, mỗi dòng ghi một số nguyên x
Output: Với mỗi truy vấn, hãy in kết quả trên một dòng.
Ví dụ:
10 3
1 2 2 3 4 4 4 5 6 62
49
230
Ý tưởng giải thuật: Với mỗi truy vấn:
- Tìm id1 là chỉ số vị trí x đầu tiên, id2 là chỉ số vị trí ngay sau x cuối cùng
số lượng là id2-id1
- id1 = lower_bound; id2=upper_bound
Cài đặt hàm tìm kiếm trên C+
int countx(int a[], int l, int r, int x)
{
//Đếm trong dãy a từ a[l] đến a[r] số lượng phần tử x
int id1 = lower(a, l, r, x);
int id2 = upper(a, l, r, x);
Trang 9return id2-id1;
}
Bài tập 2: COUNTONE.CPP (Nguồn: Đề thi HSG tỉnh Yên bái 2014)
Cho dãy A chỉ gồm các số nguyên 0, 1; Hãy đếm số lượng phần tử có giátrị 1 trong dãy đã được sắp xếp giảm dần
Input:
- Dòng đầu ghi số nguyên n là số lượng phần tử trong dãy (1 ≤ n ≤ 106)
- Dòng thứ hai ghi n số nguyên, mỗi số có giá trị là 0 hoặc 1
- Dữ liệu đảm bảo dãy đã được sắp xếp giảm dần
Output: Một số nguyên là số lượng phần tử 1 trong dãy
Ý tưởng giải thuật 2 : tìm nhị phân trực tiếp trên dãy
- Nhận xét: dãy toàn số 0, rồi toàn số 1 Tìm vị trí i thoả mãn a[i]<a[i-1]
o Nếu a[mid]=1 && a[mid+1]=0 thì ans=mid
o Ngược lại nếu a[mid]=1 && a[mid+1]!=0 thì tìm ở [mid+1,R]
o Ngược lại nếu a[mid]=0 và a[mid+1]=0 thì tìm ở nửa đầu [L,mid-1]
Độ phức tạp của thuật toán là O(logn)
Cài đặt hàm tìm kiếm trên C++
int countone(int a[], int l, int r)
{
int ans = 0;
while(l<=r)
{
Trang 10Bạn hãy tính giúp cho admin xem có thể có bao nhiêu cách sắp xếp từngcặp đôi với nhau thỏa mãn?
Input:
- Dòng đầu tiên là N - số lượng người tham gia bữa tiệc và số K (N≤10^5,K≤10^9)
- Các dòng tiếp theo là chiều cao của N người tham gia bữa tiệc – không có
2 người nào có chiều cao giống nhau (Hi ≤ 10^9)
Trang 11- Ta sắp xếp mảng chiều cao h thành dãy không giảm (tăng dần), vì nếu(a,b) là một cặp thì cặp (b,a) không được tính nữa
- Ta duyệt i từ 1 đến n-1, với mỗi i ta tìm: vị trí trái (left) là vị trí đầu tiêntrong đoạn từ [i+1, n] mà h[left]==h[i] +k Vị trí phải (right) là vị trí cuối cùngtrong đoạn từ [i+1, n] mà h[right]== h[i] +k Như vậy số cặp ghép với h[i] sẽ làright – left +1
- Độ phức tạp của thuật toán là O(nlogn)
Ta có thể dùng hàm lower_bound và upper_bound có sẵn trong thư viện C++
// tìm vị trí đầu tiên >= h[i] +k trong đoạn [i+1,n]
left= lower_bound(h+i+1, h+n+1, h[i]+k)- h
if (h[left!=h[i]+k) continue; // Nếu không có giá trị h[i]+k thì quay lên vòng lặp thực hiện tiếp
//Tìm vị trí cuối cùng bằng h[i]+k
right = upper_bound (h+i+1, h+n+1, h[i]+k) – h -1
res=res + (right – left +1)
}
Bài tập 4 Hẹn gặp (Nguồn: Đề thi HSG cấp Tỉnh Thanh Hóa năm 2016-2017)
Thành phố Gloaming (Hoàng hôn) nổi tiếng với đường dẫn vào công
viên thành phố Các bức tượng tuyệt đẹp theo chủ đề thần thoại Hy lạp – La mãđặt dọc theo con đường thẳng có một sức hút không cưỡng được với mọi khách
du lịch Còn khi những tia nắng cuối cùng trong ngày miễn cưỡng rời khỏi bầutrời thì sương mù dày đặc, như một tấm voan trắng mềm mại từ từ rũ xuống
Bây giờ đứng cách quá r mét là đã không nhìn thấy mặt nhau và các bức tượng
trở thành nơi lý tưởng cho các đôi nam nữ thanh niên hẹn hò
James Bond cần gặp gấp 2 điệp viên nội tuyến của mình để nhận các mậtbáo khẩn Không muốn 2 người này nhìn thấy nhau, Bond hẹn gặp mỗi người ở
một bức tượng sao cho khoảng cách giữa chúng lớn hơn r Trên đường có n bức tượng, bức tượng thứ i ở vị trí cách đầu con đường d i mét i = 1 ÷ n, 1 ≤ d 1 < d 2 <
.< d n ≤ 109
Yêu cầu: Hãy xác định James Bond có bao nhiêu cách chọn địa điểm.
Dữ liệu vào: Vào từ file văn bản HENGAP.INP:
- Dòng đầu tiên chứa 2 số nguyên n và r (1 ≤ n ≤ 3×105, 1 ≤ r ≤ 109)
Trang 12- Dòng thứ 2 chứa n số nguyên d 1 , d 2 , , d n.
Kết quả: Đưa ra file văn bản HENGAP.OUT một số nguyên là số cách
chọn địa điểm tìm được
- Có ½ số test tương ứng với ½ số điểm có n ≤ 104
- Có ½ số test tương ứng với ½ số điểm có 104 < n ≤ 3×105
Ý tưởng giải thuật đề xuất:
- Dùng một vòng lặp For – do để duyệt i từ 1 đến n-1, trong đó với mỗi vịtrí i thì ta tìm kiếm nhị phân để tìm địa điểm j xa nhất thỏa mãn r+di < dj
Cài đặt giải thuật trên C++ (Trình bày chi tiết ở phần phụ lục)
2.2.3.2 Dạng 2: Tìm kiếm trên miền kết quả (Binary search the answer)
Dạng tìm một phần tử trên dãy được sắp xếp là dạng cơ bản quen thuộc Ta
có thể mở rộng việc tìm kiếm nhị phân trên miền kết quả Bắt đầu bằng miền tìmkiếm: là miền giá trị mà chắc chắn kết quả cần tìm sẽ thuộc vào đó Với mỗi lầntìm kiếm, chia đôi miền đó
Giả sử ta có hàm check(x) trả về true nếu kết quả của x là có thể, ngược lạitrả về false Với loại bài toán dạng này, ta thường tìm giá trị lớn nhất, nhỏ nhấtcủa x sao cho check(x)=true
- Nếu muốn tìm giá trị lớn nhất của x:
o Nếu check(x) là true thì check(y)=true với mọi y ≤ x
o Nếu check(x)=false, thì check(y)=false với mọi y ≥ x
- Nói cách khác, ta muốn giảm không gian tìm kiếm sử dụng hàm check ởtrên theo dạng sau: True – false
- Khi đó, cần tìm vị trí mà thay đổi từ True thành False bằng Tìm kiếm nhịphân
2.3.3.2.2 Một số bài tập vận dụng trong các đề thi học sinh giỏi
Bài tập 1: Nguồn: http://www.spoj.com/PTIT/problems/PTIT126J/
Có N cây gỗ, có chiều cao lần lượt là A[1], A[2], , A[n] Bạn cần lấy mộtlượng gỗ độ cao tối thiểu là M bằng cách chặt từ N cây theo cách như sau: chặttất cả những phần thừa của các cây có độ cao lớn hơn H Hãy tìm giá trị H lớnnhất để bạn có thể lấy được lượng gỗ tối thiểu là M
Dữ liệu: Vào từ file văn bản PTIT126J.INP
+ Dòng 1 chứa 2 số nguyên N (1<=N<=106) và M (1 <= M <= 2.109).+ Dòng 2 chứa N số nguyên A[1], A[2], …, A[n], là chiều cao mỗi cây gỗtương ứng (A[i] <= 109, i=1 N) Giả sử luôn tồn tại cách chặt