Tải bản đầy đủ (.doc) (22 trang)

Một số dạng toán cho học sinh khá, giỏi môn tin học 11 ở trường THPT lê lợi năm học 2020 2021

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (184.74 KB, 22 trang )

Mục lục
Nội dung
1. Mở đầu
1.1 Lý do chọn đề tài
1.2. Mục đích của đề tài sáng kiến kinh nghiệm

Trang
1
1
1

1.3. Đối tượng, phạm vi nghiên cứu của SKKN

1

1.4. Phương pháp nghiên cứu

1

1.5 Những điểm mới của SKKN:

2

1.6. Đóng góp của SKKN
2. Nội dung đề tài sáng kiến kinh nghiệm

2

2.1. Cơ sở lý luận của sáng kiến kinh nghiệm

2



2.2 Thực trạng của vấn đề trước khi áp dụng sáng kiến kinh
nghiệm

2

2. 3 Nội dung

3. KẾT LUẬN, KIẾN NGHỊ

2,3,4,5,6,7,8,9,10,11,1
2,13,14,15,16,17,18,19
,20
20

3.1. Kết luận:

20

3.2. Kiến nghị

20

1


1. Mở đầu
1.1 Lý do chọn đề tài
Bồi dưỡng học sinh giỏi là một nhiệm vụ quan trọng đối với mỗi giáo viên.
Do đó, việc nghiên cứu, tìm tịi, tích lũy kiến thức là công việc thường nhật của

mỗi giáo viên nhằm nâng cao trình độ chun mơn nghiệp vụ, tích lũy kinh
nghiệm cho bản thân.
Trong những năm gần đây mức độ yêu cầu của đề thi học sinh giỏi ngày
càng nâng cao. Những bài tốn này khó đối với học sinh, chương trình thường
khó thực hiện hết các test yêu cầu, đặc biệt là đối với học sinh không chun như
ở trường THPT Lê lợi.
Trong lập trình có rất nhiều phương pháp, cách giải các bài toán được nêu
ra, tuy nhiên có rất ít tài liệu trình bày cụ thể về cách giải quyết các bài tốn có
dữ liệu lớn. Hơn nữa các bài toán trong Tin học thường rất đa dạng, phong phú.
Do đó giáo viên bồi dưỡng học sinh giỏi gặp rất nhiều khó khăn trong việc hướng
dẫn học sinh giải các bài tốn này.
Qua q trình giảng dạy, học tập và tham gia bồi dưỡng học sinh giỏi môn Tin
học ở trường THPT Lê Lợi, tôi đã tổng hợp được một số kinh nghiệm giải quyết
một phần khó khăn nêu trên. Do đó tơi đã chọn đề “Một số dạng toán cho học
sinh khá, giỏi môn Tin học 11 ở trường THPT Lê Lợi năm học 2020-2021"
1.2. Mục đích của đề tài sáng kiến kinh nghiệm
- Đưa ra một số kinh nghiệm giúp giải các bài tốn có dữ liệu lớn mà bằng
cách giải thơng thường có thể khơng giải quyết được về mặt thời gian hoặc không
thực hiện được hết bộ test yêu cầu.
- Nâng cao năng lực giải quyết vấn đề giải các bài toán trong Tin học đối với
học sinh khá giỏi và học sinh tham gia dự kỳ thi học sinh giỏi cấp tỉnh.
1.3. Đối tượng, phạm vi nghiên cứu của SKKN
- Hs khá giỏi lớp 11 trường THPT Lê Lợi
- Các bài tốn Tin học .
- Chun đề, chương trình để bồi dưỡng học sinh giỏi Tin học THPT.
- Một số kinh nghiệm, giải pháp giúp bồi dưỡng năng lực giải quyết vấn đề
cho học sinh khá, giỏi.
1.4. Phương pháp nghiên cứu
- Nghiên cứu tài liệu, trao đổi chuyên môn với đồng nghiệp, bạn bè để giải
quyết vấn đề.

- Thực hiện bồi dưỡng học sinh giỏi qua các năm học 2017 – 2018, năm học
2018 – 2019, 2020- 2021

2


- Dựa vào các kiến thức cơ bản mà học sinh đã được học trong chương
trình Tin học phổ thơng và cấu trúc đề thi học sinh giỏi môn Tin học qua các kì
thi học sinh giỏi Tỉnh Thanh Hóa.
1.5 Những điểm mới của SKKN:
- Đưa ra các giải pháp giúp giải quyết các bài toán bằng các thuật toán tối ưu
và tổ chức hợp lí dữ liệu được phân loại thành các dạng cụ thể.
- Minh họa các giải pháp bằng các ví dụ cụ thể. Liên hệ một số đề thi, bài
tập vận dụng.
- SKKN đã đưa ra được một số giải pháp cơ bản để xử lí các bài tốn dữ liệu
lớn thơng qua việc phân loại thành các dạng thường gặp, giúp người đọc dễ hiểu
và dễ liên hệ với các bài toán khác.
- Hệ thống các bài tốn trong ví dụ có thể khơng phải là những bài toán xa
lạ, nhưng trong SKKN đã đưa ra được hướng giải quyết theo hướng “làm mịn
dần”, và có sự so sánh giữa các cách giải bằng kiểm chứng thời gian thực hiện cụ
thể nhờ phần mềm Themis.
1.6. Đóng góp của SKKN
- Đưa ra được một số kinh nghiệm để giải quyết một số bài toán.
- Là tài liệu tham khảo cho học sinh khá giỏi môn tin 11 để bồi dưỡng học
sinh giỏi Tin học trong nhà trường có hiệu quả.
2. Nội dung đề tài sáng kiến kinh nghiệm
2.1. Cơ sở lý luận của sáng kiến kinh nghiệm
- Với sự phát triển nhanh của ngành công nghệ thơng tin, máy tính ngày
càng có tốc độ xử lý cao đáp ứng được yêu cầu xử lý các bài tốn có dữ liệu lớn
trong thời gian thực hiện ngắn.

