Tải bản đầy đủ (.docx) (19 trang)

(SKKN 2022) rèn luyện tư duy lập trình cho học sinh khá giỏi thông qua việc phân tích, đánh giá nhiều cách giải khác nhau của một số bài tập tin học có sử dụng kĩ thuật ép kiểu trong ngôn ngữ lập trình c++

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 (327.87 KB, 19 trang )

1. MỞ ĐẦU
1.1 Lý do chọn đề tài
Để bắt nhịp xu thế hiện đại, trong hướng dẫn điều chỉnh nội dung dạy học
Tin học năm 2021- 2022 Bộ Giáo dục đã định hướng các trường THPT ngừng
dạy học NNLT Pascal, thay thế bằng các NNLT tiên tiến hơn như C, C++,
Python… Năm học 2021- 2022 là năm đầu tiên trường chúng tôi đưa NNLT C+
+ vào các bài dạy học Tin học chính khố cho học sinh khối 11 và ôn luyện thi
học sinh giỏi.
Đã từ lâu, công tác bồi dưỡng học sinh khá, giỏi được xem là nhiệm vụ
trọng tâm của mỗi nhà trường. Thông qua kết quả thi học sinh giỏi, phần nào
giúp khẳng định vị thế của nhà trường đối với các trường trong huyện trong tỉnh.
Đối với mơn Tin học, học lập trình góp phần phát triển tư duy sáng tạo, rèn
luyện tính cẩn thận tính chính xác cho người học; đồng thời việc học lập trình
ngay từ THPT góp phần phát hiện tài năng, định hướng nghề nghiệp cho học
sinh trước khi lựa chọn ngành nghề, bổ sung nguồn nhân lực tin học cho một xã
hội công nghệ thông tin.
Các đề thi học sinh giỏi mơn Tin học cấp THPT thường u cầu xử lí các
bộ dữ liệu có phạm vi khá lớn. Ngồi việc học sinh phải chú ý xây dựng thuật
toán tối ưu để đảm bảo thời gian thực hiện chương trình (theo quy định khơng
q 1 giây/1 test) thì học sinh cịn cần phải chú ý về kiểu dữ liệu của các biến
được sử dụng trong chương trình.
Ép kiểu là một kĩ thuật được sử dụng phổ biến được sử dụng trong các bài
thi học sinh giỏi.
Trên thực tế đã có rất nhiều tài liệu có đề cập đến kĩ thuật ép kiểu nhưng
các các tài liệu này chỉ đưa ra code của bài tốn, chưa phân tích lí do cần ép
kiểu, cách tư duy, cách cài đặt. Do đó khi áp dụng vào các bài toán khác nhau
học sinh thường lúng túng quên không thực hiện ép kiểu hoặc thực hiện ép kiểu
nhưng chưa đúng dẫn đến kết quả đưa ra (output) bị sai.
Từ lí do trên tơi xin trình bày sáng kiến kinh nghiệm “RÈN LUYỆN TƯ
DUY LẬP TRÌNH CHO HỌC SINH KHÁ GIỎI THƠNG QUA VIỆC
PHÂN TÍCH, ĐÁNH GIÁ NHIỀU CÁCH GIẢI KHÁC NHAU CỦA MỘT


SỐ BÀI TẬP TIN HỌC CĨ SỬ DỤNG KĨ THUẬT ÉP KIỂU TRONG
NGƠN NGỮ LẬP TRÌNH C++”
1.2 Mục đích nghiên cứu
Đề tài này đưa ra một số bài tập cần sử dụng kĩ thuật ép kiểu, các cách cài
đặt code mà học sinh thường thực hiện, phân tích đánh giá các cách giải. Thơng
qua đó học sinh sẽ biết chú ý hơn đến việc xem xét kĩ dữ liệu, cân nhắc khi khai
1


báo biến, cài đặt thuật tốn, đồng thời qua đó giúp học sinh rèn luyện tư duy lập
trình.
1.3 Đối tượng nghiên cứu
Các bài tập lập trình có sử dụng ép kiểu trong chương trình Tin học
THPT.
1.4 Phương pháp nghiên cứu
Trong q trình nghiên cứu và hồn thiện SKKN này tơi đã sử dụng nhiều
phương pháp:
- Phương pháp nghiên cứu tài liệu: Tơi đã nghiên cứu các bài tập lập trình
đề thi môn Tin học trong nhiều năm trở lại đây, nghiên cứu các bài làm của học
sinh, tổng hợp các cách giải.
- Phương pháp xử lí số liệu: Dựa vào kết quả chấm điểm từ phần mềm
chấm bài tự động Themis trên cùng một bộ Test chấm của các cách làm, phân
tích các lỗi sai, từ đó rèn luyện tư duy lập trình cho học sinh.

