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

Diện tích hình tròn

Diện tích hình tròn
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Hình học Làm tròn

Đề bài

Cho hình tròn có bán kính $R$ là số nguyên dương. Hãy tính diện tích hình tròn $S = \pi R^2$ rồi làm tròn đến số nguyên gần nhất.

(Dùng giá trị $\pi$ chính xác của máy, ví dụ acos(-1) trong C++ hoặc math.pi trong Python; không dùng $3{,}14$.)

Dữ liệu vào: Một dòng chứa số nguyên dương $R$.

Kết quả: In ra diện tích hình tròn sau khi làm tròn đến số nguyên gần nhất (in dạng số nguyên, không có phần thập phân).

Giới hạn:

  • $1 \le R \le 10^6$

Subtask:

  • Subtask 1 (40% số điểm): $R \le 1000$.
  • Subtask 2 (60% số điểm): không có ràng buộc gì thêm.

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
3
Ví dụ 1 – output
28
Ví dụ 2 – input
1
Ví dụ 2 – output
3

Giải thích: Ví dụ 1: $\pi \cdot 3^2 \approx 28{,}274$, làm tròn được $28$.

Ví dụ 2: $\pi \cdot 1^2 \approx 3{,}1416$, làm tròn được $3$.

Nguồn: phỏng theo bài "DIỆN TÍCH HÌNH TRÒN" – vinhdinhcoder.net.

Lời mở đầu

Công thức $S = \pi R^2$ thì ai cũng thuộc. Cái làm bài này "có chuyện" là yêu cầu làm tròn đến số nguyên gần nhất và đáp án có thể lên tới hơn 3 nghìn tỉ. Hai ví dụ của đề đều là trường hợp làm tròn xuống và số nhỏ, nên code sai vẫn chạy đúng ví dụ – rất dễ chủ quan. Mình sẽ đi qua 3 cái bẫy hay gặp nhất, mỗi bẫy có code và kết quả chấm thật, rồi mới tới code chuẩn.

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 là một số $R$. Mình đã kiểm tra: test 1–8 thoả subtask 1 ($R \le 1000$), test 9–20 là subtask 2. Đáp án được tính bằng số học chính xác.

NhómTest$R$ lớn nhấtĐáp án lớn nhấtSố test phải làm tròn lênSố test đáp án $> 2^{31}-1$Số test đáp án $\ge 10^6$
Subtask 11–81.0003.141.593502
Subtask 29–201.000.0003.141.592.653.59091011
  • Có 14/20 test mà phần lẻ của $S$ từ $0{,}5$ trở lên, tức là phải làm tròn lên (test 4–12, 15–17, 19, 20) – chính là các test "bắt" lỗi ép kiểu cắt bớt. Cả hai ví dụ của đề lại đều làm tròn xuống!
  • Có 10 test đáp án vượt $2^{31} - 1 = 2\,147\,483\,647$ (test 9–15, 17–19) – kiểu int không chứa nổi. Đáp án lớn nhất là $\pi \cdot 10^{12} \approx 3{,}14 \cdot 10^{12}$, phải dùng long long.
  • Có 13 test đáp án từ $10^6$ trở lên (test 4, 8–15, 17–20) – cout một số thực lớn như vậy sẽ in ra dạng 3.14159e+06.

Kết quả chạy thật của các code trong bài theo từng subtask:

SubtaskTestBẫy 1
ép kiểu cắt
Bẫy 2
int ans
Bẫy 3
cout<<round
Code
chuẩn
Subtask 1 (40%)
$R \le 1000$
1–83/88/86/88/8
Subtask 2 (60%)
đầy đủ: $R \le 10^6$
9–203/122/121/1212/12
Điểm0%40%0%100%

Ý tưởngLàm tròn là gì?

