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

Mức độ thành công

Mức độ thành công
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Số học Long long

Đề bài

Ban tổ chức một kỳ thi lập trình muốn ước lượng mức độ thành công của kỳ thi dựa trên khả năng giải bài của các thí sinh. Mỗi thí sinh được xếp vào đúng một trong ba loại:

LoạiKhả năngĐiểm đánh giá
1giải được ít nhất 1 bài500
2giải được ít nhất 3 bài1000
3giải được toàn bộ các bài1500

Ban tổ chức dự đoán có $a$ thí sinh loại 1, $b$ thí sinh loại 2 và $c$ thí sinh loại 3. Mức độ thành công là tổng điểm đánh giá của tất cả thí sinh. Hãy tính giá trị đó.

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

Kết quả: In ra mức độ thành công của kỳ thi.

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

Subtask:

  • Subtask 1 (40% số điểm): $a, b, c \le 100$.
  • 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
2 1 1
Ví dụ 1 – output
3500
Ví dụ 2 – input
1 1 1
Ví dụ 2 – output
3000

Giải thích: Ví dụ 1: $2 \cdot 500 + 1 \cdot 1000 + 1 \cdot 1500$ $= 3500$.

Nguồn: phỏng theo bài "MỨC ĐỘ THÀNH CÔNG" – vinhdinhcoder.net.

Lời mở đầu

Công thức thì ai cũng thấy ngay: $S = 500a + 1000b + 1500c$. Cái khó duy nhất (và là lý do bài này có subtask) là độ lớn của kết quả: với $a = b = c = 10^9$ thì $S = 3 \cdot 10^{12}$, lớn gấp hơn một nghìn lần số lớn nhất mà kiểu int chứa được. Mình sẽ cho các bạn thấy hai cái bẫy tràn số kinh điển – trong đó bẫy thứ hai bẫy được cả những bạn "biết" phải dùng long long – kèm 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, c \le 100$ (subtask 1), test 9–20 là subtask 2.

NhómTest$a, b, c$ lớn nhấtĐáp án nhỏ nhấtĐáp án lớn nhấtSố test đáp án
vượt quá int
Subtask 11–81003.000300.0000
Subtask 29–201.000.000.000500.000.002.5003.000.000.000.00012
  • Ở subtask 1, đáp án lớn nhất chỉ là $3000 \cdot 100 = 300\,000$, int chứa thoải mái.
  • Ở subtask 2, cả 12 test đều có đáp án lớn hơn $2^{31} - 1 = 2\,147\,483\,647$ (giới hạn của int), lớn nhất tới $3 \cdot 10^{12}$.

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 s = biểu thức int
Lời giải
long long
Subtask 1 (40%)1–88/88/88/8
Subtask 2 (60%)9–200/120/1212/12

Ý tưởngCông thức và "đồng hồ" int

Có $a$ thí sinh loại 1, mỗi bạn 500 điểm → $500a$; tương tự $1000b$ và $1500c$. Vậy

$$S = 500a + 1000b + 1500c.$$

Còn chuyện tràn số: kiểu int có 32 bit, chỉ chứa được các số từ $-2\,147\,483\,648$ tới $2\,147\,483\,647$ (khoảng $\pm 2{,}1$ tỉ). Hãy tưởng tượng int như một đồng hồ hình tròn: đi từ 0 theo chiều kim đồng hồ là các số dương, tới $2\,147\,483\,647$ mà cộng thêm 1 thì kim nhảy sang $-2\,147\,483\,648$ (nửa màu đỏ) rồi đi tiếp lên 0, quay hết vòng này tới vòng khác. Máy chỉ nhớ vị trí của kim, không nhớ đã quay bao nhiêu vòng – nên kết quả bị sai mà không hề báo lỗi.

Em nhập $a, b, c$ rồi bấm Tiếp để xem tổng được cộng dần và kim đồng hồ int quay:

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 – kim đồng hồ int quay vòng khi tổng vượt 2.147.483.647.
Dữ liệu mẫu:

Code này đúng với hai ví dụ của đề và cả subtask 1. Nhưng với $a = b = c = 10^9$, ngay phép nhân $500 \cdot a = 5 \cdot 10^{11}$ đã vượt xa giới hạn int. Em bấm dữ liệu mẫu a = b = c = 10^9 trong khung chạy tay để xem kim đồng hồ quay hàng trăm vòng.

Kết quả chạy thật: bản C++ đúng 8/20 test, sai ở test 9–20; test đúng chạy lâu nhất 0.003s. Với trường hợp lớn nhất $a = b = c = 10^9$, chương trình in 2112827392, trong khi đáp án là 3000000000000. Tất cả 12 test của subtask 2 đều sai → chỉ được 40% số điểm.

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

Nhiều bạn biết kết quả lớn nên sửa đúng một chỗ: đổi biến tổng thành long long, còn a, b, c vẫn để int:

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

Vấn đề là C++ tính vế phải trước, theo kiểu của các toán hạng: 500 là int, a là int nên 500 * a được tính bằng int và đã tràn ngay lúc nhân. Gán kết quả (đã sai) vào long long sau đó cũng chẳng cứu được gì – giống như rót nước tràn ly rồi mới đổi sang cái xô.

Kết quả chạy thật: bản C++ đúng 8/20 test, sai ở test 9–20; test đúng chạy lâu nhất 0.003s – y hệt bẫy 1, in ra đúng những con số sai như bẫy 1.

Cách sửa đúng
Phải làm cho phép nhân được tính bằng long long: hoặc khai báo luôn long long a, b, c (cách mình dùng), hoặc viết 500LL * a + 1000LL * b + 1500LL * c (hậu tố LL biến hằng số thành long long, kéo cả phép nhân lên long long), hoặc 1LL * 500 * a.

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

long long có 64 bit, chứa được tới khoảng $9{,}2 \cdot 10^{18}$ – dư sức cho $3 \cdot 10^{12}$. Khai báo $a, b, c$ là long long thì mọi phép nhân, phép cộng đều tính bằng long long:

C++▶ Chạy tay từng dòng codeLời giải: a, b, c là long long nên mọi phép nhân, phép cộng đều tính bằng long long.
Dữ liệu mẫu:
  1. Python không có bẫy này: số nguyên của Python tự lớn lên bao nhiêu cũng được, nên chỉ một dòng là xong.
  2. Mẹo kiểm tra nhanh trước khi code: ước lượng kết quả lớn nhất có thể có ($3000 \cdot 10^9 = 3 \cdot 10^{12}$). Lớn hơn khoảng $2 \cdot 10^9$ là phải nghĩ ngay tới long long.
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.017s. 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++8/200.003sSai test 9–20 → mất subtask 2
Bẫy 2: long long s nhưng a, b, c là intC++8/200.003sSai test 9–20 → mất subtask 2
Lời giải: long longC++20/200.003sFull điểm
Python20/200.017sFull đ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); khác nhau chỉ ở chỗ có tràn số hay không. Cả hai bẫy đều đúng subtask 1 và sai toàn bộ 12 test subtask 2. Bài học: kiểu dữ liệu của một biểu thức do các toán hạng quyết định, không phải do biến mà nó được gán vào – muốn tính bằng long long thì phải có toán hạng long long ngay trong phép nhâ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