Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Calexp (Tin học trẻ toàn quốc 2024 – Vòng sơ khảo – Bảng B)

Calexp (Tin học trẻ toàn quốc 2024 – Vòng sơ khảo – Bảng B)
Nguồn: Tin học trẻ 2024 Độ khó: Dễ Toán Biểu thức Nhập xuất

Đề bài

Nguồn đề: Hội thi Tin học trẻ toàn quốc 2024 – Vòng sơ khảo – Bảng B (bài calexp, viết tắt của calculate expression – tính biểu thức). Đề bên dưới do mình viết lại bằng lời của mình cho dễ hiểu (giữ đúng nghĩa, đúng số liệu và ví dụ của đề gốc).

Cho biểu thức

$$A = x^2 + y^2 + x \times y.$$

Biết giá trị của hai số nguyên $x$ và $y$, em hãy tính giá trị của $A$.

Dữ liệu vào:

  • Dòng thứ nhất: số nguyên $X$ ($0 \le X \le 100$) – giá trị của $x$;
  • Dòng thứ hai: số nguyên $Y$ ($0 \le Y \le 100$) – giá trị của $y$.

Kết quả: in ra một số nguyên duy nhất là giá trị của $A$.

Giới hạn thời gian: 1 giây. Dữ liệu nhập từ bàn phím, in ra màn hình.

Ví dụ – input
1
2
Ví dụ – output
7

Giải thích: $x = 1$, $y = 2$ nên $A = 1^2 + 2^2 + 1 \times 2 = 1 + 4 + 2 = 7$.

Lời mở đầu

Đây là bài "khởi động" của đề sơ khảo Bảng B Tin học trẻ 2024: đọc hai số, thay vào công thức và in kết quả. Bài dễ nhưng vẫn có vài điểm các em mới học hay vấp: đọc dữ liệu nằm trên hai dòng, viết $x^2$ trong C++ thế nào (đừng viết x^2!), và có bị tràn số không. Mình đi lần lượt từng ý, kèm một hình vẽ nhỏ để thấy biểu thức này "trông như thế nào".

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test có gì?

Bộ test trên CLUE do mình tự sinh theo đúng giới hạn của đề: 20 test, test 1 là ví dụ của đề. Đề không chia subtask (cả bài là một nhóm 100%).

Nhóm testTestNội dung
Ví dụ của đề1$x = 1, y = 2$, đáp án $7$
Biên2–10$x = y = 0$ (đáp án $0$), $x = y = 100$ (đáp án lớn nhất $30000$), một số bằng $0$, hai số bằng nhau, $99$ và $100$…
Ngẫu nhiên11–20$x, y$ ngẫu nhiên trong đoạn $[0, 100]$

Có 5 test có ít nhất một số bằng $0$ (test 2, 4–5, 7, 10), trong đó test 2 cả hai số đều bằng $0$. Đáp án lớn nhất là $30.000$ (test 3, $x = y = 100$).

Đã nộp thử trên bộ chấm chính thức
Lời giải bên dưới mình đã nộp lên hệ thống chấm chính thức của Hội thi (tinhoctre.vn) và được 100/100 điểm (AC).
SubtaskĐiều kiệnTestCách làm
C++
Cách làm
Python
Duy nhất (100%)$0 \le X, Y \le 100$1–2020/2020/20

Ý tưởngBiểu thức nhìn bằng hình vuông

Cách làm thì chỉ là thay số vào công thức. Nhưng có một cách nhìn rất đẹp: vẽ hình vuông cạnh $x + y$. Diện tích của nó là $(x + y)^2 = x^2 + y^2 + 2xy$ – gồm hình vuông $x \times x$, hình vuông $y \times y$ và hai hình chữ nhật $x \times y$. Biểu thức $A$ chỉ lấy một hình chữ nhật, nên

$$A = x^2 + y^2 + xy = (x + y)^2 - xy.$$