2


2. PHẦN NỘI DUNG
2.1 Cơ sở lí luận
Ép kiểu trong C, C++ là việc gán giá trị của một biến có kiểu dữ liệu này

tới biến khác có kiểu dữ liệu khác.
Cú pháp:

(type) value;

Ví dụ:
float c = 35.8f;
int b = (int)c + 1;
Trong ví dụ trên, đầu tiên giá trị dấu phẩy động c được đổi thành giá trị
nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.1
2.2 Thực trạng vấn đề trước khi áp dụng sáng kiến.
Năm học 2021-2022, được sự thống nhất của Tổ nhóm chun mơn, chúng
tơi đã lựa chọn NNLT C++ để đưa vào minh hoạ cho các bài dạy chính khố Tin
học 11 và ơn thi học sinh giỏi (thay thế cho NNLT Pascal). Tuy nhiên, do chưa
có sách giáo khoa thay thế nên cơ trò vẫn sử dụng sách giáo khoa Tin học hiện
hành, việc tổ chức dạy học, chỉ dẫn tìm kiếm tài liệu cho học sinh tham khảo gặp
khơng ít khó khăn.
Trong q trình ơn luyện học sinh giỏi tơi thường khơng máy móc yêu cầu
học sinh phải thực hiện cùng một cách làm, mà học sinh có thể thực hiện giải
theo nhiều cách khác nhau. Để nâng cao hiệu quả dạy học, tôi sử dụng phần
mềm chấm bài tự động Themis (tác giả Lê Minh Hoàng) để kiểm tra kết quả.
Sau khi chấm, thông báo kết quả tôi thường yêu cầu học sinh phải xem xét kĩ
từng cách làm của các bạn trong nhóm đội tuyển, tìm ra cái hay cái dở trong
từng code chương trình, để từ đó học sinh rèn luyện tư duy lập trình cho học
sinh. Trong quá trình thảo luận, trao đổi cách làm, tơi nhận thấy nhiều học sinh
tỏ ra khá lúng túng bởi các em khơng hiểu tại sao cùng một ý tưởng, cùng thuật
tốn của nhưng kết quả lại khác nhau. Có bạn ghi điểm tuyệt đối, có bạn chỉ ghi
được một phần nhỏ số điểm, lại cũng có bạn khơng ghi được điểm nào. Nguyên
nhân có thể rất nhiều, nhưng trong phạm vi sáng kiến kinh nghiệm này tôi chỉ
đưa ra lỗi do học sinh không thực hiện ép kiểu trong các bài tập cần ép kiểu

1 Từ ép kiểu trong C++
trinh-cpp/ep-kieu-trong-cpp

….. đến lưu vào b trích dẫn từ nguồn: />

hoặc có ép kiểu nhưng thực hiện chưa đúng dẫn tới kết quả chưa được như
mong muốn thông qua việc phân tích đánh giá các cách làm.
2.3 Các biện pháp đã sử dụng để giải quyết vấn đề
Cách rèn luyện tư duy lập trình tốt nhất đó là thực hành giải các bài tập.
Quá trình giải hệ thống các bài tập, học sinh buộc phải động não suy nghĩ để
xây dựng thuật tốn, viết code, khi đó học sinh mới thực sự tích luỹ và phát
triển tư duy, biến các kiến thức từ sách vở thành các kiến thức của bản thân. Để
học sinh tránh các lỗi sai, với mỗi dạng bài giáo viên cần tạo tình huống có vấn
đề, để học sinh giải quyết vấn đề và từ đó tích luỹ kinh nghiệm cho bản thân.
Như vậy để học sinh hiểu rõ kĩ thuật ép kiểu, tránh lỗi sai khi ép kiểu tôi đã xây
dựng hệ thống các bài tập có sử dụng ép kiểu. Để đưa học sinh vào tình huống
có vấn đề tơi thường lựa chọn các bài tập học sinh dễ sai nhất. Quá trình học
sinh giải bài tập, trao đổi với bạn cùng nhóm về cách giải của mình và của bạn
chính là học sinh đang tự giải quyết vấn đề. Thông qua đây học sinh sẽ tích luỹ
được thêm kinh nghiệm cho bản thân, rèn luyện tư duy lập trình, nâng cao hiệu
quả học tập.
Dưới đây là một số bài tập ép kiểu được tơi sử dụng để đưa học sinh vào
tình huống có vấn đề.
2.3.1 Bài tốn ví dụ
Bài tốn 1: Tổng nhỏ nhất
Tên file: MINSUM.CPP
Với hai số nguyên dương A, B cho trước. Ta dễ dàng tìm được ước chung
lớn nhất G và bội chung nhỏ nhất L của hai số A và B.
Bây giờ chúng ta hãy xét bài toán ngược của bài toán trên:
“Cho biết trước ước chung lớn nhất G và bội chung nhỏ nhất L của hai số

