Tổng bốn chữ số
Đề bài
Cho số nguyên $N$ có đúng 4 chữ số. Hãy tính tổng các chữ số của $N$.
Ví dụ với $N = 2345$ thì tổng các chữ số là $2 + 3 + 4 + 5 = 14$.
Dữ liệu vào: Một dòng chứa số nguyên $N$.
Kết quả: In ra tổng các chữ số của $N$.
Giới hạn:
- $1000 \le N \le 9999$
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.
30148234514Giải thích: Ví dụ 1: $3 + 0 + 1 + 4 = 8$. Ví dụ 2: $2 + 3 + 4 + 5 = 14$.
Nguồn: phỏng theo bài "TỔNG CHỮ SỐ" – vinhdinhcoder.net.
Lời mở đầu
Đây là bài "nhập môn" kinh điển để làm quen với hai phép toán cực kỳ quan trọng của số nguyên: chia lấy dư % và chia nguyên /. Mình sẽ giải theo hai cách: tách chữ số bằng % 10, / 10 (cách "toán") và đọc $N$ thành xâu ký tự (cách "chữ"). Mỗi cách đều có một cái bẫy nhỏ mà rất nhiều bạn mới học bị dính – mình đã chạy thử cả các code bị bẫy trên bộ test thật để các bạn thấy nó sai thế nào.
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ó 15 test, mỗi test chỉ là một số có 4 chữ số. Bài này không chia subtask: đúng hết thì được trọn điểm.
| Số test | $N$ nhỏ nhất | $N$ lớn nhất | Đáp án nhỏ nhất | Đáp án lớn nhất | Test có chữ số 0 |
|---|---|---|---|---|---|
| 15 | 1000 (test 3) | 9999 (test 4) | 1 (test 3) | 36 (test 4) | 7 test |
- Có cả hai "biên" $N = 1000$ (tổng nhỏ nhất bằng 1) và $N = 9999$ (tổng lớn nhất bằng 36).
- 7 test có chữ số 0 nằm ở giữa hoặc cuối (như $3014$, $9000$, $5050$) – đây là chỗ hay làm lộ lỗi nếu điều kiện dừng vòng lặp viết sai.
Ý tưởngLấy chữ số cuối bằng % 10, bỏ chữ số cuối bằng / 10
Với số nguyên dương $n$ bất kỳ:
- $n \bmod 10$ (trong C++ là
n % 10) là chữ số hàng đơn vị của $n$. Ví dụ $3014 \bmod 10 = 4$. - $\lfloor n / 10 \rfloor$ (trong C++ là
n / 10vớinkiểu số nguyên) là $n$ sau khi bỏ đi chữ số cuối. Ví dụ $3014 / 10 = 301$.
Lặp lại hai việc này: cộng chữ số cuối vào tổng, rồi bỏ chữ số cuối đi – cho tới khi $n$ về $0$ (hết chữ số). Em nhập một số rồi bấm Tiếp để xem từng chữ số được "bóc" ra:
Cách 1Chia lấy dư % 10 và chia nguyên / 10
Vì $N$ có đúng 4 chữ số nên ta có thể viết thẳng 4 công thức:
int a = n / 1000; // hàng nghìn
int b = n / 100 % 10; // hàng trăm
int c = n / 10 % 10; // hàng chục
int d = n % 10; // hàng đơn vị
cout << a + b + c + d;
Cách này đúng (bản C++ này đúng 15/15 test; test đúng chạy lâu nhất 0.00s), nhưng mình thích viết thành vòng lặp hơn: không cần nhớ công thức cho từng hàng và dùng được cho số có bao nhiêu chữ số cũng được.
s += n % 10;– cộng chữ số hàng đơn vị hiện tại vào tổng.n /= 10;– bỏ chữ số đó đi. Vìnlàintnên phép chia là chia nguyên (bỏ phần thập phân):301 / 10 = 30.- Điều kiện
while(n > 0): khi đã bóc hết 4 chữ số thìnbằng 0 và vòng lặp dừng. Chữ số 0 ở giữa (như $3014$) vẫn được xét bình thường vì lúc đón = 30vẫn lớn hơn 0.
Kết quả chạy thật: Bản C++ đúng 15/15 test; test đúng chạy lâu nhất 0.00s. Bản Python đúng 15/15 test; test đúng chạy lâu nhất 0.02s.
/ khác /// luôn cho số thực (301 / 10 = 30.1), phép chia nguyên phải viết //. Nếu bạn chép nguyên công thức từ C++ sang:
n = int(input())
s = 0
while n > 0:
s += n % 10
n = n / 10 # SAI: phải là //
print(s)
thì n không bao giờ về đúng 0 mà nhỏ dần thành $30.1, 3.01, 0.301, \dots$ và các phần lẻ bị cộng hết vào tổng. Mình chạy thử: code này đúng 0/15 test, sai ở test 1–15 – ví dụ test 1 in ra 8.888888888888863.Cách 2Đọc $N$ thành xâu ký tự
Cách thứ hai: đừng coi $N$ là số mà coi nó là một dãy ký tự "3014". Khi đó các chữ số chính là các ký tự x[0], x[1], x[2], x[3] – không cần chia gì cả. Chỉ cần đổi mỗi ký tự chữ số thành giá trị số của nó.
Trong máy tính, mỗi ký tự được lưu bằng một mã số (mã ASCII). Mười ký tự chữ số có mã liên tiếp nhau:
| Ký tự | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
|---|---|---|---|---|---|---|---|---|---|---|
| Mã ASCII | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
| Mã − 48 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Vì vậy x[i] - '0' (tức là lấy mã của ký tự trừ đi 48) chính là chữ số mà ký tự đó biểu diễn: '3' - '0' = 51 - 48 = 3.
string x; cin >> x;– đọc cả dòng3014vào xâu, mỗi chữ số là một ký tự.(int)x.size()là độ dài xâu (ở bài này luôn bằng 4).- Bản Python còn gọn hơn:
int(c)đổi trực tiếp ký tự'3'thành số 3.
Kết quả chạy thật: Bản C++ đúng 15/15 test; test đúng chạy lâu nhất 0.00s. Bản Python đúng 15/15 test; test đúng chạy lâu nhất 0.02s.
'0's += x[i]; thì máy cộng mã ASCII của ký tự chứ không phải chữ số:
int s = 0;
for(int i = 0; i < (int)x.size(); i++){
s += x[i]; // QUÊN trừ '0': cộng mã ASCII của ký tự!
}
Với $N = 3014$ được $51 + 48 + 49 + 52 = 200$. Mình chạy thử: code này đúng 0/15 test, sai ở test 1–15 – test 1 in 200 trong khi đáp án là 8. (Mẹo nhớ: thật ra tổng sai luôn bằng đáp án đúng cộng $4 \times 48 = 192$.)Tổng kếtKết quả chạy thật trên bộ test
| Cách | Ngôn ngữ | Đúng | Lâu nhất | Ghi chú |
|---|---|---|---|---|
1. Chia lấy dư % 10, / 10 | C++ | 15/15 | 0.00s | Full điểm |
| Python | 15/15 | 0.02s | Full điểm | |
| 1b. Tách tường minh 4 chữ số | C++ | 15/15 | 0.00s | Full điểm |
| 2. Đọc xâu | C++ | 15/15 | 0.00s | Full điểm |
| Python | 15/15 | 0.02s | Full điểm | |
Bẫy: quên trừ '0' | C++ | 0/15 | 0.00s | Cộng mã ASCII: test 1 in 200 thay vì 8 |
Bẫy: dùng / trong Python | Python | 0/15 | 0.00s | Ra số thực: test 1 in 8.888888888888863 |
n % 10 / n / 10 sẽ còn gặp lại rất nhiều (đảo ngược số, kiểm tra số đối xứng, đếm chữ số...); còn khi làm với xâu thì luôn nhớ - '0' trong C++ và // thay cho / khi chia nguyên trong Python.
Nhận xét
Đăng nhận xét