Diện tích hình 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.
32813Giả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.
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 OJLộ 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óm | Test | $R$ lớn nhất | Đáp án lớn nhất | Số test phải làm tròn lên | Số test đáp án $> 2^{31}-1$ | Số test đáp án $\ge 10^6$ |
|---|---|---|---|---|---|---|
| Subtask 1 | 1–8 | 1.000 | 3.141.593 | 5 | 0 | 2 |
| Subtask 2 | 9–20 | 1.000.000 | 3.141.592.653.590 | 9 | 10 | 11 |
- 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
intkhô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ùnglong long. - Có 13 test đáp án từ $10^6$ trở lên (test 4, 8–15, 17–20) –
coutmột số thực lớn như vậy sẽ in ra dạng3.14159e+06.
Kết quả chạy thật của các code trong bài theo từng subtask:
| Subtask | Test | Bẫy 1 ép kiểu cắt | Bẫy 2int ans | Bẫy 3cout<<round | Code chuẩn |
|---|---|---|---|---|---|
| Subtask 1 (40%) $R \le 1000$ | 1–8 | 3/8 | 8/8 | 6/8 | 8/8 |
| Subtask 2 (60%) đầy đủ: $R \le 10^6$ | 9–20 | 3/12 | 2/12 | 1/12 | 12/12 |
| Điểm | 0% | 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ì:
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.
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.
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.
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.
long long R: cho chắc, mọi phép tính vớiRđều không tràn.double S = acos(-1) * R * R;– tính từ trái sang phải bằngdouble. Với $S \le 3{,}2 \cdot 10^{12}$,doublechí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).- Không có
llroundthì 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ặcprintf("%.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. - 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.
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.
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
| Code | Ngôn ngữ | Đúng | Điểm | Ghi chú |
|---|---|---|---|---|
Bẫy 1: (long long)S | C++ | 6/20 | 0% | 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/20 | 40% | sai test 9–15, 17–19 – đáp án vượt int |
Bẫy 3: cout << round(S) | C++ | 7/20 | 0% | sai test 4, 8–15, 17–20 – in dạng 3.14159e+06 |
| $\pi = 3{,}14$ | C++ | 5/20 | 0% | sai test 4, 6–15, 17–20 |
int R, acos(-1) * (R * R) | C++ | 10/20 | 40% | sai test 9–15, 17–19 – R * R tràn khi $R > 46340$ |
float S | C++ | 10/20 | 40% | sai test 9–15, 17–19 – float không đủ chữ số |
Code chuẩn: llround | C++ | 20/20 | 100% | Full điểm, chạy lâu nhất 0.003s |
(long long)(S + 0.5) | C++ | 20/20 | 100% | Full điểm |
printf("%.0f", S) | C++ | 20/20 | 100% | Full điểm |
Code chuẩn: round(S) | Python | 20/20 | 100% | Full điểm, chạy lâu nhất 0.019s |
int(S) | Python | 6/20 | 0% | sai test 4–12, 15–17, 19, 20 – int() cũng là cắt |
round(S, 0) | Python | 0/20 | 0% | in 28.0 thay vì 28 |
Nhận xét
Đăng nhận xét