nguyên dương A và B.
Rõ ràng, sẽ có rất nhiều cặp (A, B) nguyên dương có ước chung lớn nhất là
G và bộ chung nhỏ nhất là L, tuy nhiên cũng có trường hợp chúng ta khơng thể
tìm được giá trị A, B thỏa mãn. Vì vậy, hãy xác định giá trị nhỏ nhất của tổng A
+ B, hoặc đưa ra -1 nếu khơng tìm được cặp (A, B)”.
INPUT: Hai số nguyên dương G và L (1 ≤ G ≤ L ≤ 109)
OUTPUT: Số nguyên dương là tổng nhỏ nhất có thể. Trong trường hợp
khơng tìm được hai số A và B thì đưa ra kết quả là -1.
Ví dụ:
4


MINSUM.IN
P

MINSUM.OU
T

2 10
2 20
35

12
14
-1

Giải thích ví dụ:
- Ở ví dụ thứ nhất: Chỉ có cặp (2, 10) thỏa mãn UCLN(2,10) = 2,
BCNN(2,10) = 10. Nên tổng là 12
- Ở ví dụ thứ hai: Có hai cặp (2, 20) và (4, 10) thỏa mãn, tổng nhỏ nhất có

thể là 14.
- Ở ví dụ thứ ba: khơng tìm được cặp nào thỏa mãn UCLN là 3 và BCNN
là 5
Ràng buộc

40% số điểm tương ứng với số test có 1 ≤ G ≤ L ≤ 100;

60% số điểm cịn lại khơng có ràng buộc gì
Ý tưởng thuật tốn
Nhận xét: Để giải bài tập này ta có thể sử dụng cách trâu bị đó là dùng 2
vịng lặp để duyệt các giá trị có thể có của a và b, rồi tìm và gán lại tổng bé nhất
a+b cho biến Smin lại sau mỗi lần duyệt.
- Cách làm này học sinh chắc chắn không thể được điểm tối đa với dữ liệu
đề bài đã cho do chạy quá thời gian quy định.
Do đó ta cần tìm ra cách giải tối ưu hơn.
Ta có: BCNN(a,b) = suy ra a*b = BCNN(a,b)*UCLN(a,b) = G*L
Đặt T = G*L
Giả sử a<=b khi đó a <=
Mặt khác a>= G (vì G là UCLN(a,b))
Nhận xét: Tích của 2 số a, b khơng đổi thì tổng 2 số bé nhất khi giá trị của
a, b chênh lệch ít nhất. Do đó để chương trình được thực hiện với số lần lặp ít
nhất ta cho biến a chạy giảm dần từ về đến G, với mỗi giá trị của a ta tìm được
giá trị của b tương ứng. Tổng a, b đầu tiên được tìm thấy chính là output của bài
tốn.
Cài đặt
Cách 1:
#include <bits/stdc++.h>
using namespace std;
int x, y, G, L;
int main()