Em nhập $x, y$ nhỏ (không quá 12) rồi bấm Tiếp để đếm từng phần (mỗi ô vuông nhỏ là $1$ đơn vị diện tích):

Ví dụ nhanh:

Với ví dụ $x = 1, y = 2$: $(1 + 2)^2 - 1 \cdot 2 = 9 - 2 = 7$ – đúng như đáp án. Công thức $(x+y)^2 - xy$ chỉ là để hiểu thêm; trong code mình tính thẳng theo đề cho dễ đọc.

Lời giảiTính thẳng theo công thức – O(1)

Vài lưu ý khi viết code:

  • Bình phương: trong C++ và Python, x^2 là phép XOR bit chứ không phải lũy thừa! Hãy viết x * x. (Python có x ** 2; C++ có pow(x, 2) nhưng hàm này trả về số thực double – không nên dùng cho số nguyên.)
  • Hai dòng: cin >> x >> y đọc được cả khi hai số nằm trên hai dòng (C++ bỏ qua dấu xuống dòng). Trong Python thì mỗi lệnh input() đọc một dòng, nên gọi hai lần.
  • Tràn số: lớn nhất $A = 100^2 + 100^2 + 100 \cdot 100$ $= 30000$ – kiểu int là đủ. Mình vẫn dùng long long theo thói quen an toàn, không tốn gì cả.
C++▶ Chạy tay từng dòng codeĐọc x, y rồi tính A = x*x + y*y + x*y.
Dữ liệu mẫu:
Không gian$O(1)$
Thời gian$O(1)$

Kết quả chạy thật: Bản C++ đúng 20/20 test, chạy lâu nhất 0.003s. Bản Python đúng 20/20 test, chạy lâu nhất 0.021s.

Tổng kếtKết quả chạy thật trên bộ test

CáchNgôn ngữĐúngLâu nhất
(test đúng)
Điểm theo subtask
Tính thẳng theo công thứcC++20/200.003s100%
Python20/200.021s100%
Ghi chú của mình: thời gian là thời gian CPU đo trên máy mình (giới hạn 1 giây). Bài dễ nhưng là dịp ôn lại ba thói quen tốt: (1) viết bình phương bằng phép nhân x * x, không dùng ^; (2) luôn ước lượng giá trị lớn nhất của đáp án để chọn kiểu dữ liệu; (3) thử lại với các trường hợp biên như $x = y = 0$ và $x = y = 100$ trước khi nộp.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Vận tốc rơi tự do

Vận tốc rơi tự do
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Số thực Công thức

Đề bài

Một vật được thả rơi tự do từ độ cao $h$ mét (bỏ qua sức cản không khí). Theo vật lý, vận tốc của vật khi chạm đất là

$$v = \sqrt{2 g h} \ \text{(m/s)},$$

với gia tốc rơi tự do $g = 9{,}8$ m/s².

hv = 0v = ?v = √(2gh)g = 9,8 m/s²
Hình minh hoạ: vật rơi từ độ cao $h$, vận tốc tăng dần và lớn nhất khi chạm đất.

Cho độ cao $h$, hãy tính vận tốc $v$ khi vật chạm đất.

Dữ liệu vào: Một dòng chứa số thực $h$ — độ cao thả vật (đơn vị mét).

Kết quả: In ra $v$, làm tròn đến 5 chữ số thập phân.

Giới hạn: $1 \le h \le 10^9$.

Subtask:

  • Subtask 1 (40% số điểm): $h$ là số nguyên và $h \le 100$.
  • Subtask 2 (60% số điểm): không có ràng buộc gì thêm ($h$ có thể là số thực, rất lớn).

Bài được chấm với sai số tuyệt đối không quá $10^{-4}$.

Giới hạn thời gian: 1 giây, bộ nhớ: 256 MB. Dữ liệu nhập từ bàn phím và in ra màn hình.

