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

Số đối xứng (Tin học trẻ toàn quốc 2025 – Vòng chung kết toàn quốc – Bảng B)

Số đối xứng
Nguồn: Tin học trẻ toàn quốc 2025 – Chung kết – Bảng B Độ khó: Dễ Số đối xứng Xử lý xâu

Đề bài

🏆
Nguồn: Hội thi Tin học trẻ toàn quốc 2025 – Vòng chung kết toàn quốc – Bảng B. Đề dưới đây mình viết lại theo lời của mình cho dễ hiểu, giữ nguyên ý nghĩa, giới hạn và ví dụ của đề gốc.

Một số được gọi là số đối xứng nếu đọc các chữ số của nó từ trái sang phải hay từ phải sang trái đều giống nhau. Ví dụ $7$, $99$, $121$, $1331$ là các số đối xứng, còn $123$ hay $10$ thì không.

Cho số tự nhiên $N$. Hỏi phải cộng thêm vào $N$ ít nhất bao nhiêu đơn vị để được một số đối xứng? (Nếu bản thân $N$ đã đối xứng thì đáp án là $0$.)

Dữ liệu vào: một dòng chứa số tự nhiên $N$, $N$ có không quá $16$ chữ số.

Kết quả: in ra một số tự nhiên – số đơn vị ít nhất cần cộng thêm.

Subtask:

  • Subtask 1 ($80\%$ số điểm): $N$ có không quá $8$ chữ số.
  • Subtask 2 ($20\%$ số điểm): $N$ có không quá $16$ chữ số.

Giới hạn thời gian: 1 giây. Dữ liệu nhập từ bàn phím, in ra màn hình.

Ví dụ 1 – input
123
Ví dụ 1 – output
8
Ví dụ 2 – input
121
Ví dụ 2 – output
0

Giải thích: ví dụ 1: $123 + 8 = 131$ là số đối xứng nhỏ nhất không nhỏ hơn $123$. Ví dụ 2: $121$ đã là số đối xứng nên không cần cộng thêm gì.

Lời mở đầu

Bài mở màn của bảng B vòng chung kết. Cách "ngây thơ" – cứ cộng 1, cộng 1 rồi kiểm tra – đã ăn được 80% số điểm. Để lấy nốt 20% còn lại với $N$ tới 16 chữ số, ta cần một nhận xét nhỏ mà rất đẹp: số đối xứng được quyết định hoàn toàn bởi nửa đầu của nó. Mình sẽ đi lần lượt hai cách, cả hai đều chạy thật trên bộ test để các bạn thấy rõ mỗi cách được bao nhiêu điểm.

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 có gì?

Bộ test trên CLUE gồm 25 test do mình tự sinh theo đúng tỉ lệ subtask của đề (test 1, 2 là hai ví dụ của đề):

NhómTestSố chữ số của $N$Số test đáp án 0Đáp án lớn nhất
Subtask 1 (80%)1–201, 2, 3, 4, 5, 7, 8510.999 (test 20)
Subtask 2 (20%)21–2516198.888.889 (test 23)
  • Có đủ các trường hợp "bẫy": $N = 0$, $N$ có 1 chữ số, $N$ đã đối xứng sẵn (đáp án 0), $N = 10^k$ (đáp án 1, ví dụ $1000 + 1 = 1001$), $N$ toàn chữ số 9, và trường hợp nửa đầu phải tăng thêm 1 có nhớ (như $1\,299\,999 \to 1\,300\,031$).
  • Ở subtask 1, đáp án lớn nhất chỉ cỡ $10^4$; nhưng ở subtask 2 ($N$ có 16 chữ số) đáp án có thể lên tới gần $10^8$ – đây là chỗ phân biệt cách "thử từng số" với cách "lật gương".

Kết quả chạy thật của từng cách trên từng subtask (C++):

SubtaskĐiều kiệnTestCách 1
thử từng số
Cách 2
lật gương
Subtask 1 (80%)$N$ có không quá 8 chữ số1–20quaqua
Subtask 2 (20%)$N$ có không quá 16 chữ số21–253/5 testqua

Lời giải cách 2 (C++) mình đã nộp thử trên hệ thống chấm chính thức của hội thi (tinhoctre.vn) và đạt AC 100/100 điểm.

Ý tưởngNửa đầu quyết định tất cả

Gọi $L$ là số chữ số của $N$ và $h = \lceil L/2 \rceil$. Một số đối xứng có $L$ chữ số chỉ cần biết $h$ chữ số đầu (gọi là nửa đầu) là dựng lại được cả số: nửa sau chính là nửa đầu viết ngược (nếu $L$ lẻ thì chữ số giữa không lặp lại). Ví dụ nửa đầu 12 với $L = 3$ cho $121$, với $L = 4$ cho $1221$. Ta gọi thao tác này là lật gương.