$S$ là số thực, nằm giữa hai số nguyên liên tiếp $\lfloor S \rfloor$ và $\lfloor S \rfloor + 1$. Làm tròn đến số nguyên gần nhất nghĩa là chọn số nào gần $S$ hơn:

  • phần lẻ $< 0{,}5$ → lấy $\lfloor S \rfloor$ (làm tròn xuống), ví dụ $28{,}27 \to 28$;
  • phần lẻ $\ge 0{,}5$ → lấy $\lfloor S \rfloor + 1$ (làm tròn lên), ví dụ $153{,}94 \to 154$.

Còn ép kiểu (long long)S hay int(S) chỉ đơn giản là cắt bỏ phần lẻ – luôn ra $\lfloor S \rfloor$ (với $S > 0$). Đó là "làm tròn xuống", không phải "làm tròn".

Em nhập $R$ rồi bấm Tiếp để xem $S$ nằm ở đâu trên trục số và hai cách cho ra kết quả gì:

Ví dụ nhanh:

Bẫy 1Ép kiểu chỉ cắt, không làm tròn

Muốn bỏ phần thập phân, nhiều bạn viết ngay (long long)S (hoặc (int)S). Phép ép kiểu số thực sang số nguyên trong C++ cắt bỏ phần lẻ: (long long)153.94 = 153, trong khi đáp án là 154.

C++▶ Chạy tay từng dòng codeBẫy 1: (long long)S chỉ cắt bỏ phần lẻ – thử R = 7.
Dữ liệu mẫu:

Kết quả chạy thật: code đúng đúng 6/20 test, sai test 4–12, 15–17, 19, 20, chạy lâu nhất 0.002s. Đúng y các test phải làm tròn lên bị sai, kể cả 5/8 test subtask 1 → chỉ còn 0% điểm. Bản Python print(int(math.pi * R * R)) cũng sai đúng những test đó.

Bẫy 2Lưu kết quả vào int

Đã biết dùng round để làm tròn, nhưng lại cất kết quả vào int ans. Diện tích lớn nhất cỡ $3{,}14 \cdot 10^{12}$, gấp hơn nghìn lần giới hạn của int. Khi ép một số thực quá lớn vào int, máy chấm (x86) cho ra giá trị đặc biệt -2147483648.

C++▶ Chạy tay từng dòng codeBẫy 2: kết quả để trong int – thử R = 10⁵.
Dữ liệu mẫu:

Kết quả chạy thật: code đúng đúng 10/20 test, sai test 9–15, 17–19, chạy lâu nhất 0.003s – đúng là 10 test có đáp án vượt int. Subtask 1 vẫn đúng hết (đáp án tối đa $\approx 3{,}14 \cdot 10^6$) nhưng mất trọn 60% của subtask 2.

Một biến thể cũng hay gặp: int R; rồi viết acos(-1) * (R * R) – khi đó R * R là phép nhân int, tràn khi $R > 46340$. Mình chấm thử: đúng đúng 10/20 test, sai test 9–15, 17–19, chạy lâu nhất 0.004s. Viết acos(-1) * R * R (không ngoặc) thì an toàn vì acos(-1) * R đã là double.

Bẫy 3cout << round(S)

Hàm round của C++ làm tròn đúng, nhưng nó trả về kiểu double. Mà cout in số thực mặc định chỉ với 6 chữ số có nghĩa: round(3141592.65) là 3141593.0 nhưng in ra thành 3.14159e+06.

C++▶ Chạy tay từng dòng codeBẫy 3: round trả về double, cout in dạng 3.14159e+06.
Dữ liệu mẫu:

Kết quả chạy thật: code đúng đúng 7/20 test, sai test 4, 8–15, 17–20, chạy lâu nhất 0.003s. Sai đúng các test đáp án có từ 7 chữ số trở lên, kể cả 2 test của subtask 1 (test 4: $R = 1000$ và test 8: $R = 999$) → 0 điểm. Python có lỗi "anh em": round(S, 0) (có tham số thứ hai) vẫn trả về số thực nên in 28.0 → đúng đúng 0/20 test, sai test 1–20, chạy lâu nhất 0.020s.