Ví dụ 1 – input
2
Ví dụ 1 – output
6.26099
Ví dụ 2 – input
20
Ví dụ 2 – output
19.79899

Giải thích: Ví dụ 1: $v = \sqrt{2 \cdot 9{,}8 \cdot 2}$ $= \sqrt{39{,}2}$ $\approx 6{,}260990\ldots$, làm tròn 5 chữ số thập phân được 6.26099.

Nguồn: phỏng theo bài "VẬN TỐC" – vinhdinhcoder.net.

Lời mở đầu

Bài này chỉ cần một công thức có sẵn trong đề, nên ai cũng nghĩ là "ăn chắc". Nhưng đây lại là bài rất hay để luyện ba thói quen của bài số thực: đọc đúng kiểu dữ liệu (đề ghi "số thực" thì đừng dùng int), in đúng số chữ số thập phân, và chọn double thay cho float. Mình sẽ cho các bạn xem từng cái bẫy, mỗi bẫy đều có code thật và kết quả chạy thật trên bộ test của bài.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test chính thức có gì?

Bộ test có 20 test, mỗi test chỉ một số $h$. Mình đã kiểm tra: test 1–8 đúng là $h$ nguyên, $h \le 100$ (subtask 1); test 9–20 là subtask 2.

NhómTest$h$ nhỏ nhất$h$ lớn nhất$h$ nguyên / $h$ thựcSố chữ số
sau dấu chấm
$v$ lớn nhất
Subtask 11–811008 / 0≤ 044,272
Subtask 29–201,0011 000 000 0002 / 10≤ 3140 000
  • Subtask 2 có 10 test $h$ là số thực (test 10–13, 15–20), trong đó có cả những test $h$ nhỏ mà phần thập phân chiếm tỉ lệ đáng kể – đây là các test "bắt" bạn nào đọc $h$ bằng số nguyên.
  • $h$ lớn nhất là $10^9$ nên $v$ lớn nhất là $\sqrt{2 \cdot 9{,}8 \cdot 10^9} = 140\,000$ m/s – số có tới 6 chữ số phần nguyên, cộng 5 chữ số thập phân là 11 chữ số có nghĩa.
  • Bài chấm với sai số tuyệt đối $10^{-4}$: đáp án $q$, em in $p$ thì được chấp nhận khi $|p - q| \le 10^{-4}$, dù $v$ lớn tới $140\,000$. Nghĩa là với $v$ lớn em vẫn phải đúng tới chữ số thập phân thứ 4 – tức khoảng 10 chữ số có nghĩa. Mình chấm thử đúng theo luật này.

Kết quả chạy thật của các cách viết bên dưới theo từng subtask (chấm như CLUE, sai số tuyệt đối $10^{-4}$):

SubtaskTestBẫy 1
int h
Bẫy 2
quên định dạng
Bẫy 3
float
Lời giải
double
Subtask 1 (40%)1–88/88/88/88/8
Subtask 2 (60%)9–205/123/124/1212/12

Ý tưởngChỉ cần thay số vào công thức

Đề đã cho sẵn công thức $v = \sqrt{2gh}$, việc của mình chỉ là: đọc $h$, tính $2 \cdot 9{,}8 \cdot h$, lấy căn bậc hai (hàm sqrt) rồi in ra 5 chữ số thập phân. Nếu tò mò công thức từ đâu ra: vật rơi tự do sau $t$ giây đã rơi được quãng $\frac{1}{2} g t^2$ và có vận tốc $g t$. Chạm đất khi $\frac{1}{2} g t^2 = h$, tức $t = \sqrt{2h/g}$, khi đó $v = g t = \sqrt{2gh}$.

Em nhập độ cao $h$ rồi bấm Tiếp để xem vật rơi qua từng khoảng thời gian bằng nhau (các vị trí mờ là vị trí cũ – càng về sau vật càng rơi nhanh, khoảng cách giữa hai vị trí càng xa):

Ví dụ nhanh:

