Để hệ thống lại các chuyên đề bồi dưỡng học sinh giỏi HSG Tin học mà tôi đãdạy trong nhiều năm qua, đồng thời qua quá trình nghiên cứu, giảng dạy, tham khảo ýkiến đồng nghiệp, tôi thấy r
Trang 1SỞ GIÁO DỤC VÀ ĐÀO TẠO HÀ TĨNH
Đề tài:
Sử dụng thuật toán “lùa bò vào chuồng”
để giải các bài toán đếm
Hà Tĩnh, tháng 09 năm 2019
z
Trang 2em vững bước đi tới tương lai - tương lai của thời đại công nghệ thông tin bùng nổ!Trong chương trình Tin học THPT lớp 10 học sinh được giới thiệu các kiến thứcđại cương về tin học, lớp 11 học sinh được giới thiệu về lập trình, lớp 12 học sinhđược học về cơ sở dữ liệu Đặc biệt, chương trình Tin học lớp 11 là phần được cho làkhó cho Thầy Cô giáo cũng như học sinh, vì phải làm thế nào để học sinh có thể hiểuđược ngôn ngữ lập trình, để từ đó có thể lựa chọn và thiết kế thuật toán Chương trìnhtin học lớp 11 nhằm rèn luyện tư duy về thuật toán cho học sinh, rèn luyện kĩ năng lậptrình, tính kiên trì, tỉ mỉ cẩn thận Đối với học sinh thì phải làm quen với lối suy nghĩlogic với sự hoạt động của máy tính, mà đây lại là một lối suy nghĩ hoàn toàn khác vớicác môn học khác.
Từ thực tiễn giảng dạy học sinh đại trà cũng như học sinh đội tuyển học sinh giỏiTin học của trường THPT tôi thấy rằng, học sinh gặp khó khăn khi chuyển lời giải cácbài toán từ toán sang ngôn ngữ lập trình Đặc biệt là việc phân tích bài toán, nhận biếtbài toán đó có thể giải quyết bằng phương pháp nào, cỏ lời giải tối ưu hay không?
Để hệ thống lại các chuyên đề bồi dưỡng học sinh giỏi (HSG) Tin học mà tôi đãdạy trong nhiều năm qua, đồng thời qua quá trình nghiên cứu, giảng dạy, tham khảo ýkiến đồng nghiệp, tôi thấy rằng một số bài toán tin học có liên quan đến việc đếm
Chính vì vậy tôi chọn viết đề tài về chuyên đề “Sử dụng thuật toán “lùa bò vào chuồng” để giải các bài toán đếm”
Trang 3I.2 Mục tiêu nghiên cứu
"Đếm" là công việc quan trọng và đơn giản mà chúng ta làm thường ngày, điều
đó thì không cần phải bàn đến làm gì? Điều đáng nói ở đây là công việc có vẻ nhàmchán đó lại chứa bao điều thú vị, nhất là khi gặp những bài toán yêu cầu ta phải "đếm"
"Đếm" ở đây không phải đơn thuần là đếm 1, 2, 3, mà còn cần ở người đếmmột sự khéo léo và có thể có một chút kỹ thuật Bên cạnh đó để giúp các em học sinh
có kiến thức khoa học cơ bản, hiện đại, tiến tiến, có tính tự lập và khả năng sáng tạo,nhận thức ở mức độ cao, tư duy tốt về lập trình Các phương pháp đếm đơn giản là mộttrong những vấn đề mà bất cứ người lập trình tin học đều cần phải nắm vững
I.3 Nhiệm vụ nghiên cứu
Trước hết là thực hiện đổi mới phương pháp giảng dạy Tin học làm cho học sinhtìm ra những kết quả sáng tạo, lời giải hay trên một số “dạng bài toán tin có liên quanđến việc đếm”; giúp bản thân nắm vững hơn nữa về tư duy thuật toán, khả năng lậptrình, đồng thời để trao đổi và học tập kinh nghiệm với các quý thầy cô giáo trongnhóm Tin học của nhà trường nói riêng và các quý thầy cô giảng dạy môn Tin họctrong ngành nói chung
I.4 Đối tượng nghiên cứu
Trong nghiên cứu này, các học sinh được chọn là các em học sinh học môn Tinhọc khối 10, khối 11, các học sinh là thành viên của đội tuyển HSG môn Tin học, vàmột số giáo viên đứng lớp dạy Tin học ở các trường THPT trên địa bàn
I.5 Các phương pháp nghiên cứu
* Phương pháp suy luận, tổng hợp: kết hợp từ nhiều nguồn tài liệu tham khảo củacác tác giả và tra cứu trên mạng internet với các đề thi HSG rút ra những kinh nghiệm,
hệ thống lại kiến thức, mở ra các hướng mới
* Phương pháp trò chuyện – phỏng vấn: trao đổi tâm tình với nhiều HSG để nắmtình hình trong việc giải các bài toán tin về dãy số (mảng)
* Phương pháp khảo sát: bản thân được tham gia giảng dạy các lớp, đội tuyểnHSG, các kỳ tập huấn; tham khảo các thầy cô, đồng nghiệp giảng dạy đội tuyển nhiềunăm nên có tìm hiểu thêm về các phương pháp làm bài của các em học sinh
* Phương pháp phân tích lý luận: phân tích giúp học sinh nắm thật rõ bản chấtvấn đề, lựa chọn được phương pháp giải cho phù hợp
Trang 4II NỘI DUNG ĐỀ TÀI
II 1 Lịch sử của vấn đề nghiên cứu
Trong những năm liên tiếp dạy bồi dưỡng HSG môn Tin Học lớp 10, 11, 12 thiHSG cấp Tỉnh, cũng như tham khảo ý kiến các đồng nghiệp chuyên dạy bồi dưỡng độituyển ở trong trường, trong ngành và các Tỉnh bạn, ở các trường THPT Chuyên, tôi rút
ra một điều là “công việc đếm rất quan trọng trong dạy lập trình”, vì vậy tôi mạnh
dạn chọn viết đề tài: Sử dụng thuật toán “lùa bò vào chuồng” để giải các bài toán đếm.
II 2 Cơ sở lý luận của đề tài
Kết hợp các bài giảng chuyên đề bồi dưỡng HSG của cá nhân tôi và các tài liệutham khảo để phân tích, tổng hợp, hệ thống
II 3 Thực trạng của vấn đề nghiên cứu
Đa số HSG tin rất ngại, sợ khi giải các bài toán tin có liên quan đến việc đếm; rấtlúng túng trong quá trình phân tích, tổ chức dữ liệu, tìm thuật toán hiệu quả để hiểuđược bản chất và vận dụng kiến thức một cách thích hợp
II 4 Nội dung nghiên cứu và kết quả nghiên cứu
A NỘI DUNG NGHIÊN CỨU
Tư tưởng của thuật toán được xây dựng dựa trên suy nghĩ thực tế là để đếm sốlượng bò trên một vùng xác định thì người ta phải tìm cách lùa chúng vào các chuồng(để chúng khỏi chạy rông) cho dễ đếm Đương nhiên là những con bò cùng loại phảiđược lùa vào cùng một chuồng để dễ phân biệt hoặc lùa mỗi con vào một chuồng thìcàng tốt Tương tự như vậy, muốn giải bài toán đếm các đối tượng thì ta dùng một cấutrúc dữ liệu hợp lý (thường dùng kiểu mảng hoặc kiểu con trỏ) với ý nghĩa giống nhưcác “chuồng” để lưu các đối tượng, mỗi phần tử của mảng tương ứng với một chuồng.Trên cơ sở đó ta dễ dàng thực hiện được mục đích của mình là thực hiện thao tác đếm
Để hiểu rõ hơn thuật toán ta xét ví dụ sau: “Viết chương trình nhập từ bàn phímmột xâu ký tự S và thông báo ra màn hình số lần xuất hiện của mỗi chữ cái tiếng Anh
trong S (không phân biệt chữ hoa hay chữ thường)” – Bài 2 Bài tập và thực hành 5 sách Tin học 11 - trang 73 Rõ ràng với bài toán này ta có thể duyệt toàn bộ xâu S và
-mỗi lần duyệt ta thực hiện thao tác đếm một chữ cái Sau 26 lần duyệt tương ứng với
26 chữ cái thì ta thu được toàn bộ kết quả Tuy nhiên, vì việc duyệt xâu như vậy tương
Trang 5đối chậm và có thể xâu văn bản đã cho rất dài nên về thời gian là không chấp nhậnđược Ta vận dụng thuật toán “lùa bò vào chuồng” bằng cách sử dụng một mảng tĩnhA:array[′A′ ′Z′] of Longint; với ý nghĩa như sau: giá trị A[c] trong đó c thuộc [′A′ ′Z′]lưu số lần xuất hiện của ký tự c trong file văn bản Mảng A được khởi tạo với tất cảcác giá trị bằng 0, khi duyệt xâu ta đọc lần lượt các ký tự trong xâu ra một biến trung
gian ch nào đó, và tăng giá trị của mảng tại vị trí tương ứng có chỉ số ch lên một đơn
vị qua câu lệnh: A[ch]:=A[ch] + 1 Như vậy, bài toán được giải quyết chỉ với một lầnduyệt xâu duy nhất
Sau đây chúng ta cùng khảo sát một số ví dụ điển hình vận dụng thuật toán này
1 Bài 1: Đếm bò
Tên chương trình: DEMBO.*
Bài toán đặt ra là giả sử trên cánh đồng rộng thả rất nhiều bò (N con), mỗi con bòđeo một thẻ có số hiệu nguyên dương (là số tháng tuổi của nó)
Tất nhiên, hai con bò cùng tháng tuổi thì đeo thẻ có số hiệu như nhau Làm thếnào để đếm được loại bò nào có nhiều nhất?
Bài toán này có thể phát biểu lại như sau:
+ Nhập từ bàn phím số nguyên dương N (0 < N ≤ 200) và các phần tử của mảngmột chiều A(N) có giá trị nguyên dương (0 < A[i] ≤ 100)
+ Giá trị nào xuất hiện nhiều nhất trong A và xuất hiện bao nhiêu lần?
+ Ví dụ: A(12) = {2, 3, 2, 4, 5, 6, 2, 6, 7, 1, 6, 2} thì A(12) có số phần tử giá trịbằng 2 là nhiều nhất Số lượng phần tử này là 4
Thuật toán “Lùa bò vào chuồng”
Để giải bài toán này người ta dùng thuật toán “lùa bò vào chuồng” gồm 3 bước:
- Bước 1: Đóng dãy chuồng bò và đánh số các chuồng bằng các số tự nhiên liêntiếp từ 1 đến max (max là số tháng tuổi của con bò già nhất), ban đầu mọi chuồng đềuchưa có bò ở trong
- Bước 2: Lùa bò vào chuồng có số hiệu bằng số thẻ của nó
- Bước 3: Duyệt dãy chuồng bò tìm chuồng có nhiều bò nhất
Áp dụng thuật toán vào bài tập:
Trang 6+ Bước 1: Giả sử con bò thứ i có tháng tuổi là a[i] (1 ≤ i ≤ n) Coi mảng b(n) nhưdãy chuồng bò, b[x] là số lượng bò (có x tháng tuổi) trong chuồng có số hiệu x, banđầu các phần tử của mảng b(n) đều bằng 0.
+ Bước 2: Con bò có tháng tuổi a[i] phải vào chuồng bò có số hiệu a[i] Thêm 1con bò vào chuồng a[i] tương ứng với lệnh inc(b[a[i]])
+ Bước 3: Duyệt mảng b, tìm chỉ số phần tử lớn nhất
Chương trình tham khảo:
Fillchar(B, sizeof(B), 0); maxsl := 0;
{Tao day chuong bo rong chua co bo}
cin>>n;
for(i=1;i<=n;i++){
cout<<"a["<<i<<"]=";
cin>>a[i];
}memset(b,0,sizeof(b));
for(i=1;i<=n;i++) b[a[i]]++;maxsl=0;
for (i=1;i<= 200;i++)
if (b[i]>maxsl) {
maxsl=b[i];
j=i;
}cout << " so "<<j<<" co soluong lon nhat la "<<maxsl;
Trang 7Write('So ', li, ' co so luong lon nhat la ',
Ý tưởng: Để làm bài này ta dùng thuật toán “lùa bò vào chuồng”, các phần tử
bằng nhau sẽ nhốt chung một chuồng, có bao nhiêu chuồng thì có bấy nhiêu phần tửkhác nhau trong dãy ban đầu
Ta đóng dãy chuồng C, đánh số chuồng lần lượt từ 1, 2….32000 ( ai là các sốnguyên dương, ai<32000), ban đầu các chuồng đều trống) Duyệt mảng A ban đầu,phần tử a[i] sẽ được “nhốt” vào chuồng có số a[i] (tăng C[a[i]] lên một đơn vị) Đếm
số chuồng khác trống đó chính là số phần tử khác nhau của dãy ban đầu
Chương trình tham khảo:
var A: array[1 10000] of integer;
const int maxint = 32768;
int i, D, n, a[10000], c[maxint];
int main(){ cout << " Nhap n = "; cin>>n; for(i=1; i<=n; i++)
for (i=1;i<= maxint;i++)
if (c[i]>0) D++;
cout << " so phan tu khac nhau la
Trang 83 Bài 3 (Ví dụ 5 Tài liệu chuyên Tin học - quyển 1 trang 53)
Cho dãy gồm N (N <=30000) số tự nhiên không vượt quá 109, tìm số tự nhiênnhỏ nhất không xuất hiện trong dãy
Dữ liệu vào trong file SN.INP có dạng:
Ý tưởng: Ta có nhận xét sau: số tự nhiên nhỏ nhất không xuất hiện trong dãy sẽ
nằm trong đoạn [0, n] Do đó, ta sử dụng mảng c: array[0 30000] of longint; với c[x]
là số lần xuất hiện của x trong dãy, nếu c[x]=0 tức là x không xuất hiện trong dãy.Chương trình tham khảo:
memset(c,0,sizeof(c));freopen("SN.inp", "r", stdin);
Trang 9x=i;
break;
}freopen("SN.out", "w", stdout);
cout<<x;
return 0;
}
4 Bài 4 Tần số (Đề thi Olimpic sinh viên 2001, khối đồng đội)
Cho một văn bản gồm không quá N (N ≤500) dòng, mỗi dòng chứa không quá 80
ký tự Ta gọi tần số của một ký tự trong văn bản là số lần xuất hiện của ký tự đó trongvăn bản
Yêu cầu: Tìm tần số lớn nhất trong các tần số của các chữ cái (không phân biệt
chữ hoa hay chữ thường) trong văn bản đã cho
Dữ liệu vào: từ file văn bản có tên là FREQ.INP:
Dòng đầu tiên chứa N là số lượng dòng văn bản
N dòng tiếp theo mỗi dòng chứa một dòng của văn bản đã cho
Kết quả: ghi ra file văn bản có tên FREQ.OUT tần số lớn nhất tìm được.
Trang 115 Bài 5 Phủ nhỏ nhất (Đề thi chọn HSG Quốc gia lớp 12 năm 1998-1999)
Cho tập hợp n đoạn thẳng (đánh số từ 1 đến n) với các đầu mút có toạ độ nguyên[Li,Ri], i=1,2, 3,…,n và một đoạn thẳng [P,Q] (P, Q là các số nguyên)
Yêu cầu: Cần tìm một số ít nhất đoạn thẳng trong tập đã cho để phủ kín đoạn
thẳng [P,Q] (nghĩa là mỗi điểm x thuộc [P,Q] phải thuộc vào ít nhất một trong số cácđoạn thẳng được chọn)
Dữ liệu vào: từ file văn bản PHU.INP:
- Dòng đầu tiên ghi 3 số n, P, Q phân cách nhau bởi dấu trắng;
- Dòng thứ i trong số n dòng tiếp theo chứa hai số L, R phân cách nhau bởi dấu trắng(i=1, 2,…, n); 1 ≤ n ≤ 100 000; P − Q ≤ 5000; |Li| ≤ 50000; |Ri|≤50000,
i = 1, 2,…, n
Kết quả: ghi ra file văn bản PHU.OUT:
- Dòng đầu tiên ghi số k là số lượng đoạn cần chọn (quy ước ghi số 0 nếu không tìmđược lời giải);
- Nếu k > 0 thì mỗi dòng trong số k dòng tiếp theo ghi số của một đoạn thẳng đượcchọn
Ví dụ:
2 0 1-1 0
0 1
12
Thuật toán: Ta phân tích bài toán để có thể áp dụng sáng tạo tư tưởng của thuật
toán đã trình bày như sau: Ta thấy rằng độ dài đoạn thẳng [P,Q] không lớn hơn 5000.Còn số đoạn thẳng trong tập đã cho có thể lên tới 100000 Ta không thể lưu tất cả cácđoạn thẳng này lại được Vậy chúng ta có thể lưu các đoạn thẳng này theo cách: lưutừng đoạn một, dùng một mảng A có độ dài chính bằng độ dài đoạn thẳng [P,Q], kích
Trang 12thước của mảng này là 5000x2 (A: Array [ 0 5000, 1 2 ] Of LongInt;)
Ta coi đoạn [P,Q] là mảng A trên Ta lần lượt đọc các đoạn thẳng của tập n các đoạnthẳng Với mỗi đoạn thẳng vừa đọc được, ta xét xem chúng phủ đoạn [P,Q] bắt đầu từđâu và độ dài mà đoạn thẳng đó phủ được Ta sẽ lưu lại trên mảng A ở phần tử màđoạn thẳng chúng ta vừa đọc bắt đầu phủ đoạn [P,Q] chỉ số của đoạn thẳng đó và độdài chúng phủ được
A[Phần tử bắt đầu phủ,1] = chỉ số của đoạn vừa đọc và A[Phần tử bắt đầu phủ, 2] = độ dài mà đoạn đó phủ được.
Nếu có nhiều đoạn thẳng bắt đầu phủ đoạn [P,Q] tại cùng một vị trí thì chọn đoạnnào có độ dài phủ được là lớn nhất
Sau khi làm xong công việc trên ta được một mảng gồm các đoạn thẳng phủ lênđoạn [P, Q] Bây giờ ta sẽ tiến hành tìm xem số đoạn phủ ít nhất sẽ là bao nhiêu bằngcách: ta bắt đầu đi từ vị trí 0 (nếu tại vị trí này mà không có đoạn nào phủ thì sẽ không
có lời giải) ta tìm xem có đoạn nào giao với đoạn này mà tạo thành một đoạn thẳng cóphủ dài nhất thì ta lưu đoạn này lại (nếu không có đoạn thẳng nào giao với đoạn thẳngnày để tạo được một đoạn thẳng có độ che phủ lớn hơn độ che phủ của đoạn trên thì sẽkhông có lời giải) Làm tương tự với đoạn thẳng vừa tìm được cho đến khi các đoạnthẳng được chọn sẽ phủ kín đoạn [P, Q] ta sẽ được số đoạn thẳng ít nhất để phủ đoạn[P, Q] Như vậy, thuật toán này cho phép giải quyết bài toán cũng chỉ với một lầnduyệt mảng duy nhất
Dưới đây là chương trình minh hoạ:
Const Fi = ′PHU.INP′;
Fo = ′PHU.OUT′;
Max = 5000; Dd = 2;
Var n, P, Q, Min, Id : LongInt;
A : Array [ 0 Max,1 2 ] Of LongInt;
Trang 13If R > Q then A[0, Dd] := Q - P else A[0, Dd] := R - P; End
else
else If L <= Q then
If A[L - P, Dd] < R - L then
Begin A[L - P, id] := i;
If R > Q then A[L-P, Dd]:= Q-L else A[L-P, Dd]:= R - L; End;
Trang 14For i:=j + 1 To j + A[j, Dd] Do
If (A[i, id]<>0) And (tt(i, j)>Max) then Begin
Max:=tt(i, j); tmp:=i;
End;
If Max=j+A[j, Dd] then Exit else
Begin Count:=Count + 1;
Trang 15Tham khảo thêm: Trong Sáng tạo trong thuật toán và lập trình - tập 2 (Ebook)
tác giả Đỗ Xuân Huy có giới thiệu thêm thuật toán giải bài toán này như sau: Bài 1.7 trang 21 - Phương pháp: Tham (độ phức tạp N2)
* Sắp các đoạn tăng theo đầu phải R
* k : = 1; { chỉ số đầu tiên }; v := P; { Đầu trái của đoạn [P,Q] }
* Lặp đến khi v >= Q
- Duyệt ngược từ N đến k
- Tìm đoạn j [Lj, Rj] đầu tiên có đầu trái Lj <=v
+ Nếu không tìm được: vô nghiệm;
+ Nếu tìm được:
Ghi nhận đoạn j;
Đặt lại v := Rj;Đặt lại k := j+1;
Trang 16* Có thể dùng thuật toán “Lùa bò vào chuồng” để giải quyết bài toán sau:
1 Bài 1: Dự tiệc
Ông An đến dự một buổi tiệc Buổi tiệc đã có N người (0 < N < 500.000) Mỗingười dự tiệc đều được cài lên áo một bông hoa trên đó có ghi một con số nguyêndương X bất kì ( X<106) cho biết người khách đó sẽ dự tiệc tại phòng có chỉ số X Hầuhết các phòng đều có số lượng khách là số chẵn, duy nhất chỉ có một phòng có sốlượng khách là số lẻ
Để đảm bảo đủ cặp cho việc khiêu vũ nên ban tổ chức cần tìm ra số hiệu củaphòng khách có số lượng khách là số lẻ để ghi số cho ông An
Yêu cầu: Cho trước một danh sách khách dự tiệc cùng với các số trên áo của họ,
hãy giúp ban tổ chức tìm ra số hiệu của phòng khách có số lượng khách là số lẻ
Dữ liệu vào: Cho trong file văn bản DUTIEC.INP, gồm:
+ Dòng đầu tiên ghi một số N cho biết số khách của buổi tiệc khi ông An đến.+ Trong N dòng tiếp theo, mỗi dòng ghi một số nguyên dương cho biết con sốghi trên áo của người khách thứ i
Dữ liệu ra: Ghi ra file văn bản DUTIEC.OUT gồm một số nguyên dương duy
nhất Đó là số hiệu của phòng có số lượng khách là số lẻ
Ví dụ:
5
1 2
2
3
1
3
Hướng dẫn: Trong bài toán này, dãy phòng chỉ cần đóng số hiệu từ 1 đến số
hiệu Xmax (Xmax = 106) và mỗi phòng chỉ chứa 1 trong 2 trạng thái chẳn hoặc lẻ(true, false), tìm thấy thêm một người khách của phòng i thì đổi trạng thái phòng i(toán tử not)