- Học sinh và giáo viên có thể dễ dàng tìm hiểu nguồn tài liệu để học tập
tham khảo qua đồng nghiệp, mạng Internet, …
2.2 Thực trạng của vấn đề trước khi áp dụng sáng kiến kinh nghiệm
- Những kiến thức trong chương trình Tin học phổ thơng cịn hạn chế hoặc
chưa đủ đáp ứng cho việc giải một số bài tốn trong các kỳ thi học sinh giỏi Tỉnh
khi có yêu cầu dữ liệu lớn cùng thời gian thực hiện ngắn.
- Các tài liệu tổng hợp các cách để giải quyết các bài toán yêu cầu cao như
vậy chưa nhiều để học sinh tham khảo, ôn luyện.
2. 3 Nội dung
Bài tốn có dữ liệu lớn là những bài tốn có dữ liệu vào có giá trị lớn hơn
các kiểu dữ liệu thường dùng như kiểu Byte, integer, word, string… Tuy so với
Turbo Pascal thì Free pascal đã cho phép bộ nhớ lưu trữ lớn hơn rất nhiều. Tuy
nhiên, các bài toán trong các đề thi học sinh giỏi hiện nay thì khơng những u
cầu dữ liệu lớn mà cịn u cầu thời gian thực hiện nhanh (thường không quá 1s).
3


Để giải các bài tốn có dữ liệu lớn như vậy ta cần tối ưu hóa các thuật tốn để
giảm không gian lưu trữ và thời gian thực hiện chương trình.
Tối ưu hố thuật tốn là một cơng việc u cầu tư duy thuật toán rất cao,
cùng với khả năng sử dụng thuần thục các cấu trúc dữ liệu. Vì vậy, việc tìm ra
thuật tốn tối ưu là khơng dễ chút nào. Tối ưu hoá thường được tiến hành theo hai
góc độ đó là tối ưu theo khơng gian lưu trữ (bộ nhớ) và tối ưu theo thời gian có
nghĩa là giảm độ phức tạp thuật toán, giảm các bước xử lý trong chương trình…
Tuy nhiên khơng phải lúc nào ta cũng có thể đạt được đồng thời cả 2 điều đó,
trong nhiều trường hợp tối ưu về thời gian sẽ làm tăng không gian lưu trữ, và
ngược lại. Bài toán trong Tin học đa dạng và phong phú. Do đó, việc giải được
các bài tốn này khơng phải là vấn đề đơn giản. Từ kinh nghiệm bồi dưỡng đội
tuyển học sinh giỏi cấp tỉnh môn Tin học những năm qua tôi mạnh dạn đưa ra
một số kinh nghiệm giải một số bài tốn có dữ liệu lớn bằng cách tối ưu hóa thuật

tốn bằng các dạng bài tập cụ thể như sau:
Dạng 1: Sử dụng suy luận toán học
Trong nhiều bài toán tin học nếu biết suy luận dựa theo các định lí, cơng
thức, kết quả… của tốn học có thể cho ta cách giải rất tốt. Chúng ta xét các ví dụ
sau:
Ví dụ 1:
Bài toán: Kiểm tra tính nguyên tố của một số nguyên dương (N>0)
- Cách 1: Đây là 1 bài toán cơ bản trong Tin học nhưng nếu sử dụng thuật
tốn khơng tối ưu thì khơng thể giải quyết được trong trường hợp N lớn
- Từ định nghĩa số nguyên tố là số nguyên dương chỉ có 2 ước khác nhau là
1 và chính nó thì ta có thể viết hàm nguyên tố như sau:
Function NT1(n: Longint): Boolean;
Var i: integer;
Begin
If n=1 then exit(false);
If n<4 then exit(true);
NT:= true;
For i:=2 to n – 1 do
If n mod i = 0 then Exit(false);
End;

Thuật toán trên trong trường hợp xấu nhất có độ phức tạp tới O(n). Nếu n là
hợp số thì có thể chương trình vẫn thực hiện với N khá lớn. Tuy nhiên nếu là số
nguyên tố thì chỉ thực hiện với N có 8 chữ số trong thời gian không quá 1 giây.
Thử test với phần mềm Themis:
Với n = 16769023 (8 chữ số), Thời gian ≈ 0.303929545 giây
Với n = 479001599 (9 chữ số),Thời gian ≈ 8.526715727 giây
4



Chúng ta vẫn có thể cải tiến thuật tốn trên tốt hơn nữa.
- Cách 2: Theo toán học, một số N nếu có ước khác ngồi ước 1 và chính nó
thì ít nhất nó phải có một ước từ 2 đến

, ta có thuật tốn tốt hơn như sau:

Function NT2(n: int64): Boolean;
Var i: longint;
Begin
If n=1 then exit(false);
If n<4 then exit(true);
NT2:= true;
For i:=2 to Trunc(sqrt(n)) do
If n mod I = 0 then Exit(false);
End;