Bẫy 1Đọc h bằng số nguyên

Subtask 1 toàn là số nguyên, ví dụ trong đề cũng là số nguyên ($2$ và $20$), nên rất nhiều bạn quen tay khai báo int h:

C++▶ Chạy tay từng dòng codeBẫy 1: đọc h bằng int – thử với h = 2.5 để thấy phần thập phân bị bỏ lại.
Dữ liệu mẫu:

Khi gặp 2.5, lệnh cin >> h với h kiểu int chỉ đọc được phần 2 rồi dừng ở dấu chấm. Chương trình không báo lỗi gì cả, nó lặng lẽ tính $\sqrt{2 \cdot 9{,}8 \cdot 2}$ – sai hoàn toàn. Em bấm dữ liệu mẫu h = 2.5 trong khung chạy tay ở trên để thấy rõ phần .5 bị bỏ lại.

Kết quả chạy thật: bản C++ đúng 13/20 test, sai ở test 11–13, 15–17, 20; test đúng chạy lâu nhất 0.002s.

  • Trong 10 test $h$ thực của subtask 2, chỉ 3 test "may mắn" qua: $h$ rất lớn mà phần thập phân bị mất quá nhỏ, $v$ chỉ lệch chưa tới $10^{-4}$. Các test còn lại đều sai → mất trọn 60% của subtask 2.
  • Bản Python h = int(input()) còn tệ hơn: gặp xâu có dấu chấm như "2.5" thì báo lỗi ValueError và dừng luôn. Kết quả bản Python đúng 10/20 test, lỗi chạy (RE) ở test 10–13, 15–20; test đúng chạy lâu nhất 0.016s.

Bản Python mắc bẫy:

import math
h = int(input())                # BẪY: h có thể là số thực như 1.5
v = math.sqrt(2 * 9.8 * h)
print(f"{v:.5f}")

Bẫy 2 & 3In sai định dạng và dùng float

Bẫy 2: quên định dạng khi in

Đề yêu cầu in 5 chữ số thập phân. Nếu chỉ viết cout << v; thì C++ mặc định in 6 chữ số có nghĩa (không phải 6 chữ số sau dấu phẩy!): $v = 19{,}798989\ldots$ (ví dụ 2) sẽ in ra 19.799, còn với $h = 12345678{,}9$ thì $v = 15555{,}555485\ldots$ in ra 15555.6. Python thì ngược lại: print(v) in tới 15–17 chữ số.

Chỉ khác lời giải đúng ở dòng in kết quả:

cout << v;                      // C++: mặc định 6 chữ số có nghĩa
print(v)                        # Python: in tới 15-17 chữ số

Kết quả chạy thật:

  • Bản C++ đúng 11/20 test, sai ở test 10, 12, 14–20; test đúng chạy lâu nhất 0.002s. Với $v$ nhỏ (dưới 100) thì 6 chữ số có nghĩa đã gồm 4 chữ số thập phân nên vẫn qua; nhưng $v$ càng lớn càng ít chữ số thập phân được in, sai lệch vượt $10^{-4}$ → mất subtask 2.
  • Bản Python đúng 20/20 test; test đúng chạy lâu nhất 0.019s – in thừa chữ số thì vẫn nằm trong sai số cho phép. Nhưng nếu đề chấm khớp từng ký tự (rất nhiều đề "làm tròn đến k chữ số" chấm như vậy) thì đoạn code này đúng 0/20 test, còn bản C++ đúng 4/20 test.
Thói quen tốt
Đề nói "làm tròn đến $k$ chữ số thập phân" thì luôn in đúng $k$ chữ số: C++ dùng cout << fixed << setprecision(k) hoặc printf("%.kf", v); Python dùng f"{v:.kf}" hoặc "%.kf" % v. Đừng trông chờ vào việc OJ chấm sai số.

Bẫy 3: dùng float thay cho double

