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ổng bốn chữ số

Tổng bốn chữ số
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Chia lấy dư

Đề 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.

Ví dụ 1 – input
3014
Ví dụ 1 – output
8
Ví dụ 2 – input
2345
Ví dụ 2 – output
14

Giả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.

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ó 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ấtTest có chữ số 0
151000 (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 / 10 với n kiể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:

Ví dụ nhanh:
Thuật toán dùng được cho số có bao nhiêu chữ số cũng được, nên ở đây em thử cả số dài hơn 4 chữ số.

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.

C++▶ Chạy tay từng dòng codeCách 1: lặp "lấy chữ số cuối bằng n % 10, bỏ chữ số cuối bằng n / 10" tới khi n = 0.
Dữ liệu mẫu:
  1. s += n % 10; – cộng chữ số hàng đơn vị hiện tại vào tổng.
  2. n /= 10; – bỏ chữ số đó đi. Vì n là int nên phép chia là chia nguyên (bỏ phần thập phân): 301 / 10 = 30.
  3. Điều kiện while(n > 0): khi đã bóc hết 4 chữ số thì n bằ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 = 30 vẫn lớn hơn 0.
Không gian$O(1)$
Thời gian$O(\text{số chữ số}) = O(4)$

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.

Bẫy với Python: / khác //
Trong Python, / 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ã ASCII48495051525354555657
Mã − 480123456789

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.

C++▶ Chạy tay từng dòng codeCách 2: đọc N thành xâu, cộng x[i] - '0' của từng ký tự.
Dữ liệu mẫu:
  1. string x; cin >> x; – đọc cả dòng 3014 vào xâu, mỗi chữ số là một ký tự.
  2. (int)x.size() là độ dài xâu (ở bài này luôn bằng 4).
  3. Bản Python còn gọn hơn: int(c) đổi trực tiếp ký tự '3' thành số 3.
Không gian$O(1)$
Thời gian$O(\text{độ dài xâu}) = O(4)$

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.

Bẫy: quên trừ '0'
Nếu viết 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áchNgôn ngữĐúngLâu nhấtGhi chú
1. Chia lấy dư % 10, / 10C++15/150.00sFull điểm
Python15/150.02sFull điểm
1b. Tách tường minh 4 chữ sốC++15/150.00sFull điểm
2. Đọc xâuC++15/150.00sFull điểm
Python15/150.02sFull điểm
Bẫy: quên trừ '0'C++0/150.00sCộng mã ASCII: test 1 in 200 thay vì 8
Bẫy: dùng / trong PythonPython0/150.00sRa số thực: test 1 in 8.888888888888863
Ghi chú của mình: thời gian là thời gian CPU đo trên máy mình, giới hạn 1 giây – mọi cách đều chạy tức thì. Hai cách đúng đều cho trọn 15/15 test. Điều cần nhớ ở bài này: cặp 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.
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