Lời giảiCode chuẩn

Ba bài học: làm tròn chứ không cắt, kết quả để trong long long, và in ra kiểu số nguyên. Hàm llround(x) làm cả ba việc: làm tròn đến số nguyên gần nhất và trả về luôn kiểu long long.

C++▶ Chạy tay từng dòng codeCode chuẩn: llround rồi in số nguyên long long.
Dữ liệu mẫu:
  1. long long R: cho chắc, mọi phép tính với R đều không tràn.
  2. double S = acos(-1) * R * R; – tính từ trái sang phải bằng double. Với $S \le 3{,}2 \cdot 10^{12}$, double chính xác tới khoảng $10^{-3}$, đủ để biết phần lẻ lớn hơn hay nhỏ hơn $0{,}5$ (bộ test không có trường hợp sát nút).
  3. Không có llround thì dùng mẹo cổ điển (long long)(S + 0.5): cộng 0,5 rồi cắt – phần lẻ $\ge 0{,}5$ sẽ "vượt" lên số nguyên kế tiếp. Hoặc printf("%.0f", S) (in 0 chữ số thập phân, tự làm tròn). Mình chấm cả hai: đều đúng 20/20.
  4. Python: round(S) không có tham số thứ hai trả về số nguyên (int), in ra đúng dạng. Số nguyên Python không bao giờ tràn.
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.019s.

Nhắc thêm về $\pi = 3{,}14$
Đề đã dặn không dùng $3{,}14$. Mình vẫn thử: đúng đúng 5/20 test, sai test 4, 6–15, 17–20, chạy lâu nhất 0.003s – vì $3{,}14$ lệch $\pi$ khoảng $0{,}0016$, nhân với $R^2$ lớn là sai cả hàng chục, hàng trăm đơn vị. Dùng float thay double cũng hỏng: đúng đúng 10/20 test, sai test 9–15, 17–19, chạy lâu nhất 0.003s (float chỉ có khoảng 7 chữ số chính xác, không đủ cho đáp án 13 chữ số).

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

CodeNgôn ngữĐúngĐiểmGhi chú
Bẫy 1: (long long)SC++6/200%sai test 4–12, 15–17, 19, 20 – các test phải làm tròn lên
Bẫy 2: int ans = round(S)C++10/2040%sai test 9–15, 17–19 – đáp án vượt int
Bẫy 3: cout << round(S)C++7/200%sai test 4, 8–15, 17–20 – in dạng 3.14159e+06
$\pi = 3{,}14$C++5/200%sai test 4, 6–15, 17–20
int R, acos(-1) * (R * R)C++10/2040%sai test 9–15, 17–19 – R * R tràn khi $R > 46340$
float SC++10/2040%sai test 9–15, 17–19 – float không đủ chữ số
Code chuẩn: llroundC++20/20100%Full điểm, chạy lâu nhất 0.003s
(long long)(S + 0.5)C++20/20100%Full điểm
printf("%.0f", S)C++20/20100%Full điểm
Code chuẩn: round(S)Python20/20100%Full điểm, chạy lâu nhất 0.019s
int(S)Python6/200%sai test 4–12, 15–17, 19, 20 – int() cũng là cắt
round(S, 0)Python0/200%in 28.0 thay vì 28
Ghi chú của mình: bài chấm so khớp chính xác (đáp án là số nguyên), mọi kết quả trên đều chạy thật trên 20 test chính thức, thời gian chỉ vài phần nghìn giây. Hai ví dụ của đề đều làm tròn xuống và đáp án nhỏ, nên cả 3 code dính bẫy đều qua ví dụ – đừng vội tin "chạy đúng ví dụ là đúng". Hãy tự thử thêm $R = 7$ (đáp án 154, làm tròn lên) và $R = 10^6$ (đáp án 13 chữ số) 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 😇

Nhận xét