Tìm hai số từ tổng và hiệu (Tin học trẻ toàn quốc 2025 – Vòng loại quốc gia đợt 3 – Bảng B, C2)
Đề bài
Có hai số nguyên $A$ và $B$ (với $|A|, |B| \le 10^{100}$). Người ta đã tính sẵn tổng $A + B$ và hiệu $A - B$ của chúng. Rõ ràng khi biết tổng và hiệu thì ta tìm lại được hai số ban đầu.
Yêu cầu: cho tổng và hiệu của hai số nguyên, hãy tìm lại hai số đó.
Dữ liệu vào:
- Dòng 1: số nguyên $A + B$.
- Dòng 2: số nguyên $A - B$.
Kết quả:
- Dòng 1: số $A$.
- Dòng 2: số $B$.
Subtask:
- Subtask 1 (90% số điểm): $|A|, |B| \le 10^9$.
- Subtask 2 (10% số điểm): không có ràng buộc gì thêm.
Giới hạn thời gian: 1 giây. Dữ liệu nhập từ bàn phím và in ra màn hình.
0
-200-100
100Giải thích: $A = -100$, $B = 100$ cho $A + B = 0$ và $A - B = -200$.
Nguồn: Hội thi Tin học trẻ toàn quốc 2025 – Vòng loại quốc gia đợt 3 – Bảng B và Bảng C2 (bài AB). Đề được mình viết lại cho dễ đọc, giữ nguyên ý và số liệu.
Lời mở đầu
Đây là bài "cho điểm" của đợt 3 nhưng có một cái bẫy rất đặc trưng của các kỳ thi: 90% điểm chỉ cần một công thức lớp 8, còn 10% điểm cuối lại đòi số có tới 101 chữ số – vượt xa mọi kiểu số nguyên của C++. Mình sẽ đi từ công thức (ăn subtask 1), rồi tự cài đặt phép cộng, trừ, chia đôi trên xâu chữ số để lấy trọn điểm. Với Python thì... các bạn sẽ thấy nó "ăn gian" 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 có gì?
Bộ test của mình gồm 20 test (sinh theo đúng tỉ lệ subtask của đề): test 1–18 thuộc subtask 1 ($|A|, |B| \le 10^9$), test 19–20 thuộc subtask 2 (số tới $10^{100}$). Test 1 là ví dụ của đề. Lời giải cuối cùng (cách 2) đã được nộp thử trên hệ thống chấm chính thức tinhoctre.vn và đạt 100/100 điểm.
| Nhóm | Test | Số chữ số lớn nhất của $A+B$, $A-B$ | Số chữ số lớn nhất của $A$, $B$ | Số test có $A$ hoặc $B$ âm | Số test có $A$ hoặc $B$ bằng 0 |
|---|---|---|---|---|---|
| Subtask 1 | 1–18 | 10 | 10 | 10/18 | 3/18 |
| Subtask 2 | 19–20 | 101 | 101 | 2/2 | 0/2 |
- Subtask 1 có các test biên: $A = B = 10^9$ (tổng bằng $2 \cdot 10^9$), $A = -B = 10^9$ (hiệu bằng $2 \cdot 10^9$), $A = B = 0$, $A$ hoặc $B$ âm... Test 3–6 có tổng hoặc hiệu bằng đúng $2 \cdot 10^9$.
- Subtask 2 có test $A = 10^{100}$, $B = -10^{100}$ (hiệu có 101 chữ số) và một test ngẫu nhiên khoảng 100 chữ số.
Kết quả chạy thật của các cách bên dưới theo từng subtask:
| Subtask | Điều kiện | Test | Cách 1 long long | Cách 1 Python | Cách 2 số lớn tự cài |
|---|---|---|---|---|---|
| Subtask 1 (90%) | $|A|, |B| \le 10^9$ | 1–18 | 18/18 | 18/18 | 18/18 |
| Subtask 2 (10%) | không ràng buộc thêm ($|A|, |B| \le 10^{100}$) | 19–20 | 0/2 | 2/2 | 2/2 |
Ý tưởngCộng và trừ hai phương trình
Gọi $S = A + B$ và $D = A - B$. Cộng hai đẳng thức lại, $B$ triệt tiêu; trừ hai đẳng thức, $A$ triệt tiêu:
$$S + D = 2A \;\Rightarrow\; A = \frac{S + D}{2}, \qquad S - D = 2B \;\Rightarrow\; B = \frac{S - D}{2}.$$
Vì $S + D = 2A$ là số chẵn nên phép chia 2 luôn chia hết, không phải lo làm tròn – kể cả khi số âm.
Phần khó chỉ là độ lớn: ở subtask 2, $S$ và $D$ có thể có 101 chữ số. Khi đó ta làm y như học sinh tiểu học: đặt tính theo cột để cộng/trừ từng chữ số (có nhớ, có mượn), rồi chia đôi từ hàng cao xuống. Em nhập $S$, $D$ rồi bấm Tiếp để xem máy "đặt tính" từng bước:
Cách 1Dùng công thức với long long (subtask 1)
Với $|A|, |B| \le 10^9$ thì $|S|, |D| \le 2 \cdot 10^9$. Số này vừa sát giới hạn của int ($2^{31} - 1 \approx 2{,}147 \cdot 10^9$): chỉ cần đề nới thêm một chút là tràn. Thói quen tốt là dùng luôn long long (tới khoảng $9{,}2 \cdot 10^{18}$) cho yên tâm.
- Phép chia
/trong C++ làm tròn về 0, nhưng ở đâys + dvàs - dluôn chẵn nên không có chuyện làm tròn, kết quả đúng cả với số âm (ví dụ $(0 + (-200)) / 2 = -100$). - Bản Python giống hệt về công thức, nhưng kiểu
intcủa Python không giới hạn số chữ số, nên nó tự đúng luôn cho subtask 2. Lưu ý dùng//(chia nguyên); dùng/sẽ ra số thực và mất chính xác khi số lớn.
Kết quả chạy thật: bản C++ đúng 18/20 test, sai test 19–20, chạy lâu nhất 0.003s – test 19, 20 có số hơn 100 chữ số nên cin >> s không đọc nổi, mất 10% điểm của subtask 2. Bản Python đúng 20/20 test, chạy lâu nhất 0.023s – full điểm.
Cách 2Tự cài đặt số nguyên lớn (full điểm)
Ta lưu mỗi số dưới dạng dấu (neg) và trị tuyệt đối là một xâu chữ số (m). Cần 3 thao tác:
- Cộng hai số có dấu
add(x, y): nếu cùng dấu thì cộng hai trị tuyệt đối, giữ dấu chung; nếu khác dấu thì lấy trị tuyệt đối lớn trừ bé và mang dấu của số có trị tuyệt đối lớn hơn (ví dụ $3 + (-8) = -(8 - 3) = -5$). - Đổi dấu: $S - D = S + (-D)$, nên chỉ cần lật
negcủa $D$ rồi dùng lại hàm cộng. - Chia đôi trị tuyệt đối: đi từ chữ số hàng cao,
cur = rem·10 + chữ số, ghicur / 2vào thương, mangcur % 2sang hàng sau. Dấu giữ nguyên.
cmpAbs: so sánh hai xâu chữ số (không có số 0 ở đầu) – xâu dài hơn thì lớn hơn; cùng độ dài thì so sánh như từ điển (a < bcủastring) là đúng thứ tự số.addAbs/subAbsduyệt hai con trỏi,jtừ cuối xâu (hàng đơn vị) về đầu, ghép kết quả vàortheo thứ tự ngược rồireverselại. TrongsubAbs, sau khi trừ có thể còn số 0 ở đầu (ví dụ $100 - 99 = 001$) nên phải bỏ chúng đi.halfAbscũng có thể sinh số 0 ở đầu ($14 / 2$ cho ra"07") nên phải cắt bỏ, nhưng giữ lại ít nhất 1 chữ số.- Khi in, nếu kết quả là 0 thì không in dấu trừ (tránh in
-0).
Kết quả chạy thật: bản C++ đúng 20/20 test, chạy lâu nhất 0.004s – full điểm. Mình cũng thử thêm 1500 bộ ngẫu nhiên (số từ 1 tới 100 chữ số, có các trường hợp $A = B$, $A = -B$, $B = 0$) so với đáp án tính bằng Python: khớp tất cả.
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. Công thức, long long | C++ | 18/20 | 0.003s | Sai 2 test số lớn → 90 điểm |
| 1. Công thức | Python | 20/20 | 0.023s | Python có sẵn số nguyên lớn → full điểm |
| 2. Số lớn tự cài | C++ | 20/20 | 0.004s | Full điểm |
Nhận xét
Đăng nhận xét