Hơn nữa, hai số đối xứng cùng $L$ chữ số thì số nào có nửa đầu lớn hơn sẽ lớn hơn (vì so sánh hai số cùng độ dài là so từ chữ số đầu tiên). Từ đó:

  1. Lấy nửa đầu của chính $N$ rồi lật gương được số đối xứng $p$. Mọi số đối xứng có nửa đầu nhỏ hơn thì đều nhỏ hơn $N$ (vì $h$ chữ số đầu đã nhỏ hơn). Vậy nếu $p \ge N$ thì $p$ chính là số đối xứng nhỏ nhất $\ge N$.
  2. Nếu $p < N$: tăng nửa đầu thêm $1$ rồi lật gương lại. Số mới có nửa đầu lớn hơn nửa đầu của $N$ nên chắc chắn $> N$, và nó là số đối xứng kế tiếp sau $p$.
Không bao giờ bị "tràn" sang thêm chữ số
Ở bước 2, nếu nửa đầu toàn chữ số $9$ thì $p = 99\ldots9$ là số lớn nhất có $L$ chữ số, nên chắc chắn $p \ge N$ và ta đã dừng ở bước 1. Vậy khi phải tăng nửa đầu thì nó không toàn $9$, cộng $1$ không làm tăng số chữ số.

Em nhập $N$ rồi bấm Tiếp để xem từng bước lật gương:

Ví dụ nhanh:

Cách 1Thử cộng từng đơn vị

Cách tự nhiên nhất: thử $d = 0, 1, 2, \ldots$ và dừng ở giá trị $d$ đầu tiên mà $N + d$ là số đối xứng. Để kiểm tra một số $x$ có đối xứng không, ta lật ngược các chữ số của $x$ (lấy dần chữ số cuối bằng % 10, bỏ chữ số cuối bằng / 10) được số $y$, rồi so sánh $y$ với $x$.

C++▶ Chạy tay từng dòng codeCách 1: thử d = 0, 1, 2, ... và kiểm tra N + d bằng cách lật ngược các chữ số.
Dữ liệu mẫu:
  1. Dòng y = y * 10 + t % 10 đẩy các chữ số cũ của $y$ sang trái một bậc rồi gắn chữ số cuối của $t$ vào – sau vòng lặp $y$ là $x$ viết ngược.
  2. Vòng while trong main chắc chắn dừng vì số $99\ldots9$ luôn là số đối xứng.
  3. Với $N = 0$: hàm trả về y == x là 0 == 0, đúng – đáp án 0.
Không gian$O(1)$
Thời gian$O(D \cdot L)$, $D$ là đáp án (tới $\approx 10^8$)

Kết quả chạy thật: bản C++ đúng 23/25 test – quá thời gian test 23, 25 (test đúng chạy lâu nhất 0,004 giây). Bản Python đúng 23/25 test – quá thời gian test 23, 25 (test đúng chạy lâu nhất 0,019 giây).

Với $N$ tối đa 8 chữ số, khoảng cách tới số đối xứng gần nhất chỉ cỡ $10^4$ nên chạy tức thì. Nhưng khi $N$ có 16 chữ số, khoảng cách có thể gần $10^8$ (ví dụ $N = 1234567898765432$ phải cộng tới $98\,888\,889$), nhân thêm 16 phép chia cho mỗi lần kiểm tra là quá 1 giây.

Cách 2Lật gương nửa đầu – O(L)

Cài đặt đúng như phần ý tưởng. Mình đọc $N$ vào một xâu để tiện cắt nửa đầu, hàm latGuong(nua, L) ghép nửa đầu với phần đảo ngược của nó (bỏ chữ số giữa nếu $L$ lẻ: chỉ lấy $\lfloor L/2 \rfloor$ chữ số đầu của nua để đảo).

C++▶ Chạy tay từng dòng codeCách 2: lật gương nửa đầu; nếu còn nhỏ hơn N thì tăng nửa đầu thêm 1 rồi lật lại.
Dữ liệu mẫu:
  1. s = to_string(n): phòng khi dữ liệu có số 0 ở đầu (như 007) thì độ dài $L$ vẫn tính đúng theo giá trị thật của $N$.
  2. Vòng for(int i = L / 2 - 1; i >= 0; i--) chép ngược $\lfloor L/2 \rfloor$ chữ số đầu: $L = 3$, nua = "12" → chỉ chép nua[0] → "121"; $L = 4$ → chép nua[1], nua[0] → "1221".
  3. $N < 10^{16}$ nên mọi số đều vừa kiểu long long.
Không gian$O(L)$
Thời gian$O(L)$, $L \le 16$

Kết quả chạy thật: bản C++ đúng 25/25 test, chậm nhất 0,004 giây. Bản Python đúng 25/25 test, chậm nhất 0,023 giây.

Tổng kếtKết quả

Ghi chú của mình: cách 1 (thử từng số) được 23/25 test – trọn subtask 1 ($80\%$), trượt các test 16 chữ số có đáp án lớn. Cách 2 (lật gương nửa đầu) đạt 25/25 test cả bản C++ lẫn Python, chạy tức thì; bản C++ này cũng đã đạt 100 điểm trên hệ thống chấm chính thức. Bài học: khi đối tượng cần tìm (số đối xứng) có cấu trúc đặc biệt, hãy "dựng" nó trực tiếp thay vì thử từng ứng viê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