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

GSUM

GSUM
Nguồn: CLUE OJ – Chương 19: Đệ quy cơ bản Độ khó: Dễ Đệ quy Tổng chữ số

Đề bài

Ta gọi GSUM của một số nguyên dương $n$ là lặp lại tính tổng các chữ số của $n$ cho đến khi nào tổng các chữ số chỉ còn lại đúng 1 chữ số thì dừng. Ví dụ:

  • $GSUM(153) = 1 + 5 + 3 = 9$.
  • $GSUM(2244)$ $= 2 + 2 + 4 + 4$ $= 12 \to 1 + 2$ $= 3$.

Nhiệm vụ của bài tập này là xây dựng chương trình con đệ quy tính GSUM của một số nguyên dương $n$ được nhập vào từ bàn phím.

Dữ liệu vào:

  • Gồm số nguyên dương $n$ $(0 \le n \le 10^{18})$.

Kết quả:

  • In một số nguyên duy nhất là đáp án của bài toán.
Ví dụ 1 – input
153
Ví dụ 1 – output
9
Ví dụ 2 – input
2244
Ví dụ 2 – output
3

Lời mở đầu

Bài này có hai tầng đệ quy lồng nhau: tính tổng chữ số đã là một bài đệ quy, và việc "cộng tiếp cho tới khi còn 1 chữ số" lại là một tầng đệ quy nữa. Thêm một cái bẫy nhỏ: $n$ lên tới $10^{18}$, dùng int là tràn số ngay. Cuối bài mình tặng thêm một công thức $O(1)$ rất gọn dựa trên phép chia cho 9. Bài thuộc chương Đệ quy cơ bản trên CLUE OJ (nguồn bài: VinhDinhCoder).

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ó 100 test, mỗi test là một số $n$:

TestnSố chữ sốĐáp án
1–30111 … 525.095.7033 … 91, 2, 3, 4, 5, 6, 7, 8, 9
31–702.494.215.876 … 821.034.825.241.42410 … 151, 2, 3, 4, 5, 6, 7, 8, 9
71–1001.357.911.167.972.809 … 986.435.618.980.070.89216 … 183, 4, 5, 6, 7, 8, 9
  • Test 1–30: $n$ tối đa khoảng $5 \cdot 10^8$ – vẫn vừa kiểu int.
  • Test 31–70: $n$ có 10 tới 15 chữ số; test 71–100: $n$ có 16 tới 18 chữ số – bắt buộc dùng long long.
  • Không có test $n = 0$ (code bên dưới vẫn in đúng $0$).

Đề không chia subtask, mình chia 3 nhóm theo độ lớn của $n$ để thấy rõ cái bẫy kiểu dữ liệu (kết quả chạy thật, giới hạn 1 giây):

NhómĐiều kiệnTestDùng int
(code sai)
Cách 1
(đệ quy)
Cách 2
(công thức)
1$n < 2^{31}$ (vừa kiểu int)1–3030/3030/3030/30
2$n \le 10^{15}$31–704/4040/4040/40
3$n \le 10^{18}$71–1000/3030/3030/30

Ý tưởngHai hàm đệ quy lồng nhau

Hàm 1 – tổng chữ số sumDigit(n): chữ số cuối của $n$ là n % 10, phần còn lại (bỏ chữ số cuối) là n / 10. Vậy:

sumDigit
Nếu $n < 10$ (chỉ còn 1 chữ số) thì $sumDigit(n) = n$. Ngược lại $sumDigit(n)$ $= n \bmod 10 + sumDigit(\lfloor n / 10 \rfloor)$.

Hàm 2 – GSUM: nếu $n$ đã chỉ có 1 chữ số thì xong; ngược lại thay $n$ bằng tổng chữ số của nó rồi làm tiếp:

gsum
Nếu $n < 10$ thì $gsum(n) = n$. Ngược lại $gsum(n)$ $= gsum(sumDigit(n))$.