Thuật toán này độ phức tạp là O(

. Cụ thể số phép tính thực hiện được

giảm đi rất nhiều, cho nên có thể mở rộng n lên kiểu int64. Thuật tốn này có thể
thực hiện với N lớn(khoảng 1016).
Với n = 16769023 (8 chữ số), Thời gian ≈ 0.013671612 giây
Với n = 479001599 (9 chữ số), Thời gian ≈ 0.013969978 giây
Với n = 1125899839733759 (16 chữ số) Thời gian ≈ 0.586635056 giây
Với n = 18014398241046527 (17 chữ số) Thời gian ≈ 2.713529998 giây
- Cách 3: Sau đây là một thuật toán dựa trên nhận xét nếu N là số nguyên tố
lớn hơn 5 thì phần dư của phép chia N cho 6 chỉ là 1 hoặc 5. Nhận xét này cho
phép ta chỉ kiểm tra tính chia hết của N cho các số theo bước tăng 2, 4, 2, 4…
Function NT3(n: int64): Boolean;

Var I,j: longint;
Begin
Nt3:=true;
If n =1 then
Exit(false);
If n mod 2 =0 then
Exit(false);
If n mod 3 =0 then
Exit(false);
If n<25 then exit;
i:=5; j:=2;
While i<= trunc(sqrt(n)) do
begin
If n mod i = 0 then
Exit(false);
inc(i,j); j:=6-j;
5


End;
End;

Với thuật toán này số bước kiểm tra giảm đi rất nhiều.
Với n = 1125899839733759 ( 16 chữ số) Thời gian ≈ 0.481653661 giây
n = 18014398241046527 ( 17 chữ số) Thời gian ≈ 1.886606245 giây
Như vậy, khi N là nguyên tố lớn thì thời gian thực hiện của thuật tốn này
nhanh hơn. Tuy nhiên với yêu cầu thời gian không quá 1 giây thì cũng chỉ thực
hiện với N khoảng 216
Ví dụ 2:
Bài toán: Tính tổng tất cả các số tự nhiên bé hơn hoặc bằng N chia hết cho 3

hoặc 5. Yêu cầu thời gian thực hiện không quá 1 giây.
- Cách 1: Đây là bài toán khá đơn giản, đa số học sinh sẽ làm theo cách
thông thường như sau:
Function Sum(n:longword): qword;
var i:longword;
Begin
sum:=0;
for i:=1 to n do
If (i mod 3 =0) or ( i mod 5 =0) then
sum:=sum + i;
End;

Với cách giải này có độ phức tạp thuật tốn là O(n).
Kiểm tra với chương trình chấm Themis
Với N = 107 thì thời gian thực hiện ≈ 0.109979881 giây.
Với N = 108 thời gian thực hiện ≈ 2.476291022 giây.
Vậy nếu sử dụng cách trên chỉ thực hiện được với N ≤ 107
Nếu bài toán yêu cầu với dữ liệu lớn hơn, chúng ta phải tìm cách tối ưu thuật
tốn. Đây là một bài tốn trong tốn học do đó giáo viên có thể hướng dẫn học
sinh suy luận theo toán học như sau:
- Cách 2:
Nhận xét:
Tổng số từ 1 ->N chia hết cho 3 và 5 có thể tách ra 3 vế:
sum(3) + sum(5) - sum(15) => tổng các số chia hết cho 3 + tổng các số chia
hết cho 5 và trừ đi tổng các số chia hết cho 15 (vì chia hết cho 15 chắc chắn sẽ
chia hết cho 3 hoặc 5)
Để tính sum(3) chúng ta thấy các số như sau:
s = { 3 , 6, 9, 12, … , …} các số trong chuỗi này đều chia hết cho 3
Đặt 3 ra khỏi chuỗi số chúng ta sẽ có
s = 3 {1, 2, 3,…, ..}


6


Tương tự với chuỗi chia hết cho 5 và 15
5{1, 2, 3,…,…)
15{1, 2, 3, …, …}
Như vậy chúng ta phải tính tổng của 1 + 2 + 3 +…+ m
Cơng thức để tính là : m*(m+1)/2
Số cuối cùng của mỗi chuỗi là m = N/ 3 với chuỗi số chia hết cho 3; m=N/5
với chuỗi số chia hết cho 5; m = N/15 với chuỗi số chia hết cho 15.
Như vậy chúng ta sử dụng 2 hàm:
Hàm tongk(n,k) để tính tổng 1 + 2 + 3 + …+ ..n/k (k=3 hoặc 5 hoặc 15)
Hàm Sum(n) để tính tổng theo yêu cầu của bài tốn.
Chương trình:
Const f1='SUM.inp';
f2='SUM.out';
Var x:int64;
procedure Doctep;
Begin
assign(input,f1);
reset(input);
assign(output,f2);
rewrite(output);
read(x);
end;
procedure Dongtep;
begin
close(input);
close(output);

end;
Function tongk(n:qword;k: byte): qword;
var m:qword;
Begin
m:=trunc(n/k);
Tongk:= m*(m+1) div 2;
{tongk nhận giá trị nguyên nên dùng phép toán div}
End;
Function sum(n:qword): qword;
Begin
Sum:=3*tongk(n,3) + 5* tongk(n,5) - 15*tongk(n,15);
End;
Begin
Doctep;
write(sum(x));
7


Dongtep;
End.

Thực hiện chạy kiểm tra với chương trình Themis,
với N = 107, Thời gian ≈ 0.013697812 giây.
với N = 109 thời gian ≈ 0.013900636 giây, lúc này tổng Sum chứa 18 chữ số
(Sum =233333334166666668≈ kiểu qword ), thời gian thực hiện vẫn đảm bảo
yêu cầu. Nếu thay đổi kiểu dữ liệu của Sum như sau, ta có thể thực hiện với N
lớn hơn:
Function tongk(n:qword;k: byte): real;
var m:qword;
Begin

m:=trunc(n/k);
Tongk:= m*(m+1) / 2;
End;
Function sum(n:qword): real;
Begin
Sum:=3*tongk(n,3) + 5* tongk(n,5) - 15*tongk(n,15);
End;

Thay đổi trên chương trình có thể thực hiện với N =1010,
thời gian thực hiện ≈ 0.018241349 giây.
Như vậy, với cách tối ưu trên bài tốn có thể giải được với dữ liệu lớn trong
thời gian yêu cầu. Để có thể giải theo cách này địi hỏi người giải phải có sự suy
luận theo cơng thức tốn học. Trong thực tế rất cần có sự suy luận tốn học trong
các bài tốn tin học.
Ví dụ 3: Bài toán: TÌM SỐ
Hãy tìm số ngun dương nhỏ nhất có chữ số hàng đơn vị là d (1≤d≤9) sao
cho nếu chuyển chữ số hàng đơn vị lên trước chữ số đầu tiên thì ta được một số
mới gấp k lần số cũ (1≤k≤9)
Dữ liệu: Vào từ file văn bản NUMBER.INP
Gồm nhiều dòng, mỗi dòng chứa hai số nguyên dương d, k cách nhau ít
nhất một dấu cách.
Kết quả: Ghi ra file văn bản NUMBER.OUT
Gồm nhiều dòng, mỗi dịng ghi một số ngun tìm được ứng với d,k ở dòng
tương ứng của file dữ liệu. Ghi -1 nếu khơng có sốthỏa mãn
Ví dụ:
NUMBER.INP
8 4

NUMBER.OUT
205128


Ghi chú: Có 50% số test kết quả không vượt quá 106.

8


- Cách 1: Đa số học sinh sẽ thực hiện tìm số N bằng cách thử lần lượt các số
từ số có 2 chữ số, đến 3 chữ số… xem số nào thỏa mãn khi đổi chỗ chữ số hàng
đơn vị lên trước chữ số đầu tiên thì được số mới hơn số cũ k lần. Nếu thực hiện
cách này sẽ có 2 nhược điểm:
+ Thứ nhất, số lần thực hiện việc tìm kiếm sẽ rất nhiều (vì phải kiểm tra tất
cả các số đến khi tìm thấy).
+ Thứ hai, khó kiểm sốt được số lần thực hiện tìm kiếm.
- Cách 2: Nếu suy luận theo tốn học thì sẽ dễ dàng hơn nhiều.
Ví dụ: N =

số sau khi chuyển chữ số hàng đơn vị lên trước số đầu

tiên là
. Theo đề ra:
=k*
, ta có
c = (k*d + nhớ) mod 10 (nhớ lúc đầu =0, nhớ = (k*d + nhớ) div 10)
b = (k * c + nhớ) mod 10
a = (k*b + nhớ) mod 10
d = (k*a + nhớ) mod 10
Như vậy, sẽ lần lượt tạo ra các chữ số của số cần tìm bằng cách trên, cho đến
khi có giá trị bằng d và nhớ =0 và giá trị số tạo ra trước đó lớn hơn 0 thì q trình
tạo số thỏa mãn kết thúc.
Ta có chương trình như sau:

const
fi='number.inp';
fo='number.out';
var d,k: longint;
n: longint;
x: array[0..110] of longint; {Dùng mảng x để lưu ngược các chữ số tạo ra trong
số N, giả sử có thể chứa số có 100 chữ số}
procedure Taoso;
var nho,i: longint;
begin
x[0]:=d; nho:=0; {x[0] = d}
for i:=1 to 100 do
begin
x[i]:=(x[i-1]*k+nho) mod 10;
nho:=(x[i-1]*k+nho) div 10;
if (x[i]=d) and (x[i-1]>0) and (nho=0) then
begin
n:=i-1;
{khi gặp x[i] = d thì kết thúc việc tạo số, số cần tìm chỉ tính từ i -1 đến 1}
exit;
end;
9


end;
n:=-1;
end;
procedure xuli;
var i: longint;
begin

assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
while not seekeof do
begin
readln(d,k);
Taoso;
if n=-1 then writeln(n) else
begin
for i:=n downto 1 do write(x[i]);
writeln(d);
end;
end;
close(input); close(output);
end;
BEGIN
xuli;
END.

Thực hiện chạy thử chương trình
NUMBER.INP
97
98
99

NUMBER.OUT
1304347826086956521739
1139240506329
10112359550561797752808988764044943820224719

Thời gian thực hiện ≈ 0.474935056 giây

Như vậy, giải bằng cách này ta có thể tìm ra được N rất lớn thỏa mãn mà
thời gian thực hiện nhanh.
Kết luận: Đối với những bài toán dạng toán học đòi hỏi người giải cần linh
hoạt vận dụng các suy luận, cơng thức…trong tốn học để giải thì chương trình
sẽ đơn giản và giải quyết được vấn đề dữ liệu lớn trong thời gian hạn chế.
Các bài tập tương tự:
Bài 1: TRÁO ĐỔI
Có 3 chiếc hộp kín, khơng thể nhìn thấy bên trong, được đặt ở 3 vị trí 1, 2,
3 trên bàn. Trong chiếc hộp ở vị trí 1 có một quả bóng, cịn các hộp ở vị trí 2 và 3
là hộp rỗng. Người ta thực hiện một số thao tác tráo đổi vị trí các chiếc hộp, mỗi
thao tác tráo đổi là đổi vị trí của 2 chiếc hộp, cụ thể như sau:
10


- Thao tác A là tráo đổi hộp ở vị trí 1 với hộp ở vị trí 2.
- Thao tác B là tráo đổi hộp ở vị trí 2 với hộp ở vị trí 3.
- Thao tác C là tráo đổi hộp ở vị trí 1 với hộp ở vị trí 3.
Yêu cầu: Cho chuỗi thao tác tráo đổi, hãy cho biết vị trí nào có chiếc hộp
chứa quả bóng.
Dữ liệu vào từ file TRIK.INP
Chỉ một dòng duy nhất chứa không quá 50 ký tự, là chuỗi các thao tác tráo
đổi.
Mỗi ký tự là ‘A’, ‘B’ hoặc ‘C’
Kết quả ghi ra file TRIK.OUT
Chỉ một số duy nhất là vị trí của chiếc hộp có chứa quả bóng.
Ví dụ
TRIK.INP
AB

TRIK.OUT

3

TRIK.INP
CBABCACCC

TRIK.OUT
1

Bài 2: SỐ SIÊU NGUYÊN TỐ
Số siêu nguyên tố là số nguyên tố mà khi bỏ một số tuỳ ý các chữ số bên
phải của nó thì phần cịn lại vẫn tạo thành một số nguyên tố.
Ví dụ: 3733 là một số siêu ngun tố có 5 chữ số vì 3733, 373, 37,3 cũng là
các số nguyên tố.
Hãy viết chương trình đọc dữ liệu vào là một số nguyên N (0< N <=10), kết
quả ra là các số siêu nguyên tố có N chữ số.
SIEUNT.INP
10

SIEUNT.OUT
19793393331979339339

Bài 3: Nhập vào n (1<=n<=30000), hãy in ra số chữ số 0 cuối cùng của n
giai thừa.
Dạng 2: Bài toán đoạn con
Trong các bài toán học sinh giỏi hiện nay có rất nhiều bài tốn dạng đoạn
con, dãy con. Dữ liệu yêu cầu của bài toán thường rất lớn. Do đó chúng ta thường
phải tối ưu hóa thuật toán cho những bài toán dạng này để đáp ứng thời gian thực
hiện.
Ví dụ 4: Cho dãy A gồm n số thực, một đoạn con của dãy A là một dãy con
gồm các phần tử liên tiếp của dãy A. Hãy tìm đoạn con dài nhất của dãy số gồm

các phần tử khơng giảm.
Ví dụ: cho dãy 12 số: 8 8 9 9 5 1 2 4 6 6 5 7
Đoạn con không giảm dài nhất là: 1 2 3 6 6
11


- Cách 1:
- Tìm tất cả mọi đoạn con của dãy đã cho:
- Kiểm tra một đoạn con (từ i đến j) có là đoạn khơng giảm khơng?
- Nếu đoạn con đúng là đoạn khơng giảm thì so sánh độ dài đoạn con vừa
tìm được (độ dài=j-i+1) với những đoạn trước nếu lớn hơn thì lưu lại.
Đoạn chương trình như sau:
Function Khonggiam(i, j:integer):boolean;
Var k: integer;
Begin
Khonggiam:=true;
For k:=i to j-1 do
If A[k]>A[k+1] then begin khonggiam:=false; exit; end;
End;
{đoạn chương trình xét mọi đoạn con}
Procedure xuly;
Var i,j: integer;
Begin
For i:=1 to n do
For j:=i to n do
If khonggiam(i,j) then
{So sánh để tìm đoạn con tốt hơn và lưu lại}
If j-i+1> max then
Begin max:=j-i+1; luu:=i; end;
End;


Nhận xét: Ta thấy rằng việc xét tất cả mọi đoạn con như trên chỉ giải quyết
được với N nhỏ, với N lớn thì ảnh hưởng tới thời gian thực hiện chương trình. Để
tăng tốc chương trình ta cải tiến thuật toán như sau:
- Cách 2: Bắt đầu từ i:=1, nếu từ chỉ số i đến j là đoạn không giảm thì đoạn
tiếp theo được xét sẽ bắt đầu với i:=j+1 như vậy những đoạn con trong khoảng từ
i đến j sẽ khơng bị xét lại.
Ví dụ với đoạn số: 2 2 5 1 2 3 7 7 4 9 thì sẽ chỉ xét so sánh 3 đoạn con: 2 2
5; 1 2 3 7 7; và 4 9.
Chương trình như sau:
Const fi = ’doanmax.inp’;
fo=’doanmax.out’;
maxn=1000000;
Var a: Array[1..maxn] of real;
n, i, k: integer; f: text;
Procedure docfile; {đọc dữ liệu vào từ tệp}
Var i:integer;
12


Begin
Assign(f,fi);
Reset(f);
Readln(f,n);
For i:=1 to n do read(f,a[i]);
Close(f);
End;
function khonggiam(i,k:integer):boolean;
{Kiểm tra đoạn con bắt đầu từ chỉ số i và có độ dài k là đoạn không giảm?}
Var j: integer;

Begin
Khonggiam:=true;
For j:=i to i+k-2 do
if a[j]>a[j+1] then begin khonggiam:=false; exit; end;
end;
Procedure Xuat;
Var j:integer;
Begin
Assign(f,fo);
Rewrite(f);
For j:=i to i+k-1 do write(f, A[j],’ ’) { đoạn con không giảm}
Writeln(f);
End;
Procedure xuly;
Var ok:boolean;
Begin
Ok:=false;
For k:= n downto 1 do {k là độ dài đoạn con}
Begin
For i:=1 to n-k+1 do { đoạn con bắt đầu từ i}
If khonggiam(i, k) then begin Xuat; ok:=true; end;
If ok then exit;
End;
End;
Begin
Docfile;
Xuly;
Close(f);
End.


13


Ví dụ 5: Cho dãy số a0, a1 , …, an-1 (0 < n < 106), hãy tìm dãy con có tổng
lớn nhất của dãy số đó. Yêu cầu đưa ra tổng lớn nhất và chỉ sổ đầu và cuối của
dãy con tìm được. Hạn chế của bài tốn là thời gian chạy dưới 1s.
Bài tốn có vẻ đơn giản, nhưng với n khá lớn nếu không được giải quyết tốt
sẽ không đáp ứng được yêu cầu về thời gian. Với các bài tốn có hạn chế về thời
gian như thế, địi hỏi ta phải đưa ra được thuật tốn tối ưu nhất.
- Cách 1: Xét tất cả các đoạn con, code như sau:
Procedure Timmax;
Var i,j,k: longint;
Begin
Max:= a[0]; dau:=0; cuoi:=0;
For i:=0 to n do
For j:=i to n do
Begin
S:=0;
For k:=i to j do
S:=s+a[k];
If s > max then
Begin
Max:=S;
Dau:=i;
Cuoi:=j;
End;
End;
End;

Với cách giải trên có độ phức tạp O(n 3) với 3 vòng for lồng nhau. Đoạn mã

trên có quá nhiều cái để ta có thể tối ưu, ví dụ như khi tính tổng từ i->j đã không
tận dụng được tổng từ i->(j-1) đã được tính trước đó! Do vậy đây chưa thể là
chương trình tối ưu được. Đương nhiên không thể thực hiện với dữ liệu và thời
gian yêu cầu của đề ra.
- Cách 2: Với 1 cải tiến nhỏ ta sẽ xét hết tất cả các dãy con có thể của dãy số
bắt đầu từ vị trí thứ i (i=0,1,2…n-1), với việc tận dụng lại các giá trị đã được tính
trước đó. Chúng ta có đoạn mã sau:
Procedure Timmax;
Var i,j,k: longint;
Begin
Max:= a[0]; dau:=0; cuoi:=0;
For i:=0 to n-1 do
Begin
S:=0;
14


For j:=i to n do
Begin
S:=s+a[j];
If s > max then
Begin
Max:=S;
Dau:=i;
Cuoi:=j;
End;
End;
End;
End;


Thuật toán trên sử dụng 2 vòng lặp lồng nhau, vòng đầu tiên lặp n lần, vòng
thứ 2 lặp tối đa n lần, nên dễ thấy độ phức tạp của thuật toán này là O(n2).
Thử với N =104Thời gian thực hiện ≈ 0.173273530 giây. Với N lớn hơn
không thể thực hiện trong thời gian dưới 1s như yêu cầu của đề ra. Do đó khơng
đáp ứng được về mặt thời gian.
- Cách 3:
Ta sử dụng thêm 2 mảng k,c mỗi mảng gồm n phần tử. Trong đó k[i] sẽ lưu
giá trị lớn nhất của tổng dãy con mà a[i] là cuối dãy, c[i] sẽ lưu chỉ số đầu của dãy
đó. Như vậy k[i]=max(a[i],a[i]+k[i-1]). Chúng ta có đoạn mã sau, nó được xây
dựng trên tư tưởng quy hoạch động:
Procedure timmax;
Var i,j,k: longint; k:array[1..100000] of longint;
Begin
Max:=0; k[0]:=0; c[0]:=0; dau:=0;cuoi:=0;
For i:=1 to n do
Begin
S:=k[i-1] +a[i];
K[i]:=a[i];
C[i]:=i;
If s> k[i] then
Begin
K[i]:=s;
C[i]:=c[i-1];
End;
If k[i] > max then
Begin
Max:=k[i];
Cuoi:=i;
15



End;
End;
Dau:=c[cuoi];
End;

Với thuật toán trên độ phức tạp là O(n) với chỉ 1 vịng lặp n lần duy nhất.
Như vậy, có thể nói ta đã tối ưu xong về mặt thời gian từ độ phức tạp O(n3) xuống
cịn O(n). Về khơng gian bộ nhớ, ở cách làm trên ta đã sử dụng thêm 2 mảng k và
c mỗi mảng n phần tử, nếu bài toán yêu cầu với N lớn hơn thì giải quyết thế nào?
Ta sẽ dễ dàng bỏ mảng k đi khi sử dụng tính tốn hồn tồn trên mảng a, vậy có
thể bỏ nốt mảng c đi khơng? nếu bỏ đi thì ta sẽ xác định giá trị đầu của mỗi dãy
như thế nào?
- Cách 4:
Tại mỗi vị trí đang xét ta sẽ so sánh để tìm ra dãy có tổng lớn nhất ngay như
trên, và như vậy ta sẽ sử dụng 1 biến để lưu giá trị đầu của dãy tìm được! Cách
làm như sau:
Procedure timmax;
Var i,j,k: longint;
Begin
Dau:=0; cuoi:=0; luu:=0;max:=a[0];
For i:=1 to n do
Begin
A[i]:=a[i] + a[i-1];
If a[i] < a[i]-a[i-1] then
Begin
A[i]:=a[i]-a[i-1];
Dau:=i;
End;
If a[i]>max then

Begin
Max:=a[i];
Cuoi:=i;
Luu:=dau;
End;
End;
Dau:=luu;
End;

Cách làm trên đã hiệu quả, tuy nhiên nó đã làm biến đổi dãy số ban đầu
(mảng a). Làm sao có thể giữ nguyên dãy số, không dùng thêm mảng phụ, vẫn
đáp ứng được yêu cầu đề bài. Với một cải tiến nhỏ ta sẽ thấy thuật toán tối ưu
nhất:
16


Procedure timmax;
Var i,j,k: longint;
Begin
Dau:=0; cuoi:=0; luu:=0;max:=a[0];
For i:=1 to n do
Begin
S:=s+a[i];
If s < a[i] then
Begin
S:=a[i];
Dau:=i;
End;
If s > max then
Begin

Max:=s;
Cuoi:=i;
Luu:=dau;
End;
End;
Dau:=luu;
End;

Với N = 104Thời gian thực hiện ≈ 0.014385053 giây
Với N = 106Thời gian thực hiện ≈ 0.145823363 giây
Như vậy, với những bài toán dạng dãy con, đoạn con, chúng ta cần chú ý
tinh chỉnh sao cho giảm số lần thực hiện để chương trình có thể thực hiện với dữ
liệu lớn trong thời gian hạn chế. Bài toán dạng này khá đa dạng. Trên thực tế, đôi
lúc việc tinh chỉnh cũng chưa giải quyết hết tối ưu mà đòi hỏi sử dụng các thuật
toán khác như quy hoạch động, chia để trị…mới đạt được u cầu bài tốn.
Chúng ta sẽ tìm hiểu ở dạng 3.
Bài tập tương tự:
Bài 1: DÃY CON
Cho một dãy số nguyên dương a1,a2…aN (10=1..N và một số nguyên dương S (S<100 000 000)
Yêu cầu: Tìm độ dài nhỏ nhất của dãy con chứa các phần tử liên tiếp của
dãy mà có tổng các phần tử lớn hơn hoặc bằng S.
Dữ liệu vào: Đọc từ file SUB.INP gồm nhiều test, mỗi test chứa N và S ở
dòng đầu. Dòng 2 chứa các phần tử của dãy.
Dữ liệu ra: Kết quả ghi vào file SUB.OUT, mỗi test đưa ra một dòng chứa
độ dài của dãy con tìm được.
Ví dụ:
17



SUB.INP
10 15

SUB.OUT
2

Bài 2: Cho dãy số gồm N số nguyên (0 phần tử có cùng dấu.
Dạng 3: Sử dụng các phương pháp giải tối ưu
Bài toán trong Tin học rất đa dạng và phong phú. Với 2 dạng trên có thể
chúng ta chỉ cần tinh chỉnh, giảm độ phức tạp thuật toán là có thể đáp ứng dữ liệu
lớn. Tuy nhiên, đa số các bài toán trong Tin học muốn tối ưu thuật tốn thì
thường phải sử dụng đến các phương pháp giải tối ưu trong Tin học như Chia để
trị, Quy hoạch động…Ở đây, chúng ta xét một bài toán từ cách giải thông thường
đến cách tối ưu bằng phương pháp quy hoạch động.
Ví dụ 6: Cho dãy số nguyên A = a1, a2, ..., an (1≤n≤500000,
-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ử và giữ nguyên thứ tự.
Yêu cầu: Tìm dãy con đơn điệu tăng của dãy A có độ dài lớn nhất.
Dữ liệu vào: Cho từ file văn bản INCSEQ.INP
- Dòng đầu ghi số n
- Dòng thứ hai ghi n số a1, a2, ..., an cách nhau ít nhất một dấu cách.
Kết quả: Ghi ra file văn bản INCSEQ.OUT
- Dòng đầu ghi độ dài của dãy con tìm được
- Các dịng tiếp theo, mỗi dòng ghi hai số tương ứng là chỉ số và giá trị
của phần tử được chọn trong dãy A theo thứ tự từ đầu đến cuối.
Ví dụ:
INCSEQ.INP
INCSEQ.OUT
10

5
3 9 4 8 6 2 1 7 10 5
13
34
56
87
9 10
Bài toán này nếu giải theo cách thơng thường thì với N lớn không thể đạt
yêu cầu về thời gian được. Vấn đề được giải quyết nếu sử dụng phương pháp quy
hoạch động. Gọi L là độ dài cần tìm, ta có chương trình như sau
Const

fi ='INCSEQ.INP';
fo='INCSEQ.OUT';
Var
L,Truoc,a:array[1..5001] of integer;
n,m,i,k:integer;
f:text;
Begin
18


Assign(f,fi);
Reset(f);
Readln(f,n);
For i:=1 to n do Read(f,a[i]);
Close(f);
L[n+1]:=0;
For k:=n downto 1 do
Begin

m:=n+1;
For i:=k+1 to n do
If (a[k]<a[i]) and (L[i]>L[m]) then m:=i;
L[k]:=L[m]+1;
Truoc[k]:=m;
End;
m:=1;
For i:=2 to n do
If L[i]>L[m] then m:=i;
Assign(f,fo);
ReWrite(f);
Writeln(f,L[m]);
Repeat
Writeln(f,m,' ',a[m]);
m:=Truoc[m];
Until m>n;
Close(f);
End.

Bài tập tương tự:
Bài 1: Một dãy N các số nguyên a1, a2, …aN được gọi là chia hết hoàn toàn
nếu aj chia hết cho ai với mọi i,j (ihoàn toàn.
Một dãy con của một dãy cho trước được thiết lập bằng cách xóa đi một số
phần tử nào đó của dãy. Ví dụ: với dãy gồm 9 phần tử 2, 3, 7, 8, 14, 39, 145, 76
thì dãy 3, 7, 14, 76 là một dãy con nhưng 3, 14, 7 không phải là dãy con.
Yêu cầu: với một dãy N số nguyên đã cho, hãy đưa ra một dãy con chia hết
hồn tồn có độ dài lớn nhất ứng với các test dữ liệu vào sau đây (mỗi test gồm 2
dòng: dòng 1 ghi số N các phần tử, dòng 2 ghi N phần tử của dãy, các phần tử
cách nhau ít nhất 1 dấu cách)

Ví dụ: Với bộ test sau:
9
2, 3, 7, 8, 14, 39, 145, 76, 320
19


Thì dãy kết quả là:2, 8, 320
Bài 2: Trong siêu thị có n gói hàng đánh số từ 1 đến n (n≤1000), gói hàng
thứ i có trọng lượng Wi ≤1000 và giá trị Vi ≤1000. Một tên trộm đột nhập vào
siêu thị mang theo một cái túi có thể mang được tối đa trọng lượng M ≤1000. 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?
Dữ liệu vào: Cho từ file văn bản BAG.INP
- Dòng đầu chứa hai số nguyên dương n, M
- n dòng tiếp theo, dòng thứ i ghi hai số nguyên dương Wi và Vi .
Kết quả: Ghi ra file văn bản BAG.OUT
- Dòng đầu ghi giá trị lớn nhất tên trộm có thể lấy được.
- Dịng thứ hai ghi chỉ số của những gói hàng bị lấy theo thứ tự chỉ số từ
nhỏ đến lớn.
Ví dụ:
BAG.INP
5 11
33
44
54
9 10
44

BAG.OUT
11
125


Việc giảm tối đa độ phức tạp thuật toán là một trong những cách tốt nhất cần
thực hiện để giải các bài tốn có u cầu dữ liệu lớn. Giáo viên nên thường xuyên
hướng dẫn học sinh linh hoạt vận dụng kiến thức toán học, tin học để cải tiến
thuật toán tối ưu nhất có thể. Khi giải một bài tốn trong Tin học ngồi việc lựa
chọn thuật tốn tối ưu nhất thì việc tổ chức dữ liệu hợp lý, đặc biệt là dữ liệu lớn
cũng rất quan trọng. Tuy nhiên, trong quá trình giải người lập trình phải linh hoạt
để đáp ứng u cầu của bài tốn. Trong q trình giảng dạy, giáo viên nên cho
học sinh thực hiện giải nhiều bài tốn có dữ liệu lớn để học sinh rèn luyện được
khả năng tư duy linh hoạt và kỹ năng vận dụng sáng tạo theo bài toán cụ thể.
2.4. Hiệu quả của sáng kiến kinh nghiệm
Qua các năm giảng dạy và tham gia bồi dưỡng đội tuyển học sinh giỏi môn
Tin học của nhà trường, tôi đã áp dụng các giải pháp nêu trên để thực hiện giảng
dạy và nhận thấy khá hiệu quả. Từ những ví dụ nên trên, với mỗi bài đều được
làm mịn dần từ cách suy nghĩ đơn giản để giải đến cách làm cho thuật toán tốt
nhất. Từ cách tổ chức dữ liệu để tiết kiệm bộ nhớ, đến cách sử dụng file để làm
“bộ nhớ ảo”, học sinh đã hứng thú rất nhiều và có thể làm được những bài tương
tự, thậm chí phức tạp hơn. Từ đó các em tự khai thác khả năng tư duy, sáng tạo
của bản thân mình và đam mê môn Tin học hơn. Kết quả bồi dưỡng học sinh giỏi

20


từ những năm gần đây đều đạt kết quả, cụ thể: năm học 2017-2018 đạt 01 giải ba,
01 giải KK; năm học 2018-2019 đạt 01 giải nhất, 01 giải ba.
Năm học 2020-2021 học sinh khá giỏi ở các lớp 11 đã làm được 1 số bài tập
cơ bản ở các dạng trên.
3. KẾT LUẬN, KIẾN NGHỊ
3.1. Kết luận:
Từ thực tế giảng dạy và bồi dưỡng học sinh giỏi, tôi nhận thấy: bài toán Tin

học đa dạng và phong phú. Người lập trình phải rất linh hoạt trong giải và cài đặt
chương trình. Đặc biệt với những bài tốn địi hỏi xử lý với dữ liệu lớn trong thời
gian hạn chế. Với mỗi dạng tốn cần có phương pháp giải phù hợp, với mỗi bài
tốn cần tìm giải thuật tốt (tối ưu) cùng với việc xây dựng cấu trúc dữ liệu lưu trữ
hợp lý. Qua các năm giảng dạy, cùng với sự tìm tịi các tài liệu, các bài viết trên
sách tham khảo và mạng Internet cùng với cấu trúc đề ra và kiến thức cơ bản qua
các kì thi chọn học sinh giỏi Tỉnh, tôi thấy với các giải pháp đã nêu có thể giúp
học sinh có thêm kinh nghiệm để giải nhiều bài toán tương tự. Với mỗi giải pháp
chúng ta có thể đưa ra rất nhiều ví dụ để học sinh có thể vận dụng, tuy nhiên
trong phạm vi của một sáng kiến kinh nghiệm, tôi chỉ đưa ra một số ví dụ để trình
bày. Vì kinh nghiệm chưa nhiều, không thể tránh khỏi các hạn chế, rất mong
muốn nhận được những góp ý từ các bạn đồng nghiệp, Hội đồng khoa học các
cấp và bạn bè chia sẻ, bổ sung để đề tài có thể hồn thiện hơn.
3.2. Kiến nghị
Nhà trường có kế hoạch bảo trì, nâng cấp phòng thực hành tin học tốt hơn
để giúp học sinh có điều kiện được thực hành.

Trên đây là Một số dạng toán cho học sinh khá, giỏi môn Tin học 11 áp dụng
ở trường THPT Lê Lợi năm học 2020-2021 giúp học sinh và giáo viên tham
khảo.

21


4.. Tài liệu tham khảo
- Sách giáo khoa Tin học 11.
- Sách bài tập Tin học 11.
- Sách giáo viên Tin học 11.
- Tài liệu bồi dưỡng giáo viên Tin học 11.
- Tài liệu tập huấn giáo viên 2015, 2017.

- Tài liệu chuyên Tin học - Hồ Sĩ Đàm - NXB Giáo dục
- Sáng kiến kinh nghiệm của tác giả đã viết những năm học trước.
Thanh Hóa, ngày 10 tháng 05 năm 2021
XÁC NHẬN CỦA THỦ TRƯỞNG ĐƠN VỊ

Tôi xin cam đoan đây là SKKN của mình
viết, khơng sao chép nội dung của người
khác.
Người thực hiện

Lê Thị Kiều

22



×