float chỉ giữ được khoảng 7 chữ số có nghĩa, trong khi $v$ lớn nhất có tới 11 chữ số có nghĩa (6 chữ số phần nguyên + 5 chữ số thập phân). Vậy các chữ số cuối mà float in ra là "rác":

Chỉ khác lời giải đúng ở kiểu dữ liệu:

float h;                        // float: chỉ khoảng 7 chữ số có nghĩa
cin >> h;
float v = sqrt(2 * 9.8 * h);
cout << fixed << setprecision(5) << v;

Kết quả chạy thật: bản C++ đúng 12/20 test, sai ở test 10, 12, 14, 16–20; test đúng chạy lâu nhất 0.002s → mất subtask 2 (các test $v$ nhỏ vẫn qua vì 7 chữ số có nghĩa đã đủ cho 4 chữ số thập phân). Ví dụ với $h = 765432109{,}8$: float in 122484.57031, còn double in đúng 122484.56781 – sai từ chữ số thập phân thứ 2. double giữ được khoảng 15–16 chữ số có nghĩa nên thoải mái cho bài này.

Lời giảidouble + in đúng 5 chữ số

Gom cả ba bài học lại: đọc $h$ bằng double, tính $v = \sqrt{2gh}$ bằng double, in với fixed << setprecision(5).

C++▶ Chạy tay từng dòng codeLời giải: đọc và tính bằng double, in đúng 5 chữ số thập phân.
Dữ liệu mẫu:
  1. fixed nghĩa là "in dạng số thập phân bình thường, không dùng dạng mũ 1.4e+05", còn setprecision(5) đi kèm fixed nghĩa là 5 chữ số sau dấu chấm (máy tự làm tròn). Hai lệnh này nằm trong thư viện <iomanip> (đã có trong bits/stdc++.h).
  2. Viết 2 * g * h với g = 9.8 kiểu double: phép nhân được tính bằng số thực. Đừng viết int g = 9.8 (bị cắt còn 9) hay 2 * 98 / 10 * h (chia nguyên $196 / 10 = 19$)!
  3. Python: float của Python chính là double của C++, nên chỉ cần float(input()) và in bằng f"{v:.5f}".
Không gian$O(1)$
Thời gian$O(1)$

Kết quả chạy thật: Bản C++ đúng 20/20 test; test đúng chạy lâu nhất 0.004s. Bản Python đúng 20/20 test; test đúng chạy lâu nhất 0.021s. Full điểm, và đúng cả khi chấm khớp từng ký tự (C++ 20/20).

Tổng kếtKết quả chạy thật trên bộ test

Cách viếtNgôn ngữChấm như CLUE
(sai số tuyệt đối $10^{-4}$)
Chấm khớp
từng ký tự
Lâu nhất
Bẫy 1: đọc h bằng số nguyênC++13/2010/200.002s
Python10/20–0.016s
Bẫy 2: quên định dạng khi inC++11/204/200.002s
Python20/200/200.019s
Bẫy 3: dùng floatC++12/2011/200.002s
Lời giải: double + 5 chữ sốC++20/2020/200.004s
Python20/20–0.021s
Ghi chú của mình: tất cả các cách đều chạy tức thì (thời gian là thời gian CPU đo trên máy mình, giới hạn 1 giây). Khác biệt chỉ nằm ở tính đúng, và vì CLUE chấm sai số tuyệt đối $10^{-4}$ nên cả ba bẫy đều mất subtask 2: đọc $h$ bằng int (C++ sai 7 test, Python lỗi 10 test), quên định dạng ở C++ (sai 9 test), dùng float (sai 8 test). Riêng Python print(v) in thừa chữ số nên qua được checker này, nhưng sang đề chấm khớp từng ký tự sẽ mất điểm ngay – cột thứ tư của bảng cho thấy rõ. Ba thói quen cho mọi bài số thực: đọc bằng double, tính bằng double, in đúng số chữ số đề yêu cầu.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