{
5


freopen("MINSUM.INP","r",stdin);
freopen("MINSUM.OUT","w",stdout);
cin >> G >> L;
int T=G*L;
int can=sqrt(T);
for(int a=can; a>=G; a--)
{
if (T%a==0)
{
int b=T/a;
if(__gcd(a,b)==G) {cout<}
}
cout << "-1";
return 0;
}
/* Cách này học sinh chỉ ghi được điểm 85% số điểm.
Lí do: mặc dù đề bài chỉ cho G, L <=10 9 (có thể khai báo kiểu int)
nhưng T= G*L <=1018 (vượt quá phạm vi kiểu int). Do đó cách làm này sẽ
cho kết quả sai do tràn bộ nhớ ở những test có dữ liệu G, L lớn (cụ thể khi
G*L >109)
Kết quả điểm chấm của cách làm này sẽ giúp học sinh hiểu tại sao cần
phải ép kiểu, khi nào cần ép kiểu */
Cách 2:
#include <bits/stdc++.h>
using namespace std;

int x, y, G, L;
int main()
{
freopen("MINSUM.INP","r",stdin);
freopen("MINSUM.OUT","w",stdout);
cin >> G >> L;
long long T=G*L;
int can=sqrt(T);
for(int a=can; a>=G; a--)
{
if (T%a=0)
6


{
int b=T/a;
if(__cd(a,b)==G) {cout<}
}
cout << "-1";
return 0;
}
/* Ở cách làm thứ 2 này học sinh cũng chỉ ghi được 85% số điểm.
Lý do: mặc dù học sinh đã khai báo T kiểu long long nhưng trong biểu thức
T = G*L, biến G và biến L đều được khai báo kiểu int, kết quả của G*L kiểu int
dẫn đến hiện tượng giá trị G*L bị tràn trước khi giá trị được đẩy vào biến T*/
Cách 3:
#include <bits/stdc++.h>
using namespace std;
int x, y, G, L;

int main()
{
freopen("MINSUM.INP","r",stdin);
freopen("MINSUM.OUT","w",stdout);
cin >> G >> L;
long long T=(long long)G*L;
int can=sqrt(T);
for(int a=can; a>=G; a--)
{
if (T%a==0)
{
int b=T/a;
if(__gcd(a,b)==G) {cout<}
}
cout << "-1";
return 0;
}
/* Cách làm thứ 3 này học sinh ghi dc 100% số điểm, do đã giải quyết
được vấn đề tràn dữ liệu và thuật tốn có độ phức tạp 0(n) */
Kết quả điểm chấm của 3 cách trên như sau:
7


(Đề, test chấm, code bài làm của 3 cách trên được tơi ghi vào đĩa CD,
nộp kèm theo SKKN này)
Ví dụ 2: Đếm số chính phương
Số X là số chính phương khi X=a*a
(Nói cách khác giá trị là một số ngun)
Ví dụ: 9 là số chính phương vì =3

u cầu: Cho 2 số tự nhiên a, b (1 ≤ a ≤ b ≤ 10 18). Hãy đếm xem trong
đoạn từ a đến b có bao nhiêu số chính phương.
Dữ liệu vào: Trong file DEM_CP.inp gồm 1 dòng ghi 2 số a,b
Dữ liệu ra: Trong file DEM_CP.out gồm 1 số tìm được
Test mẫu:
DEM_CP.inp

DEM_CP.out

1 100

10

2 10000

99

Giải thích test ví dụ:
Test1: a=1, b= 100 có 10 số
Test 2: a= 2, b = 10000 có 99 số
- Có 20 test ứng với a- Có 10 test ứng với a- Có 10 test ứng với aNhận xét: vì đề bài cho a, b <=1018 nên nếu ta sử dụng 2 vịng lặp thơng
thường thì chắc chắn sẽ không ghi được điểm tối đa.
8


Do vậy ta sẽ phải tìm thuật tốn tối ưu hơn,
Ý tưởng Thuật tốn

Số lượng các số chính phương trong [a, b] = số lượng các số chính phương
trong [1;b] – số lượng các số chính phương [1; a-1] (a-1 là số nguyên < a)
Trong đó:
- Số lượng các số chính phương trong [1 ;b] là
- Số lượng các số chính phương trong [1 ;a-1] là
Do đó: Số lượng các số chính phương trong [a, b] = –[
Cài đặt
Cách 1:
#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main()
{
freopen("dem_cp.inp","r",stdin);
freopen("dem_cp.out","w", stdout);
cin >> a >> b;
cout<< trunc(sqrt(b))- trunc(sqrt(a-1));
return 0;
}
/* Với cách làm này học sinh ghi được 70-75% số điểm.
Lý do: trunc cho kết quả kiểu số thực, hiệu của hàm trunc và trunc cũng
cho kết quả kiểu thực nên output đưa ra cũng là số thực.*/
Cách 2:
#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main()
{
freopen("dem_cp.inp","r",stdin);
freopen("dem_cp.out","w", stdout);

cin >> a >> b;
long long m= trunc(sqrt(b))- trunc(sqrt(a-1));
cout<return 0;
}
9


/* Với cách làm này học sinh ghi được 100% số điểm.
Lý do: Mặc dù trunc cho kết quả kiểu số thực, hiệu của hàm trunc và trunc
cũng cho kết quả kiểu thực nhưng khi gán giá trị biểu thức trên cho biến m có
kiểu long long, rồi mới in ra m thì output đưa ra là số nguyên.*/
Cách 3:
#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main()
{
freopen("dem_cp.inp","r",stdin);
freopen("dem_cp.out","w", stdout);
cin >> a >> b;
cout<< (long long)trunc(sqrt(b))- (long long)trunc(sqrt(a-1));
return 0;
}
/* Với cách làm này học sinh ghi được 100% số điểm. Máy tính thực hiện
tính giá trị trunc(sqrt(b)) rồi ép kiểu từ kiểu số thực về kiểu long long, tính giá
trị trunc(sqrt(a-1)) rồi ép kiểu từ kiểu số thực về kiểu long long. Kết quả phép
tính trừ của biểu thức kiểu long long với kiểu long long cũng sẽ thuộc kiểu long
long.*/
Cách 4:

#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main()
{
freopen("dem_cp.inp","r",stdin);
freopen("dem_cp.out","w", stdout);
cin >> a >> b;
cout<< (long long) trunc(sqrt(b)) -trunc(sqrt(a-1);
return 0;
}
/* cách làm này học sinh đã thực hiện ép kiểu cho hàm trunc(sqrt(b)) từ
dạng số thực về dạng số nguyên kiểu long long. Tuy nhiên hàm trunc(sqrt(a-1))
vẫn trả về kiểu số thực. Kết quả hiệu của biểu thức trả về kiểu nguyên và biểu
10


thức trả về kiểu thực lại vẫn trả về kiểu số thực. Do vậy chương trình vẫn cho
kết quả sai, học sinh sẽ không ghi được điểm tuyệt đối với cách làm này. */
Cách 5
#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main()
{
freopen("dem_cp.inp","r",stdin);
freopen("dem_cp.out","w", stdout);
cin >> a >> b;
long long m= trunc(sqrt(b));
long long n= trunc(sqrt(a-1));

cout<< m-n;
return 0;
}
/* Với cách làm này học sinh được điểm tuyệt đối 100% . Về cơ bản cách 5
cũng gần như cách 3, nhưng việc tính trunc(sqrt(b)) và trunc(sqrt(a-1)) được
thực hiện lưu giá trị vào biến m, n trước rồi mới in hiệu m-n */
Dưới đây là kết quả chấm điểm các cách của bài tập ví dụ 2.

(Đề, test chấm, code bài làm của 5 cách trên được tôi ghi vào đĩa CD,
nộp kèm theo SKKN này)
Ví dụ 3: Phân số - Tên file hoặc FRACTION.CPP
Khi còn bé, các bạn học sinh học được cách trừ phân số bằng cách quy
đồng mẫu số, rồi mới thực hiện phép trừ.
Nhưng một lần, Bờm tính thử hiệu hai phân số bằng cách lấy hiệu hai tử
số và hiệu hai mẫu số và thấy thật ngạc nhiên là kết quả vẫn đúng.
11


Bờm thấy tính chất này thật kỳ diệu và Bờm muốn biết, với phân số cho
trước, có bao nhiêu cặp giá trị a ≥ 0 và m > 0 sao cho:
INPUT
•Một dịng chứa hai số ngun dương b và n cách nhau ít nhất một
dấu cách (1 ≤ b, n ≤ 106)
OUTPUT
•Ghi ra một số nguyên là số lượng cặp (a, m) thỏa mãn yêu cầu.
Ví dụ:
INPUT
OUTPUT
9 12
5

Giải thích ví dụ:
Có 5 cặp (a, m) thỏa mãn ứng với 5 phân số:
Ý tưởng thuật toán:
Ý tưởng: Sử dụng toán học để biến đổi đẳng thức ta được

anm – an2 – bm2 + bmn = amn – bmn
– an2 = bm2 - 2bmn
an2 = 2bmn – bm2
(1)
(2)
Do a, n, b, m là các số nguyên dương nên từ (2) ta rút ra 2n > m tức là:
1<=m<2n
Từ phân tích trên ta có thể xây dựng thuật tốn như sau:
Cho giá trị m chạy từ 1 đến 2n, với mỗi giá trị m thoả mãn m thì ta tìm số
nguyên a tương ứng thoả mãn (1). Số cặp (a,m) tìm được chính là output của bài
toán.
Cài đặt
Cách 1:
#include <bits/stdc++.h>
using namespace std;
const int mmax = 1e6;
int a,m,b,n;
int d = 0;
int main()
12


{
freopen("fraction.inp","r",stdin);
freopen("fraction.out","w",stdout);

cin >> b >> n;
for (int m = 1; m <= 2*n; m++)
if (((2*b*m*n - b*m*m) % (n*n) == 0) && (m!=n))
d++;
cout << d;
return 0;
}
/* Cách làm này học sinh chỉ ghi được 40-50% số điểm. Lý do 1<=b,
n<=106; theo phân tích ở trên m<=2.n tức m<=2. 10 6; Do đó 2bmn<=4.1018
(vượt q kích thước kiểu int) Do vậy dẫn đến hiện tượng tràn dữ liệu với các
test có dữ liệu input lớn. */
Cách 2
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mmax = 1e6;
int a,m,b,n;
int d = 0;
int main()
{
freopen("fraction.inp","r",stdin);
freopen("fraction.out","w",stdout);
cin >> b >> n;
for (int m = 1; m <= 2*n; m++)
if ((((ll)(b*m*n*2) - (ll)(b*m*m)) % (ll)(n*n)) == 0 && (m!=n))
d++;
cout << d;
return 0;
}
/* Cách này học sinh chỉ ghi được 40-50% tổng số điểm. Vì: ở cách làm

này mặc dù học sinh đã thực hiện ép kiểu cho biểu thức b*m*n*2 thành kiểu ll
(long long) nhưng trên thực tế khi biểu thức trên thực hiện tính nhân giá trị của
các số nguyên kiểu int với nhau thì kết quả vẫn là int, sau đó giá trị này mới
13


được mới ép sang kiểu ll, nghĩa là giá trị biểu thức đã bị tràn trước khi chuyển
sang kiểu dữ liệu mới. */
Cách 3:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mmax = 1e6;
int a,m,b,n;
int d = 0;
int main()
{
freopen("fraction.inp","r",stdin);
freopen("fraction.out","w",stdout);
cin >> b >> n;
for (int m = 1; m <= 2*n; m++)
if ((((ll)b*m*n*2 - (ll)b*m*m) % ((ll)n*n) == 0) && (m!=n))
d++;
cout << d;
return 0;
}
/*Cách làm này học sinh ghi điểm tuyệt đối 100% số điểm, code trên đã
khắc phục được hiện tượng tràn bộ nhớ trên dữ liệu. Trong biểu thức
(ll)b*m*n*2 đã thực hiện ép kiểu cho biến b từ kiểu int sang ll, kết quả phép
nhân của kiểu ll (biến b) với kiểu int (biến m, và n) sẽ cho ra kiểu ll. Tương tự

với các biểu thức (ll) b*m*n và ll(n)*n đều cho kết quả lưu vào kiểu dữ liệu ll */
Cách 4:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mmax = 1e6;
int a,m,b,n;
int d = 0;
int main()
{
freopen("fraction.inp","r",stdin);
freopen("fraction.out","w",stdout);
cin >> b >> n;
14


for (int m = 1; m <= 2*n; m++)
if ((((ll)b*m*n*2 - (ll)b*m*m) % (ll)n*n) == 0 && (m!=n))
d++;
cout << d;
return 0;
}
/* Cách làm này học sinh ko ghi được điểm, hoặc chỉ ghi được một vài test
(nếu chấm bằng các bộ test khác nhau)
Lý do: Điểm sai ở code này nằm ở việc đặt cặp ngoặc (và) không đúng.
Trong biểu thức (((ll)b*m*n*2 - (ll)b*m*m) % (ll)n*n) == 0 thì phép % sẽ đươc
ưu tiên thực hiện trước phép nhân của giữa n*n, nghĩa là máy tính sẽ thực hiện
phép tính chia lấy dư của biểu thức 2bmn-bmm cho n trước, kết quả này sau đó
mới được nhân với n. Do đó chương trình sẽ cho kết quả sai.*/
Dưới đây là kết quả chấm điểm các cách của bài tập ví dụ 3.


(Đề, test chấm, code bài làm của 4 cách trên được tôi ghi vào đĩa CD,
nộp kèm theo SKKN này)
2.3.2 Bài tập vận dụng
Bài tập 1: DÂY XÍCH (Đề thi HSG Tỉnh Thanh Hoá năm 2013- 2014)
Người ta dùng dây thép trịn có đường kính thiết diện ngang là d làm n
vịng trịn, bán kính vịng trịn trong là r, móc nối với nhau thành một dây xích,
mỗi vịng trịn là một mắt xích. Nếu dây xích có nhiều hơn một mắt xích thì tồn
tại hai vịng trịn mà mỗi vòng chỉ nối với đúng với một vòng tròn khác, đó là
các mắt xích đầu và cuối. Cầm 2 mắt xích đầu và cuối, kéo căng ra, ta có dây
xích độ dài L.
Yêu cầu: Cho d, r và n. Hãy tính độ dài L của dây xích.
d

r

L

15


Dữ liệu vào: Vào từ file văn bản BAI1.INP gồm một dòng chứa 3 số
nguyên dương d, r và n (d < r ≤ 100; n ≤ 109).
Kết quả: Ghi ra file văn bản BAI1.OUT một số nguyên là độ dài L tìm
được.
Ví dụ:
BAI1.
INP
2 10 3


BAI1.
OUT
64

Ý tưởng thuật tốn:
- Vẽ hình ra thì ta dễ dàng tìm được cơng thức L = 2*d + 2*r*n. (1)
- Có rất nhiều học sinh tìm ra được cơng thức này nhưng lại khơng ăn được
100% điểm của bài này vì lý do bị tràn số hoặc bị quá thời gian. Ta phải có nhận
xét như sau: do độ lớn của dữ liệu đầu vào: các biến d, r, n ta có thể khai báo
kiểu int, nhưng Lmax = 2*99 + 2*100*109 = 2*99 + 2*1011. (vượt quá kích thước
kiểu int) do vậy biến L cần phải được khai báo kiểu long long, đồng thời phải ép
kiểu khi thực hiện tính giá trị biểu thức (1)
- Độ phức tạp của bài toán là O(1).
Bài tập 2: TÌM GIÁ TRỊ DƯƠNG LỚN NHẤT
Cho biểu thức T = 1 +1/3 +1/5+…+1/(2N-1) và số thực x. Tìm số nguyên
dương N lớn nhất thỏa mãn TDữ liệu: (MaxPositive.inp)
- Dòng đầu tiên chứa số nguyên T cho biết số bộ dữ liệu, tiếp theo là K bộ
dữ liệu, mỗi bộ dữ liệu gồm gồm một dòng chứa một số thực x
Ràng buộc: 1 <=K<=100 ; 1.0 < x<=10.0
Kết quả: (MaxPositive.out) gồm T dòng, mỗi dòng ứng với mỗi bộ dữ
liệu vào chứa một số nguyên N tìm được theo u cầu
Ví dụ
MaxPositive.out
MaxPositive.out
1
3
1.6
Ý tưởng thuật tốn:
Bài tập này thuộc dạng bài lặp với số lần chưa biết trước. Ta khởi tạo giá

trị N =1; thực hiện vịng lặp để tính giá trị T theo công thức T= T+2/(2N-1) ;mỗi
lần thực hiện vịng lặp thì N tăng 1 đơn vị, thuật tốn dừng lại khi T>=x. Sau đó
16


in ra giá trị N-1 (vì khi ra khỏi vịng lặp N là giá trị nhỏ nhất để T>=x, nên N-1
là giá trị lớn nhất để TLưu ý: biến T được khai báo kiểu thực và cần ép kiểu về kiểu thực khi
thực hiện phép tính chia 2 số nguyên.
cụ thể: float T= T+(float) 1/(2*N-1);
Bài tập 3: TÍCH NHỎ NHẤT
Bạn được cung cấp bốn số nguyên a, b, x và y. Ban đầu, a ≥ x và b ≥ y.
Bạn có thể thực hiện thao tác sau khơng q n lần:
- Chọn a hoặc b và giảm nó đi 1 đơn vị. Tuy nhiên, do kết quả của phép
toán này, giá trị của a không được nhỏ hơn x và giá trị của b không được nhỏ
hơn y.
Nhiệm vụ của bạn là tìm tích nhỏ nhất của a và b (a⋅b) mà bạn có thể đạt
được bằng cách áp dụng phép tốn đã cho khơng q n lần.
Bạn phải trả lời t trường hợp thử nghiệm độc lập.
Input: MINPRO.INP
- Dòng đầu tiên của đầu vào chứa một số nguyên t (1≤ t ≤ 2*10 4) - số lượng
trường hợp thử nghiệm. Sau đó, t các trường hợp thử nghiệm theo sau.
- Dòng duy nhất của test case chứa năm số nguyên a, b, x, y và n (1≤ a, b,
x, y, n ≤ 109). Ràng buộc bổ sung đối với đầu vào: a ≥ x và b ≥ y.
Output: MINPRO.OUT
- Gồm t dòng, mỗi dòng chứa một cặp a, b thỏa mãn yêu cầu đề bài
Ví dụ:
MINPRO
7
10 10 8 5 3

12 8 8 7 2
12343 43 4543 39 123212
1000000000 1000000000 1 1 1
1000000000 1000000000 1 1 1000000000
10 11 2 1 5
10 11 9 1 10

MINPRO
70
77
177177
999999999000000000
999999999
55
10

Giải thích ví dụ:
- Test 1: thay đổi số thứ hai thành 7: kết quả là 10 * 7 = 70
- Test 2: thay đổi số thứ nhất thành 11, số thứ hai thành 7: kết quả 11*7=77
ý tưởng thuật tốn:
- tích (a*b)min thì
- Để tích (a*b)min ta sẽ cố gắng tận dụng hết n phép biến đổi để giảm a, b
17


- Trong trường hợp nếu không sử dụng hết n phép biến đổi ta đưa a về x, b về y
(vì a>=x; b>=y)
Khi đó (a*b)min = x*y
Cụ thể thuật tốn như sau:
TH1: nếu (a-x)+(b-y) <=n thì in ra x*y

Tức là đã biến đổi được a về x, b về y rồi mà chưa hết phép biến đổi thì
(a*b)min = amin*bmin = x*y
TH2: Ngược lại:
TH2.1: ban đầu nếu cứ giảm a dần dần, nếu a về được x mà chưa hết phép biến
đổi thì ta tiếp tục giảm b về y cho hết n phép biến đổi thì dừng. Lưu lại giá trị
(a*b)min còn nếu giảm a về x mà hết phép biến đổi thì khơng cần giảm b nữa
TH2.2: Tương tự giảm b như trên (chỉ cần tráo đổi giá trị a cho b để làm lại công
việc trên), lưu lại giá trị (a*b)min
Lấy min của 2 trường hợp TH2.1 và TH 2.2 trên ta được output cần tìm.
(Đề, test chấm, code tham khảo của các bài tập vận dụng trên được tôi ghi
vào đĩa CD, nộp kèm theo SKKN này)
2.4 Hiệu quả của sáng kiến của kinh nghiệm
Khi thực hiện áp dụng đề tài SKKN vào công tác bồi dưỡng học sinh giỏi
môn Tin học năm học 2021- 2022, tôi nhận thấy:
Đối với học sinh: Các em đã thực sự hiểu rõ tác dụng của việc ép kiểu, khi
nào cần ép kiểu và cách thức thực hiện trong khi cài đặt chương trình. Bên cạnh
đó các em cũng chú trọng nhiều hơn đến việc để ý các giới hạn dữ liệu đề cho,
định hướng thuật toán, lựa chọn khai báo dữ liệu để phù hợp với từng bài tập cụ
thể, rèn luyện tư duy lập trình, chất lượng đội tuyển học sinh giỏi cũng từ đó
được nâng cao hơn.
Năm học 2021-2022 tôi trực tiếp đứng đội tuyển bồi dưỡng học sinh giỏi
môn Tin học cho nhà trường. Kết quả nhà trường đã có 2 học sinh đạt giải ba
mơn Tin trong kì thi học sinh cấp Tỉnh (Xếp thứ hạng 14/90 trường)
Cụ thể:
STT
1
2

Họ tên
Điểm thi

Xếp giải
Đỗ Sơn Hải
14.5
Ba
Phạm Trung Kiên
12.5
Ba
Kết quả trên tuy chưa cao nhưng đó là kết quả của sự nổ lực, cố gắng
không ngừng nghỉ của cơ trị chúng tơi trong suốt thời gian qua. Kết quả trên
cũng đã cho thấy việc giúp học sinh rèn luyện tư duy thuật tốn thơng qua việc
hướng dẫn học sinh phân tích các cách làm khác nhau của bài tập ép kiểu nói
riêng, các bài tập lập trình nói chung đã mang lại những hiệu quả nhất định.

18


3. KẾT LUẬN VÀ KIẾN NGHỊ
3.1 Kết luận
Đề tài “Rèn luyện tư duy lập trình cho học sinh khá giỏi thơng qua việc
phân tích đánh giá nhiều cách giải khác nhau của một số bài tập Tin học có
sử dụng kĩ thuật ép kiểu trong ngơn ngữ lập trình C++” đã thơng qua 3 bài
tập ví dụ cụ thể để chỉ ra một số cách cài đặt chương trình với cùng thuật tốn,
trong đó việc ép kiểu khác nhau dẫn đến những kết quả khác nhau. Đề tài giúp
học sinh hiểu rõ cách thức thực hiện ép kiểu từ đó lựa chọn cách ép kiểu cho phù
hợp với từng bài tập cụ thể. Đề tài cũng đã đưa thêm một số bài tập vận dụng, có
kèm bộ test chấm để học sinh có thể tự luyện tập thêm.
Đề tài có thể được ứng dụng rộng rãi trong công tác bồi dưỡng học sinh
giỏi môn Tin học ở các nhà trường THPT, THCS và tôi hi vọng đề tài sẽ đem lại
những hiệu quả nhất định.
3.2 Kiến nghị

Đối với đồng nghiệp: Cần tích cực sưu tầm đề thi, trên tinh thần tự học,
tự sáng tạo. Đồng thời trao đổi chuyên môn, nâng cao trình độ chun mơn,
Đối với nhà trường: Tổ chức các cuộc thi học sinh giỏi, giao lưu với các
trường trong huyện để học sinh có điều kiện va chạm trong các kì thi, đồng thời
cũng tạo điều kiện để giáo viên các nhà trường có cơ hội được giao lưu học hỏi
với nhau
Đối với Sở Giáo Dục -Đào tạo: Tơi mong muốn sau mỗi kì thi học sinh
giỏi cấp tỉnh diễn ra, Ban tổ chức cuộc thi sẽ công khai đáp án bao gồm bộ Test
chấm sử dụng cho kì thi và code tham khảo để giáo viên có nguồn tư liệu bổ ích,
dạy học cho các năm học sau.
XÁC NHẬN CỦA THỦ TRƯỞNG ĐƠN Thanh Hóa, ngày 20 tháng 4 năm 2022
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.
Nguyễn Thị Dung

19



×