1. Trang chủ
  2. » Giáo Dục - Đào Tạo

(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++

19 46 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 19
Dung lượng 402,42 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Ngoà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 quá 1 giây/1 test thì học sinh còn cần phải chú ý về kiểu dữ liệ

Trang 1

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 khoá 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 yêu cầu xử lí các

bộ dữ liệu có phạm vi khá lớn Ngoà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 quá 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 toá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

Trang 2

báo biến, cài đặt thuật toá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 quá trình nghiên cứu và hoà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

Trang 3

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 chuyên 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 khoá 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 quá 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 toá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++ … đến lưu vào b trích dẫn từ nguồn: https://viettuts.vn/lap-trinh-cpp/ep-kieu-trong-cpp

Trang 4

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 toá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 toán ví dụ

Bài toá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ụ:

Trang 5

MINSUM.IN P

MINSUM.OU T

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 toá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 toán

Cài đặt

Cách 1:

#include <bits/stdc++.h>

using namespace std;

int x, y, G, L;

int main()

{

Trang 6

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<<a+b; return 0;}

}

}

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 <=10 18 (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 >10 9 )

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)

Trang 7

{

int b=T/a;

if( cd(a,b)==G) {cout<<a+b; return 0;}

}

}

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<<a+b; return 0;}

}

}

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 toán có độ phức tạp 0(n) */

Kết quả điểm chấm của 3 cách trên như sau:

Trang 8

(Đề, 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ố nguyên)

Ví dụ: 9 là số chính phương vì =3

Yêu cầu: Cho 2 số tự nhiên a, b (1 ≤ a ≤ b ≤ 1018) 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:

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<b≤10 6

- Có 10 test ứng với a<b≤10 9

- Có 10 test ứng với a<b≤10 18

Nhậ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

Trang 9

Do vậy ta sẽ phải tìm thuật toán tối ưu hơn,

Ý tưởng Thuật toá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<<m;

return 0;

}

Trang 10

/* 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

Trang 11

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

Trang 12

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ố nguyên 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ụ:

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 toá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()

Ngày đăng: 06/06/2022, 10:21

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w