Hàm gsum gọi sumDigit trước, lấy kết quả rồi mới gọi lại chính nó. Em nhập $n$ rồi bấm Tiếp để xem ngăn xếp lời gọi của cả hai hàm:

Ví dụ nhanh:

Hàm gsum gọi lại chính nó rất ít lần: $n \le 10^{18}$ có tối đa 19 chữ số nên tổng chữ số $\le 9 \cdot 19 = 171$, lần sau còn $\le 1 + 9 + 9 = 19$, rồi $\le 10$, rồi xong.

Cách 1Cài đặt đệ quy – $O(\text{số chữ số})$

C++▶ Chạy tay từng dòng codeCách 1: hai hàm đệ quy sumDigit và gsum, xem ngăn xếp lời gọi của cả hai.
Dữ liệu mẫu:
  1. Tham số và giá trị trả về đều là long long vì $n \le 10^{18}$ vượt xa giới hạn của int (khoảng $2{,}1 \cdot 10^9$). Mình đã thử bản dùng int: chỉ đúng 34/100 test – sai gần hết các test có $n \ge 2^{31}$ (vài test đúng do may mắn).
  2. Điểm dừng của cả hai hàm đều là n < 10. Với $n = 0$ hàm trả về 0 ngay.
  3. gsum(sumDigit(n)): máy tính sumDigit(n) xong (toàn bộ chuỗi lời gọi của nó đã kết thúc) rồi mới gọi gsum với kết quả đó.
Không gian$O(\log_{10} n)$ (ngăn xếp)
Thời gian$O(\log_{10} n)$

Kết quả chạy thật: Bản C++ đúng 100/100 test; test đúng chạy lâu nhất 0.00s. Bản Python: đúng 100/100 test; test đúng chạy lâu nhất 0.02s.

Cách 2Công thức chia cho 9 – $O(1)$

Vì $10 \equiv 1 \pmod 9$ nên một số và tổng các chữ số của nó luôn có cùng số dư khi chia cho 9 (đây là dấu hiệu chia hết cho 9 các bạn đã học). Cộng chữ số bao nhiêu lần thì số dư vẫn giữ nguyên, và kết quả cuối là một số từ 1 đến 9 (khi $n > 0$). Do đó:

Công thức
$GSUM(0) = 0$; với $n \ge 1$: $GSUM(n) = 1 + (n - 1) \bmod 9$ (dư 0 thì đáp án là 9, dư $r \ne 0$ thì đáp án là $r$).

Thử lại: $153 = 9 \cdot 17$ chia hết cho 9 → đáp án 9 ✓. $2244 = 9 \cdot 249 + 3$ → đáp án 3 ✓.

C++▶ Chạy tay từng dòng codeCách 2: công thức GSUM(n) = 1 + (n − 1) mod 9.
Dữ liệu mẫu:
Không gian$O(1)$
Thời gian$O(1)$

Kết quả chạy thật: Bản C++ đúng 100/100 test; test đúng chạy lâu nhất 0.01s. Bản Python: đúng 100/100 test; test đúng chạy lâu nhất 0.02s. Cách này không dùng đệ quy nên bài nộp theo đúng yêu cầu chương thì các bạn dùng cách 1 nhé; cách 2 dùng để kiểm tra chéo và để nhớ một tính chất số học rất hay.

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

CáchNgôn ngữĐúngLâu nhấtGhi chú
Đệ quy nhưng dùng intC++34/1000.00sTràn số khi n ≥ 231
1. Hai hàm đệ quy (long long)C++100/1000.00sFull điểm
Python100/1000.02sFull điểm
2. Công thức 1 + (n − 1) mod 9C++100/1000.01sFull điểm
Python100/1000.02sFull điểm
Ghi chú của mình: cả cách đệ quy và cách công thức đều qua 100/100 test, mỗi test chỉ vài mili giây. Lỗi hay gặp nhất của bài là khai báo int – chỉ đúng khoảng một phần ba số test. Đáp án trong bộ test chỉ là các số từ 1 đến 9.
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 😇