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

Tính lượng nước cho hồ cá

Tính lượng nước cho hồ cá
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Hình học Long long

Đề bài

Nhà Tèo vừa đào một hồ nuôi cá có dạng hình hộp chữ nhật: mặt hồ là hình chữ nhật kích thước $a \times b$ mét và hồ sâu $h$ mét.

Hồ cá hình hộp chữ nhật kích thước a × b, sâu h

Tèo muốn bơm nước cho đầy hồ. Hãy giúp Tèo tính xem cần bơm bao nhiêu lít nước. Biết $1$ m³ $= 1000$ lít.

Dữ liệu vào: Một dòng gồm ba số nguyên dương $a, b, h$ cách nhau bởi dấu cách.

Kết quả: In ra số lít nước cần bơm.

Giới hạn: $1 \le a, b, h \le 10^4$.

Subtask:

  • Subtask 1 (40% số điểm): $a, b, h \le 10$.
  • 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
4 8 5
Ví dụ 1 – output
160000
Ví dụ 2 – input
1 1 1
Ví dụ 2 – output
1000

Giải thích: Ví dụ 1: thể tích hồ là $4 \cdot 8 \cdot 5 = 160$ m³, tức $160 \cdot 1000 = 160\,000$ lít.

Nguồn: phỏng theo bài "TÍNH TOÁN LƯỢNG NƯỚC" – vinhdinhcoder.net.

Lời mở đầu

Một bài hình học lớp 5: thể tích hình hộp chữ nhật bằng dài × rộng × cao, rồi đổi m³ ra lít. Nhưng nhân bốn số với nhau thì kết quả lớn rất nhanh: $10^4 \cdot 10^4 \cdot 10^4 \cdot 1000 = 10^{15}$ lít – gấp gần nửa triệu lần giới hạn của int. Bài này là dịp để các bạn nhớ mãi chuyện tràn số, với kết quả chạy thật trên bộ test.

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 dòng 3 số. Mình đã kiểm tra: test 1–8 thoả $a, b, h \le 10$ (subtask 1), test 9–20 là subtask 2.

NhómTest$a, b, h$ lớn nhấtĐáp án nhỏ nhấtĐáp án lớn nhấtSố test đáp án
vượt quá int
Subtask 11–8101.0001.000.0000
Subtask 29–2010.00010.000.0001.000.000.000.000.00010
  • Subtask 1 có đáp án lớn nhất $10 \cdot 10 \cdot 10 \cdot 1000 = 10^6$ lít, int chứa thoải mái.
  • Subtask 2 có 10 test đáp án vượt $2^{31} - 1 = 2\,147\,483\,647$. Hai test còn lại (test 11 và 13) có đáp án vẫn vừa int – trong đó có test đáp án sát nút giới hạn int (thấp hơn chưa tới 1%) – loại test "lọt khe" này cho thấy có đúng vài test lớn cũng chưa chắc code đã an toàn.

Kết quả chạy thật của các cách viết bên dưới theo từng subtask:

SubtaskTestBẫy 1
int
Bẫy 2
long long V = biểu thức int
Lời giải
long long
Subtask 1 (40%)1–88/88/88/8
Subtask 2 (60%)9–202/122/1212/12

Ý tưởngThể tích hình hộp, đổi ra lít

Thể tích hồ (hình hộp chữ nhật) là $V = a \cdot b \cdot h$ (m³). Mỗi m³ là 1000 lít, nên số lít cần bơm là

$$\text{số lít} = a \cdot b \cdot h \cdot 1000.$$

Có thể hình dung bơm nước theo từng "lớp" dày 1 m: mỗi lớp là một tấm nước $a \times b \times 1$ chứa $a \cdot b$ m³ $= 1000ab$ lít, có $h$ lớp như vậy. Em nhập kích thước hồ rồi bấm Tiếp để xem nước dâng lên từng lớp – và để ý dòng cuối: nếu lưu số lít bằng int thì tới lúc nào bị tràn:

