Để thu hút các em tham gia vào đội tuyển giáo viên phải luôn tìm những giải pháp giúp nâng cao chất lượng, hệ thống theo từng kỹ thuật các bài tập từ dễ đến khó, giúp các em nắm bắt được
Trang 11 MỞ ĐẦU 1.1 Lí do chọn đề tài
Môn tin học là môn học không thi THPT Quốc Gia, học sinh không chú trọng và đầu tư thời gian cho môn học này Số học sinh có hứng thú và yêu thích môn học cũng rất ít Để thu hút các em tham gia vào đội tuyển giáo viên phải luôn tìm những giải pháp giúp nâng cao chất lượng, hệ thống theo từng kỹ thuật các bài tập từ dễ đến khó, giúp các em nắm bắt được phương pháp áp dụng vào giải quyết các bài toán một cách tốt nhất
Những giải pháp và kỹ thuật đã có rất nhiều Tuy nhiên, trong quá trình đi sâu tìm hiểu các tài liệu, tham khảo đồng nghiệp, tôi phát hiện ra một kĩ thuật mà chưa thấy tài liệu nào viết đến, mà hầu như nó sử dụng khá nhiều trong các đề
thi học sinh giỏi Đó là sử dụng “Mảng đánh dấu” để lập trình Vì vậy tôi chọn vấn đề này để viết Sáng kiến kinh nghiệm “Kĩ thuật sử dụng mảng đánh dấu trong lập trình qua một số ví dụ”
1.2 Mục đích nghiên cứu :
Nhằm nâng cao chất lượng học sinh khá, giỏi có niềm đam mê tin học, giúp các em nắm vững kĩ thuật Từ đó hình thành tư duy logic, sự chắc chắn, chính xác để phục vụ cho giải quyết các bài toán lớn hơn và các công việc phức tạp sau này
1.3 Đối tượng nghiên cứu:
Đề tài này nhằm nghiên cứu về việc sử dụng kĩ thuật mảng đánh dấu để tối
ưu các bài toán đặc trưng, cơ bản trong tin học
1.4 Phương pháp nghiên cứu
+ Lấy các bài toán, thuật toán điển hình được áp dụng nhiều vào các bài toán khác
+ Tìm hiểu thực tế ở các đề thi học sinh giỏi qua từng năm
+ Tổng hợp lại các đề, xem xét dữ liệu bài toán yêu cầu, tìm ra các thuật toán có thể sử dụng được mảng đánh dấu
+ Thống kê, tập hợp lại các bài toán, xây dựng thuật toán, viết code
Trang 21.5 Những điểm mới của SKKN:
Để nâng cao chất lượng học sinh khá giỏi có niềm đam mê trong tin học, giúp các em nắm được vững kỹ năng trong lập trình, hình thành tư duy logic Tôi đã hệ thống các bài tập theo kỹ thuật và từ dễ đến khó, từ đó các em sẽ dễ dàng nắm bắt được kĩ thuật này để áp dụng giải quyết các bài toán phù hợp Nội dung sáng kiến kinh nghiệm tôi xin trình bày trong phần 2 dưới đây:
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
Khi được tiếp cận với cuốn “tài liệu chuyên tin học” của các tác giả Hồ Sĩ Đàm
(chủ biên), Đỗ Đức Đông, Lê Minh Hoàng, Nguyễn Thanh Hùng, Trong đó có
thuật toán sàng nguyên tố, hay còn gọi là thuật toán sàng nguyên tố Eratosthenes để tìm tất cả các số nguyên tố trong đoạn từ 1 tới N
Đó là
Bước 1: sử dụng một mảng để đánh dấu các phần từ từ 2-> N tất cả đều là nguyên tố, trong đó p = 2 là số nguyên tố đầu tiên
Bước 2: Tất cả các bội của p: 2p, 3p, 4p….sẽ bị đánh dấu vì không nguyên tố, Bước 3: Tìm các số còn lại trong danh sách chưa được đánh dấu > p Nếu không còn số nào , dừng lại tìm kiếm Nếu còn gán nó bằng x và lặp lại bước 2
Bước 4 Khi kết thúc giải thuật, các số không bị đánh dấu là số nguyên tố
Theo webside http://www.Wikipedia.org, Sau khi tìm ra giải thuật, nhà toán học Eratosthenes đã lấy lá cọ và ghi lại tất cả các số từ 2-100 Ông đã chọc thủng các hợp số và giữ nguyên các số nguyên tố
Tôi bắt đầu hình thành ý tưởng dùng kĩ thuật mảng đánh dấu để giải quyết các bài toán khác Và tôi đi tìm thêm các tài liệu, các đề thi học sinh giỏi qua các năm thì thấy sử dụng khá nhiều kĩ thuật này Và tôi bắt đầu tìm tòi, nghiên cứu
về kĩ thuật này
2.2 Thực trạng vấn đề trước khi áp dụng sáng kiến kinh nghiệm.
Trước khi áp dụng sáng kiến kinh nghiệm về kĩ thuật “Mảng đánh dấu”,
tôi gặp rất nhiều bài toán cần xử lý số liệu lớn, áp dụng các thuật toán thông
Trang 3thường chỉ giải quyết được một phần test có dữ liệu nhỏ, không full được test Khiến điểm thường thấp, không cao
2.3 Các sáng kiến kinh nghiệm hoặc các giải pháp đã sử dụng để giải quyết vấn đề
Sau đây là một số ví dụ cụ thể Tôi đã sử dụng giúp học sinh nắm vững được kỹ thuật dùng mảng đánh dấu trong trong lập trình
Trong quá trình nghiên cứu, tôi sử dụng ngôn ngữ lập trình Pascal để thể hiện các thuật toán Vì ngôn ngữ lập trình Pascal là ngôn ngữ phổ thông, đang được
sử dụng để giảng dạy trong chương trình hiện hành, để giúp các em học sinh, cũng như đồng nghiệp nắm được rõ hơn, sâu sắc hơn vấn đề tôi cần chuyển tải
Bài 1 Dãy dài nhất ( Kì thi chọn HSG cấp tỉnh lớp 12 THPT năm học
2017-2018 tỉnh Hải Dương)
Cho dãy số nguyên dương và số nguyên dương Hãy tìm dãy con dài nhất (là dãy có nhiều số nhất) gồm các số liên tiếp của A
mà tổng tất cả các số của dãy con này chia hết cho
Dữ liệu: Nhập từ file văn bản DAYDAINHAT.INP
Dòng đầu tiên ghi hai số nguyên dương ghi cách nhau một dấu trống
Dòng thứ hai ghi số nguyên dương mô tả dãy , hai số nguyên liên tiếp ghi cách nhau một dấu trống Giá trị các số nguyên không vượt quá 109
Kết quả: Ghi ra file văn bản DAYDAINHAT.OUT độ dài của dãy con dài nhất
tìm được
Ví dụ:
DAYDAINHAT.INP DAYDAINHAT.OUT
6 3
3 2 4 6 3 7
5
Ghi chú: Kết quả được chấm qua 6 test, mỗi test đúng được 0,25 điểm, trong đó:
2 test có N≤500
Trang 4 2 test có N≤5000
2 test có N≤105
Ý tưởng: Lập một mảng tiền tố S với s[i] là tổng các phần tử từ từ phần từ đầu
tiên1 tới phần tử thứ i, như vậy tổng các đoạn a[i], a[i+1], ,a[j] được tính bởi s[j]-s[i-1] và ta có thuật toán với độ phức tạp O(n2) như chương trình minh họa dưới đây:
var n,k: longint;
a,s:array[0 100000] of longint;
i,j,T,res:longint;
begin
read(n,k);
for i:=1 to n do read(a[i]);
s[0]:=0;
for i:=1 to n do s[i]:=s[i-1]+a[i];
res := 0;
for i:=1 to n do
for j:=1 to n do
begin
t:=s[j]-s[i-1];
if (t mod k =0 ) and (res<j-i+1) then res:= j-i+1;
end;
write(res);
end.
Với thuật toán O(n2 ), ta giải quyết được với N<=5000 Tuy nhiên khi N<=105,
ta cần phải có một cách tiếp cận khác: (Lưu ý rẳng trong thuật toán trên ta chưa
sử dụng điều kiện K<=105 của đề bài) Ngoài ra lưu ý rẳng:
Tmod K = (S[j]-S[i-1]) mod K
Nên nếu T mod K = 0 thì S[i] và S[i-1] phải cùng đồng dư khi chưa cho K Do vậy, bài toán qui về là với mỗi j cần tìm vị trí đầu tiên đã có S[j] mod K Do K<=105 nên ta có thể sử dụng một mảng carry[0 100000] of longint để đánh
Trang 5dấu (nhớ) xem số dư xuất hiện hay chưa? Ta có Chương trình minh họa dưới đây:
var n,kq,k:longint;
a,s,carry:array[0 100001] of longint;
i,j,du:longint;
BEGIN
readln(n,k);
for i:=1 to n do readln(a[i]);
s[1]:=a[1];
for i:=2 to n do s[i]:=s[i-1]+a[i];
for i:=1 to k do carry[i]:=-1;
carry[0]:=0;
kq:=0;
for i:=1 to n do
begin
du:=s[i] mod k ;
if (carry[du]<>-1) and (kq<i-du+1) then kq:=i-du+1;
if carry[du]=-1 then carry[du]:=i;
end;
write(kq);
readln
END.
Bài 2: Nghiên cứu gen (Đề thi HSG Bình Phước 2016-2017)
Trung tâm nghiên cứu gen thu thập N mẫu gen của N cụ Rùa trong cùng một thảo cầm viên N gen này được mã hóa thành N số nguyên dương A1, A2, ,
AN Bộ phận phân tích sau khi tính toán các cụ rùa có gen không đạt yêu cầu để loại ra không cho lai tạo nữa đã đưa ra kết luận sau:
Cụ rùa có gen không đạt yêu cầu là gen có số nguyên đại diện có tổng các ước nhỏ hơn nó mà nhỏ hơn nó.
Trang 6Ví dụ: số 9 có các ước nhỏ hơn nó là 1, 3 ta có 1+3=4<9 Vậy gen được mã hóa thành số 9 là gen không đạt yêu cầu
Yêu cầu
Hãy đếm xem trong mẫu gen của N cụ rùa có bao nhiêu gen không đạt yêu cầu
Dữ liệu vào từ tệp văn bản GEN.INP
o Dòng đầu là số nguyên dương N.
o Dòng thứ hai là mã gen của Ncụ rùaA1, A2, , AN(các số cách nhau 1 dấu cách )
Dữ liệu ra ghi vào tệp văn bản GEN.OUT
o Một số nguyên duy nhất là số gen không đạt yêu cầu
Giới hạn
Có
o
o
Ví dụ
5
3 6 9 12 7
3
Ràng buộc
o 70% test ứng với 70% số điểm của bài toán có:
o 15% test ứng với 15% số điểm của bài toán có :
o 15% test ứng với 15% số điểm của bài toán có :
Phân tích ý tưởng: Đối với bài này, Nhìn qua nhiều người sẽ nghĩ đến phương
án dùng công thức tính tổng ước Nhưng, cách đó không thể full test Ở đây ta
Trang 7phải sử dụng kĩ thuật mảng đánh dấu bằng cách như sau: Thiết lập một mảng F đánh dấu lần lượt tổng ước của các số từ 1 đến 106 (các ước bé hơn chính nó) Ban đầu gán tất cả các phần tử trong mảng F = 1 (1 luôn là ước của tất cả các
số ) Xét các F[i*j] với i chạy từ 2 tới r= [ ] , j chạy từ i tới r div i F[i*j]
= F[i*j] +i , khi i<>j cộng thêm j vào nữa là ta đươc mảng F với F[i] là tổng các ước (bé hơn i) của i
r:=5000000;
for i:=1 to r do f[i]:=1;
for i:=2 to trunc(sqrt(r)) do
for j:=i to r div i do
begin
f[i*j]:=f[i*j]+i;
if i<>j then f[i*j]:=f[i*j]+j;
end;
Cuối cùng chỉ việc ngồi đếm, nếu a[i] > F[a[i]] thì tăng biến đếm lên
Độ phức tạp : nhỏ hơn nlogn
Code tham khảo:
const fi='gen.inp';
fo='gen.out';
var l,r,dem,n:longint;
f,A:array[-1 5000000] of longint;
procedure nhap;
var g:text;
i:longint;
begin
assign(g,fi);
reset(g);
readln(g,n);
for i:=1 to n do
Trang 8read(g,A[i]);
close(g);
end;
procedure xuli;
var i,j,r:longint;
begin
r:=5000000;
for i:=1 to r do f[i]:=1;
for i:=2 to trunc(sqrt(r)) do
for j:=i to r div i do
begin
f[i*j]:=f[i*j]+i;
if i<>j then f[i*j]:=f[i*j]+j;
end;
dem:=0;
for i:=1 to n do
if A[i]>f[A[i]] then inc(dem);
end;
procedure xuat;
var g:text;
begin
assign(g,fo);
rewrite(g);
write(g,dem);
close(g);
end;
begin
nhap;
xuli;
xuat;
Trang 9Bài 3: Tập số (Đề thi HSG tỉnh Thanh Hóa năm học 2016-2017)
Cho số tự nhiên N (1 ≤ N ≤ 106) và một tập A chỉ gồm các số tự nhiên
khác nhau được xác định như sau:
- 1 thuộc A.
- Nếu k thuộc A thì 2k+1 và 3k+1 cũng thuộc A.
Yêu cầu: Giả sử tập A đã được sắp xếp theo thứ tự tăng dần Hãy tìm phần tử thứ N của tập A
Dữ liệu vào: Vào từ file văn bản BAI4.INP gồm 1 dòng duy nhất chứa số N Kết quả: Ghi ra file văn bản BAI4.OUT gồm 1 số duy nhất là phần tử thứ
N của tập A tìm được.
Ví dụ:
Rằng buộc:
- Có 3 số test tương ứng với 3 số điểm có N ≤ 104
- Có 23 số test tương ứng với 23 số điểm có 104 < N ≤ 106
Phân tích ý tưởng:
Dùng mảng đánh dấu chỉ số Với số liệu bài này, ta chỉ cần duyệt đến nmax = 2.106, tức là đánh dấu từ 1 -> 2.106, (có thể nmax đến 107 cho thoải mái nè) Đầu tiên ta đánh dấu bằng False hết, và gán A[1]=true Bắt đầu duyệt từ 1 bằng một vòng for, gặp đánh dấu nào = true thì tăng biến đếm lên 1, đồng thời, ngay lập tức gán a[i*2+1]:= true và a[i*3+1]:= true Vòng for cứ chạy đến khi nào đến n là đủ, rồi break nó ra (Tuy nhiên thực tế thì for không cần đến nmax, mà đến tầm khoảng nmax div 2 là dư sức đủ rồi.)
Đánh giá thời gian chạy: Việc dùng mảng đánh dấu này chỉ cần dùng 1 vòng
for Độ phức tạp O(n) Số lần chạy không quá 2.n Thời gian chạy không quá 0.5s
Code tham khảo:
const fi='bai4.inp';
Trang 10fo='bai4.out';
maxn= round(7e7); // Nghĩa là maxn = 7.107
var i,n: longint;
a: array[1 maxn] of boolean;
dem: longint;
BEGIN
assign(input, fi); reset(input);
assign(output, fo); rewrite(output);
readln(n);
for i:= 1 to maxn do a[i]:= false;
a[1]:= true;
for i:=1 to maxn div 2 do
if a[i]=true then
begin
if 2*i+1<= maxn then a[i*2+1]:= true;
if 3*i+1<= maxn then a[i*3+1]:= true;
end;
dem:= 0;
for i:= 1 to maxn do
if a[i]=true then
begin
inc(dem);
if dem = n then
begin
writeln(i);
break;
end;
end;
close(output);
END
Trang 11Bài 4 : Đếm số lượng ( Đề thi học sinh giỏi Bình Phước 2012-2013)
Xét dãy gồm N số nguyên dương A1, A2, …, AN và số nguyên X cho trước Hãy
đếm số cặp (Ai, Aj) thỏa mãn các điều kiện:
o Ai + Aj = X
o 1 £ i < j £ N
Dữ liệu vào từ tệp văn bản SOLUONG.INP:
o Dòng đầu tiên chứa số nguyên dương N với 1 < N ≤ 106
o Dòng tiếp theo chứa N số nguyên A1, A2, , AN với 0 < Ai < 105, 1 ≤ i ≤
N Hai số kề nhau cách nhau một khoảng trắng
o Dòng cuối cùng chứa số nguyên dương X < 106
Dữ liệu ra ghi vào tệp văn bản SOLUONG.OUT: Số lượng cặp (Ai, Aj) thỏa mãn điều kiện trên
Ví dụ:
9
5 12 7 11 9 1 2 3 11 13
3
Phân tích ý tưởng: Với số liệu 106 ta giải quyết bài này như sau, trước hết, sắp xếp lại dãy, đồng thời cho biết tần suất xuất hiện của từng số trong dãy vừa sắp xếp
Kết quả dùng quy tắc nhân các tần suất của cặp giá trị thỏa mãn
Vì liên quan đến tần suất xuất hiện của các số nên ta dùng thuật toán sắp xếp theo phân phối đơn giản và hiệu quả như sau: Xây dựng mảng C , với C[i] để đánh dấu số lần xuất hiện của i
fillchar(c, sizeof(c), 0); {Khởi tạo mảng C}
for i := 1 to n do inc(c[a[i]]);
procedure sap_xep;
var
Trang 12i: longint;
begin
fillchar(c, sizeof(c), 0);
for i := 1 to n do
inc(c[a[i]]);
fillchar(a, sizeof(a), 0);
n := 0;
for i := 1 to _value_max do
if c[i] <> 0 then
begin
inc(n);
a[n] := i;
end;
end;
Sau khi sắp xếp, các phần từ i
Độ phức tạp của thuật toán sắp xếp là: O(Max(N,K));
Code tham khảo:
const
_n_max = 1000000;
_value_max = 100000;
fi = 'soluong.inp';
fo = 'soluong.out';
var
a: array[1 _n_max] of longint;
c: array[1 _value_max] of longint;
x: longint;
n: longint;
sl: longint;
procedure nhap;
var
Trang 13i: longint;
begin
assign(input, fi); reset(input);
readln(n);
for i := 1 to n do read(a[i]);
readln(x);
close(input);
end;
procedure xuat;
begin
assign(output, fo); rewrite(output);
write(sl);
close(output);
end;
procedure sap_xep;
var
i: longint;
begin
fillchar(c, sizeof(c), 0);
for i := 1 to n do
inc(c[a[i]]);
fillchar(a, sizeof(a), 0);
n := 0;
for i := 1 to _value_max do
if c[i] <> 0 then
begin
inc(n);
a[n] := i;
end;
end;
Trang 14procedure xu_li;
var
i, j: longint;
begin
sap_xep;
sl := 0;
i := 1;
j := n;
while i < j do
begin
if a[i] + a[j] = x then
sl := sl + c[a[i]] * c[a[j]];
if a[i] + a[j] > x then
dec(j)
else
inc(i);
end;
if (2 * a[i] = x) and (c[a[i]] > 1) then
sl := sl + c[a[i]] * (c[a[i]] - 1) div 2;
end;
BEGIN
nhap;
xu_li;
xuat;
END
MẢNG HẰNG –MỘT DẠNG ĐẶC BIỆT CỦA MẢNG ĐÁNH DẤU
Mảng hằng là một dạng đặc biệt, thi thoảng vẫn được sử dụng để tính toán nhanh một số bài toán có số liệu lớn, chạy tốn nhiều thời gian Khi sử dụng mảng hẳng thì bài toán trở nên khá nhẹ nhàng
Nó cũng được xem là một dạng của mảng đánh dấu,
Trang 15Để hiểu có thể làm rõ vấn đề hơn, chúng ta đi xét một ví dụ sau:
Năm 1973, một nhà toán học đưa ra khái niệm Độ bền của một số nguyên không
âm n được định nghĩa như sau: Nếu N có một chữ số thì độ bền của n bằng 0 -Nếu N có từ 2 chữ số trở lên thì độ bền của n bằng độ bền của số nguyên là tích các chữ số của n cộng 1
Yêu cầu: Cho số nguyên N, Tìm số bé hơn n có độ bền lớn nhất
(giới hạn N < 2.10 9 )
Dứ liệu vào: file văn bản Doben.inp Mỗi dòng ghi một số nguyên dương N Kết quả: Ghi ra file văn bản Doben.out Mỗi dòng ghi một số tương ứng tìm được.
Ví dụ: Với n = 100 thì in ra kết quả: So be hon 100 co do ben lon nhat la: 77
• Giải thích:
Doben(77)=Doben(49)+1=Doben(36)+1+1=Doben(18)+1+1+1
= Doben(8)+1+1+1+1=0+1+1+1+1=4
Hướng giải quyết vấn đề:
Ta hoàn toàn có thể viết hàm tìm độ bền của một số n (như hình bên dưới):
Và chương trình in ra số k lớn nhất có độ bền bằng i như sau:
uses crt;
function doben(n:longint):longint;
var t:longint;
begin