Ví dụ nhanh:

Bẫy 1Dùng int cho tất cả

C++▶ Chạy tay từng dòng codeBẫy 1: mọi thứ là int – tích a·b·h·1000 tràn khi vượt 2.147.483.647.
Dữ liệu mẫu:

Với subtask 1 thì code này đúng. Nhưng với $a = b = h = 10^4$, ngay $a \cdot b \cdot h = 10^{12}$ đã vượt int, nhân tiếp với 1000 càng sai. Máy không báo lỗi gì, chỉ lặng lẽ "quay vòng" – em bấm dữ liệu mẫu 10000 10000 10000 trong khung chạy tay để xem.

Kết quả chạy thật: bản C++ đúng 10/20 test, sai ở test 9, 10, 12, 14–20; test đúng chạy lâu nhất 0.004s. Với hồ lớn nhất $a = b = h = 10^4$, chương trình in -1530494976 (số âm!) trong khi đáp án là 1000000000000000. Code vẫn "may mắn" đúng test 11 và 13 vì đáp án hai test đó vừa lọt int – nhưng vẫn mất trọn 60% của subtask 2.

Bẫy 2Gán vào long long vẫn tràn

Sửa "nửa vời": chỉ đổi biến kết quả sang long long:

int a, b, h;
cin >> a >> b >> h;
long long V = a * b * h * 1000; // BẪY: vế phải vẫn tính bằng int, tràn TRƯỚC khi gán
cout << V;

C++ tính vế phải trước: a * b * h * 1000 toàn là int nên được tính bằng int và tràn ngay trong lúc nhân, gán vào long long lúc đó đã muộn.

Kết quả chạy thật: bản C++ đúng 10/20 test, sai ở test 9, 10, 12, 14–20; test đúng chạy lâu nhất 0.003s – y hệt bẫy 1.

Cách sửa đúng
Khai báo luôn long long a, b, h (cách mình dùng), hoặc đưa một toán hạng long long lên đầu phép nhân: 1000LL * a * b * h hay 1LL * a * b * h * 1000. Chú ý viết a * b * h * 1000LL là chưa đủ: phép nhân tính từ trái sang phải, a * b * h vẫn tính bằng int và đã có thể tràn trước khi gặp 1000LL.

Lời giảiDùng long long ngay từ đầu

C++▶ Chạy tay từng dòng codeLời giải: a, b, h là long long, tính thể tích rồi đổi ra lít.
Dữ liệu mẫu:
  1. long long chứa được tới khoảng $9{,}2 \cdot 10^{18}$, dư sức cho $10^{15}$.
  2. Vì a, b, h đều là long long nên a * b * h và V * 1000 đều được tính bằng long long.
  3. Python không có giới hạn số nguyên nên viết thẳng công thức là được.
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.003s. Bản Python đúng 20/20 test; test đúng chạy lâu nhất 0.019s. Full điểm.

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

Cách viếtNgôn ngữĐúngLâu nhấtGhi chú
Bẫy 1: toàn bộ là intC++10/200.004sSai test 9, 10, 12, 14–20 → mất subtask 2
Bẫy 2: long long V nhưng a, b, h là intC++10/200.003sSai test 9, 10, 12, 14–20 → mất subtask 2
Lời giải: long longC++20/200.003sFull điểm
Python20/200.019sFull điểm
Ghi chú của mình: mọi cách đều chạy tức thì (thời gian CPU đo trên máy mình, giới hạn 1 giây). Hai bẫy tràn số đều chỉ đúng 10/20 test: 8 test subtask 1 và 2 test subtask 2 "lọt khe" vì đáp án nhỏ hơn $2^{31}$. Thói quen tốt: trước khi code, ước lượng kết quả lớn nhất ($10^{15}$ ở bài này); vượt khoảng $2 \cdot 10^9$ là dùng long long, và nhớ rằng kiểu của phép tính do các toán hạng quyết định, không phải do biến được gán.
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