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
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Chuyên đề 3: Số nguyên lớn (Big Int) trong C++ và Python: cộng, trừ, nhân, chia số lớn bằng xâu

Chuyên đề 3: Số nguyên lớn (Big Int) trong C++ và Python: cộng, trừ, nhân, chia số lớn bằng xâu
Series: Chuyên đề nhập môn Độ khó: Dễ Chuyên đề 3 / Nhập môn lập trình Số lớn Xâu
// Chuyên đề nhập môn · Chuyên đề 3
Số nguyên lớn: cộng, trừ, nhân, chia những con số hàng trăm chữ số
long long chỉ chứa được khoảng 19 chữ số. Muốn tính $100!$ hay $2^{1000}$, ta cất số vào một xâu và làm phép tính đúng như cách em đặt tính bằng bút chì ở tiểu học.

Lời mở đầu

Ở bài 2 em đã biết int chỉ chứa tới khoảng $2{,}1 \cdot 10^9$, còn long long chứa tới $9\,223\,372\,036\,854\,775\,807 \approx 9{,}2 \cdot 10^{18}$, tức chỉ khoảng 19 chữ số. Vượt quá giới hạn đó máy tính không báo lỗi gì cả, nó lặng lẽ cho ra một số sai (thường là số âm). Hiện tượng này gọi là tràn số, và ở bài 9 mình đã dặn: tràn số là một trong những lý do mất điểm “oan” nhiều nhất khi chấm bài. Vậy nếu đề bảo tính $25! = 15\,511\,210\,043\,330\,985\,984\,000\,000$ (26 chữ số), hay cộng hai số có tới 1000 chữ số thì sao?

Câu trả lời: cất số vào một xâu (bài 8), mỗi ký tự là một chữ số, rồi tự viết các phép cộng, trừ, nhân, chia từng chữ số một, giống hệt cách em đặt tính trên giấy. Kỹ thuật này gọi là số lớn (big int). Python có sẵn số nguyên không giới hạn nên làm rất nhàn, nhưng đi thi HSG bằng C++ thì em phải tự cài. Tin vui là cả bốn phép tính đều ngắn, chỉ 10–15 dòng mỗi phép, và em sẽ thấy chúng quen thuộc đến bất ngờ.

Trong bài này bạn sẽ học: cách biểu diễn số lớn bằng xâu hoặc mảng chữ số đảo ngược; so sánh hai số lớn; cộng có nhớ và trừ có mượn từng cột; nhân số lớn với số nhỏ và nhân hai số lớn với nhau; chia số lớn cho số nhỏ để lấy cả thương lẫn số dư; tính $100!$, $2^{1000}$ và so sánh với số nguyên sẵn có của Python. Có khung chạy tay phép cộng và phép chia, 3 mô phỏng đặt tính từng bước và 8 bài tập kèm gợi ý.

Lộ trình bài viết, bấm vào từng phần để nhảy tới:

Phần 1Biểu diễn và so sánh hai số lớn

Hãy thử chạy đoạn code nhỏ này trong đầu: long long x = 9000000000000000000; x = x * 2;. Kết quả đúng là $1{,}8 \cdot 10^{19}$, nhưng nó vượt giới hạn $9{,}2 \cdot 10^{18}$ nên máy in ra -446744073709551616. Đề bài số lớn thường cho số có hàng trăm, hàng nghìn chữ số, nên không kiểu số nguyên nào của C++ chứa nổi. (double chứa được số rất lớn nhưng chỉ giữ khoảng 15–16 chữ số đầu, phần sau bị làm tròn, xem lại chuyên đề 2.)

Cách 1: lưu bằng xâu. Đọc thẳng số vào string s. Ký tự s[i] là chữ số dạng ký tự, muốn lấy giá trị thì trừ đi '0': s[i] - '0'. Ngược lại, muốn đổi chữ số x (0…9) thành ký tự thì viết char(x + '0'). Cách này đọc, in rất tiện, nhưng có một điều bất tiện: s[0] là chữ số hàng cao nhất, còn hàng đơn vị lại nằm ở cuối xâu.

Cách 2: lưu mảng chữ số đảo ngược. Dùng vector<int> d với d[0] là hàng đơn vị, d[1] hàng chục, d[i] là chữ số nhân với $10^i$. Cách này đẹp khi tính toán: chữ số ở “hàng $10^i$” nằm đúng ở ô $i$, và khi kết quả dài thêm thì chỉ cần push_back vào cuối.

xâu smảng d(đảo ngược)9s[0]0s[1]8s[2]1s[3]7s[4]7d[0]1d[1]8d[2]0d[3]9d[4]chục nghìnnghìntrămchụcđơn vị

Hình 1. Hai cách lưu số 90817. Xâu s giữ đúng thứ tự đọc (s[0] = 9 là hàng chục nghìn), mảng d đảo ngược lại (d[0] = 7 là hàng đơn vị, d[i] ứng với $10^i$).

Nên chọn cách nào?
Trong bài này, phép cộng, trừ, nhân với số nhỏ, chia mình viết thẳng trên xâu (duyệt chỉ số từ cuối về đầu) cho dễ đọc. Phép nhân hai số lớn và bài giai thừa mình dùng mảng đảo ngược, vì ở đó chỉ số “hàng” rất quan trọng. Hai cách tương đương nhau, em quen cách nào dùng cách đó.

So sánh hai số lớn. Có thể so sánh hai xâu bằng < được không? Thử "9" < "10": kết quả là sai, vì xâu so sánh theo thứ tự từ điển, thấy ký tự đầu '9' > '1' là kết luận luôn. Với số thì phải làm hai bước:

  1. Bỏ các chữ số 0 thừa ở đầu ("007" thành "7", "000" thành "0").
  2. Số nào nhiều chữ số hơn thì lớn hơn. Nếu cùng độ dài thì mới so sánh từ điển, vì khi đó chữ số đầu tiên khác nhau quyết định tất cả.
abSo độ dàiSo từ điểnKết luận
9101 < 2(không cần)9 < 10
54321543125 = 5khác nhau ở vị trí 3: 2 > 154321 > 54312
0012399sau khi bỏ số 0: 3 > 2(không cần)123 > 99
00000sau khi bỏ số 0: 1 = 1"0" = "0"bằng nhau

Bảng 1. So sánh hai số lớn: luôn so độ dài trước, cùng độ dài mới so từ điển.

Xem code C++ / Python: so sánh hai số lớn (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

// bỏ các chữ số 0 thừa ở đầu, "000" -> "0"
string chuanHoa(string s){
    int p = 0;
    while(p + 1 < (int)s.size() && s[p] == '0') p++;
    return s.substr(p);
}

// trả về -1 nếu a < b, 0 nếu a = b, 1 nếu a > b
int soSanh(string a, string b){
    a = chuanHoa(a); b = chuanHoa(b);
    if(a.size() != b.size())                  // số nào nhiều chữ số hơn thì lớn hơn
        return a.size() < b.size() ? -1 : 1;
    if(a == b) return 0;
    return a < b ? -1 : 1;                    // cùng độ dài: so sánh từ điển
}

int main(){
    string a, b;
    cin >> a >> b;
    int c = soSanh(a, b);
    if(c < 0) cout << "<";
    else if(c > 0) cout << ">";
    else cout << "=";
    return 0;
}
def chuan_hoa(s):                 # bỏ các chữ số 0 thừa ở đầu
    s = s.lstrip('0')
    return s if s else '0'

def so_sanh(a, b):                # -1, 0 hoặc 1
    a, b = chuan_hoa(a), chuan_hoa(b)
    if len(a) != len(b):          # nhiều chữ số hơn thì lớn hơn
        return -1 if len(a) < len(b) else 1
    if a == b:
        return 0
    return -1 if a < b else 1     # cùng độ dài: so sánh từ điển

a, b = input().split()
print({-1: '<', 0: '=', 1: '>'}[so_sanh(a, b)])
Khuôn mẫu chung của mọi phép tính số lớn
Phép tính nào cũng làm theo đúng cách em đặt tính ở tiểu học: cộng, trừ, nhân đi từ phải sang trái (hàng đơn vị trước) và mang theo số nhớ / số mượn; riêng chia đi từ trái sang phải và mang theo phần dư. Ở mỗi cột, nhớ hai “thần chú”: chữ số viết xuống = s % 10, nhớ sang cột trái = s / 10.

Phần 2Cộng hai số lớn

Em nhớ lại phép đặt tính $9876 + 789$ hồi lớp 2–3: viết hai số thẳng cột theo hàng đơn vị, cộng từ phải sang trái, cột nào được từ 10 trở lên thì “viết hàng đơn vị, nhớ 1 sang cột bên trái”. Máy tính làm y hệt như vậy:

CộtChữ số aChữ số bNhớ cũs = tổngViết (s % 10)Nhớ mới (s / 10)
đơn vị6901551
chục7811661
trăm8711661
nghìn9(hết) 011001
chục nghìn(hết) 0(hết) 01110

Bảng 2. Cộng $9876 + 789$ từng cột. Đọc cột “Viết” từ dưới lên: 10665. Để ý hàng cuối: hai số đã hết chữ số nhưng vẫn còn nhớ 1, nên kết quả dài thêm một chữ số.

Ba điểm em cần để ý khi chuyển sang code:

  • Dùng hai chỉ số i, j bắt đầu từ cuối hai xâu và lùi dần. Số nào hết chữ số trước thì coi như cộng thêm 0.
  • Vòng lặp chạy khi còn chữ số hoặc còn nhớ: while(i >= 0 || j >= 0 || nho > 0). Thiếu điều kiện nho > 0 thì $999 + 1$ ra 000 thay vì 1000.
  • Ta viết kết quả từ hàng đơn vị nên xâu res bị ngược, cuối cùng phải reverse lại.

Em bấm Tiếp để xem từng cột được cộng thế nào, rồi thử tự nhập hai số khác (chẳng hạn 999999 và 1 để thấy số nhớ “chạy” suốt từ phải sang trái):

MÔ PHỎNGĐặt tính cộng có nhớ, từng cột từ phải sang tráiNhập hai số (tối đa 12 chữ số) rồi bấm Tiếp / Tự chạy. Cột vàng là cột đang cộng, ô xanh dương là số nhớ.
cột đang cộng / chữ số vừa viếtnhớ 1 sang cột tráichữ số kết quảcột đã cộng xong
Xem code C++ / Python: cộng hai số lớn (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

string cong(string a, string b){
    string res = "";
    int i = a.size() - 1, j = b.size() - 1, nho = 0;
    while(i >= 0 || j >= 0 || nho > 0){        // còn chữ số hoặc còn nhớ
        int s = nho;
        if(i >= 0) s += a[i--] - '0';           // chữ số của a ở cột này
        if(j >= 0) s += b[j--] - '0';           // chữ số của b ở cột này
        res += char(s % 10 + '0');              // viết hàng đơn vị của s
        nho = s / 10;                           // nhớ sang cột bên trái
    }
    reverse(res.begin(), res.end());            // ta viết từ hàng đơn vị nên phải đảo lại
    return res;
}

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string a, b;
    cin >> a >> b;
    cout << cong(a, b);
    return 0;
}
import sys

def cong(a, b):
    res = []
    i, j, nho = len(a) - 1, len(b) - 1, 0
    while i >= 0 or j >= 0 or nho > 0:     # còn chữ số hoặc còn nhớ
        s = nho
        if i >= 0:
            s += ord(a[i]) - 48; i -= 1     # ord('0') = 48
        if j >= 0:
            s += ord(b[j]) - 48; j -= 1
        res.append(chr(s % 10 + 48))        # viết hàng đơn vị của s
        nho = s // 10                       # nhớ sang cột bên trái
    return ''.join(reversed(res))

a, b = sys.stdin.read().split()
print(cong(a, b))

Bây giờ em chạy tay từng dòng code C++ (cùng thuật toán, viết gọn trong main). Hãy để ý biến nho và xâu res đang dài ra theo thứ tự ngược:

C++▶ Chạy tay từng dòng codeCộng hai xâu số từ phải sang trái: i, j lùi dần, s = chữ số a + chữ số b + nhớ, viết s % 10 và nhớ s / 10 (ô vàng: chữ số đang cộng).
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int main(){
5 string a, b;
6 cin >> a >> b;
7 string res = "";
8 int i = a.size() - 1, j = b.size() - 1, nho = 0;
9 while(i >= 0 || j >= 0 || nho > 0){
10 int s = nho;
11 if(i >= 0) s += a[i--] - '0';
12 if(j >= 0) s += b[j--] - '0';
13 res += char(s % 10 + '0');
14 nho = s / 10;
15 }
16 reverse(res.begin(), res.end());
17 cout << res;
18 return 0;
19}
Lỗi hay gặp khi cộng số lớn
1. Quên trừ '0': cộng thẳng hai ký tự '7' + '8' được 55 + 56 = 111 chứ không phải 15.
2. Quên điều kiện nho > 0 trong vòng lặp, làm mất chữ số 1 ở đầu.
3. Quên reverse ở cuối. Với ví dụ trên em sẽ in ra 56601. Thử ngay với $999 + 1$: in ra 0001 là biết quên đảo.
4. Viết res = char(...) + res (chèn vào đầu xâu) thay vì res += ... rồi đảo: đúng nhưng mỗi lần chèn phải dời cả xâu, số có $10^5$ chữ số sẽ chạy rất chậm ($O(n^2)$).

Phần 3Trừ hai số lớn

Phép trừ cũng đi từ phải sang trái, chỉ khác là thay “nhớ” bằng “mượn”. Ta tạm giả sử $a \ge b$ (lát nữa sẽ xử lý trường hợp ngược lại). Ở mỗi cột:

  • Tính s = chữ số a − chữ số b − mượn (số b hết chữ số thì coi như 0).
  • Nếu s < 0: không đủ trừ, ta mượn 1 chục từ cột bên trái: s += 10, muon = 1. Ngược lại muon = 0.
  • Viết s xuống. Vòng lặp chạy theo số a (số dài hơn), vì $a \ge b$ nên cuối cùng không bao giờ còn mượn.
CộtChữ số aChữ số bMượn cũs = a − b − mượnMượn 1 chục?Viết
đơn vị070−7có → s = 33
chục0(hết) 01−1có → s = 99
trăm0(hết) 01−1có → s = 99
nghìn1(hết) 010không0

Bảng 3. Tính $1000 - 7$. Đọc cột “Viết” từ dưới lên được 0993: có một số 0 thừa ở đầu, bỏ đi còn 993.

Bảng 3 cho thấy hai việc phải làm thêm so với phép cộng:

  • Bỏ số 0 ở đầu kết quả. Hiệu có thể ngắn hơn rất nhiều so với $a$, ví dụ $1000 - 999 = 0001 \to 1$, hay $5 - 5 = 0$. Vì res đang bị ngược nên các số 0 “ở đầu” lại nằm ở cuối res: cứ pop_back khi nó còn dài hơn 1 và chữ số cuối là 0. Nhớ giữ lại ít nhất một chữ số để $5 - 5$ in ra 0 chứ không phải xâu rỗng.
  • Khi $a < b$, kết quả âm. Dùng hàm so sánh ở phần 1, rồi tính $b - a$ và in thêm dấu trừ phía trước: $a - b = -(b - a)$. Ví dụ $100 - 1000 = -(1000 - 100) = -900$.
Xem code C++ / Python: trừ hai số lớn (kể cả kết quả âm) (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

string chuanHoa(string s){
    int p = 0;
    while(p + 1 < (int)s.size() && s[p] == '0') p++;
    return s.substr(p);
}

int soSanh(string a, string b){
    a = chuanHoa(a); b = chuanHoa(b);
    if(a.size() != b.size()) return a.size() < b.size() ? -1 : 1;
    if(a == b) return 0;
    return a < b ? -1 : 1;
}

string tru(string a, string b){                 // yêu cầu a >= b
    string res = "";
    int i = a.size() - 1, j = b.size() - 1, muon = 0;
    while(i >= 0){
        int s = (a[i--] - '0') - muon;
        if(j >= 0) s -= b[j--] - '0';
        if(s < 0){ s += 10; muon = 1; }          // không đủ trừ: mượn 1 chục
        else muon = 0;
        res += char(s + '0');
    }
    while(res.size() > 1 && res.back() == '0')   // bỏ số 0 ở đầu (đang nằm ở cuối res)
        res.pop_back();
    reverse(res.begin(), res.end());
    return res;
}

int main(){
    string a, b;
    cin >> a >> b;
    a = chuanHoa(a); b = chuanHoa(b);
    if(soSanh(a, b) >= 0) cout << tru(a, b);
    else cout << "-" << tru(b, a);              // a < b: a - b = -(b - a)
    return 0;
}
def chuan_hoa(s):
    s = s.lstrip('0')
    return s if s else '0'

def nho_hon(a, b):                    # a < b ? (đã chuẩn hoá)
    return (len(a), a) < (len(b), b)

def tru(a, b):                        # yêu cầu a >= b
    res = []
    i, j, muon = len(a) - 1, len(b) - 1, 0
    while i >= 0:
        s = int(a[i]) - muon; i -= 1
        if j >= 0:
            s -= int(b[j]); j -= 1
        if s < 0:                     # không đủ trừ: mượn 1 chục
            s += 10; muon = 1
        else:
            muon = 0
        res.append(str(s))
    while len(res) > 1 and res[-1] == '0':     # bỏ số 0 ở đầu
        res.pop()
    return ''.join(reversed(res))

a, b = map(chuan_hoa, input().split())
print('-' + tru(b, a) if nho_hon(a, b) else tru(a, b))
Số âm ở đầu vào
Nếu đề cho cả số âm (ví dụ -123), em tách dấu ra riêng rồi quy về phép cộng hoặc trừ hai số không âm: $(-a) + b = b - a$, $(-a) + (-b) = -(a + b)$, $a - (-b) = a + b$… Chỉ cần hai hàm cong, tru và hàm so sánh là đủ mọi trường hợp.

Phần 4Nhân số lớn

a) Nhân số lớn với số nhỏ. Đây là phép nhân hay gặp nhất (tính giai thừa, lũy thừa, đổi hệ cơ số…). Số $a$ rất dài, còn $k$ vừa với long long, chẳng hạn $k \le 10^9$. Ta vẫn đi từ phải sang trái, nhưng ở mỗi cột nhân cả số $k$ với một chữ số: s = chữ số × k + nhớ, viết s % 10, nhớ s / 10.

CộtChữ số as = chữ số × 25 + nhớViếtNhớ
đơn vị77 × 25 + 0 = 175517
chục44 × 25 + 17 = 117711
trăm33 × 25 + 11 = 8668
(hết chữ số)viết nốt nhớ 880

Bảng 4. Tính $347 \times 25$: đọc cột “Viết” từ dưới lên được 8675. Số nhớ ở đây lên tới 17, không còn chỉ là 0 hay 1 như phép cộng.

Hai chỗ khác phép cộng: số nhớ có thể lớn hơn 9 (nên hết chữ số rồi phải dùng vòng while viết nốt từng chữ số của nhớ), và s có thể tới $9 \cdot 10^9 + \text{nhớ}$ nên phải dùng long long. Nhân với $k = 0$ thì trả về "0" luôn để khỏi in ra một dãy số 0.

Xem code C++ / Python: nhân số lớn với số nhỏ (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

string nhanNho(string a, long long k){          // a là số lớn, 0 <= k <= 10^9
    if(k == 0) return "0";
    string res = "";
    long long nho = 0;
    for(int i = a.size() - 1; i >= 0; i--){
        long long s = (a[i] - '0') * k + nho;   // cả chữ số nhân với k, cộng nhớ
        res += char(s % 10 + '0');
        nho = s / 10;                           // nhớ có thể lớn hơn 9!
    }
    while(nho > 0){                             // viết nốt phần nhớ còn lại
        res += char(nho % 10 + '0');
        nho /= 10;
    }
    reverse(res.begin(), res.end());
    return res;
}

int main(){
    string a; long long k;
    cin >> a >> k;
    cout << nhanNho(a, k);
    return 0;
}
def nhan_nho(a, k):                   # a là xâu số lớn, k là số nhỏ
    if k == 0:
        return '0'
    res, nho = [], 0
    for ch in reversed(a):
        s = int(ch) * k + nho         # cả chữ số nhân với k, cộng nhớ
        res.append(str(s % 10))
        nho = s // 10                 # nhớ có thể lớn hơn 9!
    while nho > 0:                    # viết nốt phần nhớ còn lại
        res.append(str(nho % 10))
        nho //= 10
    return ''.join(reversed(res))

a, k = input().split()
print(nhan_nho(a, int(k)))

b) Nhân hai số lớn. Khi cả hai số đều dài, ta quay về phép đặt tính nhân nhiều chữ số: nhân $a$ với từng chữ số của $b$, mỗi hàng viết lùi sang trái một ô, rồi cộng tất cả lại. Có một mẹo giúp code ngắn hẳn: đánh số chữ số từ phải sang, chữ số $a_i$ mang giá trị $a_i \cdot 10^i$, chữ số $b_j$ mang giá trị $b_j \cdot 10^j$, nên tích của chúng là $a_i b_j \cdot 10^{i+j}$, tức thuộc về hàng $i + j$. Vậy:

  1. Tạo mảng res gồm $n + m$ ô toàn 0 (tích của số $n$ chữ số và số $m$ chữ số có không quá $n + m$ chữ số).
  2. Với mọi cặp $(i, j)$: res[i + j] += a[i] * b[j]. Chưa cần quan tâm nhớ, cứ cộng dồn.
  3. Cuối cùng xử lý nhớ một lượt từ ô 0 lên: res[k + 1] += res[k] / 10; res[k] %= 10;, rồi bỏ số 0 ở đầu.
Ô (hàng)Các tích cộng dồn vàores trước khi xử lý nhớXử lý nhớChữ số
0 (đơn vị)7·85656 → viết 6, nhớ 56
1 (chục)4·8 + 7·632 + 42 = 7474 + 5 = 79 → viết 9, nhớ 79
2 (trăm)3·8 + 4·624 + 24 = 4848 + 7 = 55 → viết 5, nhớ 55
3 (nghìn)3·61818 + 5 = 23 → viết 3, nhớ 23
4 (chục nghìn)(không có)00 + 2 = 22

Bảng 5. Tính $347 \times 68$ ($a_0 = 7, a_1 = 4, a_2 = 3$; $b_0 = 8, b_1 = 6$). Đọc cột “Chữ số” từ dưới lên: 23596.

Mô phỏng dưới đây vẽ đúng bảng đặt tính: mỗi hàng giữa là một chữ số của $b$ nhân với $a$, các tích rơi vào ô res[i + j] thẳng cột. Em xem cả bước xử lý nhớ ở cuối nhé:

MÔ PHỎNGNhân hai số lớn: cộng dồn tích vào res[i + j] rồi xử lý nhớNhập a (tối đa 5 chữ số) và b (tối đa 4 chữ số). Mỗi hàng giữa là b[j] nhân với từng chữ số của a, lùi sang trái j ô.
chữ số a[i] / tích vừa tính / ô đang xử lýchữ số b[j]chữ số kết quảđã xong
Xem code C++ / Python: nhân hai số lớn trong $O(n \cdot m)$ (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

string nhan(string a, string b){
    int n = a.size(), m = b.size();
    vector<long long> res(n + m, 0);
    for(int i = 0; i < n; i++)                  // a[n-1-i] là chữ số hàng 10^i của a
        for(int j = 0; j < m; j++)              // b[m-1-j] là chữ số hàng 10^j của b
            res[i + j] += (a[n - 1 - i] - '0') * (b[m - 1 - j] - '0');
    for(int k = 0; k + 1 < n + m; k++){         // xử lý nhớ một lượt từ hàng đơn vị
        res[k + 1] += res[k] / 10;
        res[k] %= 10;
    }
    int k = n + m - 1;
    while(k > 0 && res[k] == 0) k--;            // bỏ số 0 ở đầu
    string s = "";
    for(; k >= 0; k--) s += char(res[k] + '0');
    return s;
}

int main(){
    string a, b;
    cin >> a >> b;
    cout << nhan(a, b);
    return 0;
}
def nhan(a, b):
    n, m = len(a), len(b)
    A = [int(c) for c in reversed(a)]      # A[i]: chữ số hàng 10^i
    B = [int(c) for c in reversed(b)]
    res = [0] * (n + m)
    for i in range(n):
        for j in range(m):
            res[i + j] += A[i] * B[j]      # cộng dồn vào ô i + j
    for k in range(n + m - 1):             # xử lý nhớ
        res[k + 1] += res[k] // 10
        res[k] %= 10
    k = n + m - 1
    while k > 0 and res[k] == 0:           # bỏ số 0 ở đầu
        k -= 1
    return ''.join(str(res[t]) for t in range(k, -1, -1))

a, b = input().split()
print(nhan(a, b))
Vì sao dùng long long cho mảng res?
Mỗi tích chữ số tối đa $9 \cdot 9 = 81$. Ô res[i + j] nhận tối đa $\min(n, m)$ tích, nên với hai số 10000 chữ số, một ô có thể tới $81 \cdot 10^4 \approx 8 \cdot 10^5$, int vẫn chứa được. Nhưng nếu sau này em cải tiến lưu mỗi ô 4 hay 9 chữ số cho nhanh hơn thì tích mỗi cặp đã lên tới $10^8$ hay $10^{18}$, nên quen tay dùng long long cho an toàn. Độ phức tạp $O(n \cdot m)$: hai số 2000 chữ số mất $4 \cdot 10^6$ phép nhân, rất nhanh.

Phần 5Chia số lớn cho số nhỏ và ứng dụng

Phép chia là phép duy nhất đi từ trái sang phải, đúng như đặt tính chia ở lớp 3–4: hạ từng chữ số xuống, ghép vào sau phần dư cũ, chia lấy một chữ số của thương, phần dư mới lại được mang sang bước sau. Viết bằng công thức, với phần dư r (ban đầu 0) và chữ số tiếp theo c:

r = r * 10 + c;      // hạ chữ số c xuống
thương += r / k;     // một chữ số của thương
r = r % k;           // phần dư mang sang

Vì luôn có $r < k$ trước khi hạ chữ số, nên $r \cdot 10 + c < 10k$ và r / k luôn là đúng một chữ số (0…9). Với $k \le 10^9$ thì $r \cdot 10 + c < 10^{10}$, phải dùng long long.

Chữ số hạ xuốngr = r·10 + cChữ số thương r / 7Dư r % 7
10·10 + 1 = 101
21·10 + 2 = 1215
35·10 + 3 = 5374
44·10 + 4 = 4462

Bảng 6. Tính $1234 : 7$. Cột thương đọc từ trên xuống là 0176, bỏ số 0 ở đầu được 176, dư 2. Kiểm tra: $176 \cdot 7 + 2 = 1234$.

MÔ PHỎNGChia số lớn cho số nhỏ: từ trái sang phải, mang phần dư xuốngNhập số bị chia a (tối đa 14 chữ số) và số chia k (1 … 9999).
chữ số vừa hạ xuốngchữ số thương vừa viếtthươngphần dư rđã xử lý / số 0 thừa
Xem code C++ / Python: chia số lớn cho số nhỏ (thương và số dư) (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

// trả về thương a / k, phần dư cất vào r (1 <= k <= 10^9)
string chiaNho(string a, long long k, long long &r){
    string q = "";
    r = 0;
    for(int i = 0; i < (int)a.size(); i++){       // từ TRÁI sang phải
        r = r * 10 + (a[i] - '0');                // hạ chữ số tiếp theo xuống
        q += char(r / k + '0');                   // r < 10k nên r / k luôn là 1 chữ số
        r = r % k;                                // phần dư mang sang bước sau
    }
    int p = 0;
    while(p + 1 < (int)q.size() && q[p] == '0') p++;
    return q.substr(p);                           // bỏ số 0 ở đầu thương
}

int main(){
    string a; long long k, r;
    cin >> a >> k;
    string q = chiaNho(a, k, r);
    cout << q << " " << r;
    return 0;
}
def chia_nho(a, k):                   # trả về (thương, dư)
    q, r = [], 0
    for ch in a:                      # từ TRÁI sang phải
        r = r * 10 + int(ch)          # hạ chữ số tiếp theo xuống
        q.append(str(r // k))         # r < 10k nên luôn là 1 chữ số
        r %= k                        # phần dư mang sang bước sau
    q = ''.join(q).lstrip('0') or '0'
    return q, r

a, k = input().split()
print(*chia_nho(a, int(k)))
C++▶ Chạy tay từng dòng codeChia từ trái sang phải: hạ từng chữ số xuống ghép vào phần dư r, viết r / k vào thương q, giữ lại r % k (ô vàng: chữ số đang hạ).
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int main(){
5 string a; long long k;
6 cin >> a >> k;
7 string q = ""; long long r = 0;
8 for(int i = 0; i < (int)a.size(); i++){
9 r = r * 10 + (a[i] - '0');
10 q += char(r / k + '0');
11 r = r % k;
12 }
13 int p = 0;
14 while(p + 1 < (int)q.size() && q[p] == '0') p++;
15 cout << q.substr(p) << " " << r;
16 return 0;
17}
Chỉ cần số dư? Không cần lưu thương
Nhiều bài chỉ hỏi “số lớn $a$ chia $k$ dư bao nhiêu” hay “$a$ có chia hết cho $k$ không”. Khi đó bỏ hẳn xâu thương, chỉ giữ r = (r * 10 + c) % k. Đây chính là tính chất đồng dư: $(x \cdot 10 + c) \bmod k = ((x \bmod k) \cdot 10 + c) \bmod k$, nên ta không bao giờ phải lưu cả số $a$. Em sẽ gặp lại ý này nhiều lần trong số học và trong toán modulo.

Ứng dụng: tính $100!$ và $2^{1000}$. Giai thừa $n! = 1 \cdot 2 \cdot 3 \cdots n$ lớn cực nhanh: $20!$ còn vừa long long, $21!$ đã tràn, còn $100!$ có tới 158 chữ số. Nhưng mỗi bước ta chỉ nhân số lớn hiện tại với một số nhỏ $x \le n$, đúng phép nhân số lớn với số nhỏ ở phần 4. Lần này mình lưu bằng mảng đảo ngược d (d[0] là hàng đơn vị) để kết quả dài thêm thì chỉ việc push_back:

Xem code C++ / Python: tính n! bằng mảng chữ số đảo ngược (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<int> d = {1};                     // d[0] là hàng đơn vị; ban đầu số 1
    for(int x = 2; x <= n; x++){             // nhân dần với 2, 3, ..., n
        long long nho = 0;
        for(int i = 0; i < (int)d.size(); i++){
            long long s = (long long)d[i] * x + nho;
            d[i] = s % 10;
            nho = s / 10;
        }
        while(nho > 0){ d.push_back(nho % 10); nho /= 10; }
    }
    for(int i = d.size() - 1; i >= 0; i--) cout << d[i];   // in ngược lại
    return 0;
}
n = int(input())
f = 1
for x in range(2, n + 1):     # Python tự lo số lớn, chỉ việc nhân
    f *= x
print(f)

Tương tự, $2^{1000}$ chỉ là nhân đôi 1000 lần liên tiếp, mỗi lần là một phép nhân số lớn với số nhỏ $k = 2$. Kết quả có 302 chữ số, tổng các chữ số là 1366 (em sẽ tự làm ở bài tập 8). Trong Python, cả hai việc chỉ là một dòng: print(math.factorial(100)) và print(2 ** 1000).

Python có số lớn sẵn, nhưng có một cái bẫy
Kiểu int của Python không bao giờ tràn: a + b, a - b, a * b, a // k, a % k, a ** n đều đúng với số hàng nghìn chữ số. Tuy nhiên từ Python 3.11, việc đổi xâu sang số (int(s)) hoặc in số ra (str(x), print(x)) sẽ báo lỗi nếu số dài hơn 4300 chữ số. Khi đề cho số rất dài, em thêm hai dòng này ở đầu chương trình:
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)   # 0 = bỏ giới hạn
Thêm nữa, đổi một xâu $10^6$ chữ số sang int khá chậm (cỡ một giây). Nếu chỉ cần số dư thì duyệt từng chữ số như trên vẫn nhanh hơn.
Phép tínhHướng điMang theoĐộ phức tạpPython
So sánhđộ dài, rồi trái → phải(không)$O(n)$a < b
Cộngphải → tráinhớ 0 hoặc 1$O(n)$a + b
Trừ ($a \ge b$)phải → tráimượn 0 hoặc 1$O(n)$a - b
Nhân với số nhỏ $k$phải → tráinhớ có thể lớn$O(n)$a * k
Nhân hai số lớnmọi cặp $(i, j)$ vào ô $i + j$xử lý nhớ một lượt cuối$O(n \cdot m)$a * b
Chia cho số nhỏ $k$trái → phảiphần dư $r < k$$O(n)$a // k, a % k

Bảng 7. Tóm tắt các phép tính số lớn. $n$, $m$ là số chữ số. Mọi phép (trừ so sánh và chia) cần thêm bước bỏ số 0 ở đầu kết quả.

Khi đi thi: trước khi lao vào số lớn, em hãy ước lượng đáp án có vừa long long không. Nhiều bài trông “to” nhưng chỉ hỏi số dư (dùng phép đồng dư), hoặc đáp án chỉ tới $10^{18}$ (dùng long long là đủ). Chỉ khi đề thật sự yêu cầu in ra toàn bộ một số quá 19 chữ số thì mới cần cài số lớn. Nếu thi bằng Python thì cứ dùng int, nhớ bỏ giới hạn 4300 chữ số.

Luyện tập trên web
Các bài dưới đây trên blog đều có lời giải chi tiết bằng C++ và Python, xếp từ dễ tới khó. Em hãy tự làm trước rồi mới xem lời giải nhé.Xem toàn bộ bài cùng chủ đề: nhãn Số lớn (Big Int).

Bài tậpLuyện số nguyên lớn

Bài 1Cộng hai số lớnDễ

Cho hai số tự nhiên $a$ và $b$, mỗi số có không quá $10^5$ chữ số và không có chữ số 0 thừa ở đầu. Hãy in ra $a + b$.

Input
99999999999999999999 1
Output
100000000000000000000

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Đúng hàm cong ở phần 2: cộng từng cột từ phải sang trái, nhớ s / 10, viết s % 10, cuối cùng đảo ngược. Ở ví dụ, hai mươi chữ số 9 cộng 1: cột nào cũng được 10, viết 0 nhớ 1, và hết chữ số vẫn còn nhớ 1 nên kết quả có 21 chữ số. Với Python, chỉ cần print(int(a) + int(b)), nhưng nhớ bỏ giới hạn 4300 chữ số vì số có tới $10^5$ chữ số.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string cong(string a, string b){
    string res = "";
    int i = a.size() - 1, j = b.size() - 1, nho = 0;
    while(i >= 0 || j >= 0 || nho > 0){        // còn chữ số hoặc còn nhớ
        int s = nho;
        if(i >= 0) s += a[i--] - '0';           // chữ số của a ở cột này
        if(j >= 0) s += b[j--] - '0';           // chữ số của b ở cột này
        res += char(s % 10 + '0');              // viết hàng đơn vị của s
        nho = s / 10;                           // nhớ sang cột bên trái
    }
    reverse(res.begin(), res.end());            // ta viết từ hàng đơn vị nên phải đảo lại
    return res;
}

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string a, b;
    cin >> a >> b;
    cout << cong(a, b);
    return 0;
}
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)       # bỏ giới hạn 4300 chữ số (Python 3.11+)

a, b = sys.stdin.read().split()
print(int(a) + int(b))
Bài 2So sánh hai số lớnDễ

Cho hai số tự nhiên $a$ và $b$, mỗi số có không quá $10^5$ chữ số. Lưu ý: số có thể có các chữ số 0 thừa ở đầu (ví dụ 00123 chính là 123). In ra <, > hoặc = tuỳ theo $a$ nhỏ hơn, lớn hơn hay bằng $b$.

Input
000123 99
Output
>

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Bỏ số 0 ở đầu trước (nhớ giữ lại một chữ số nếu số toàn 0), rồi so sánh độ dài, cùng độ dài mới so sánh từ điển. Ví dụ: 123 có 3 chữ số, 99 có 2 chữ số nên $a > b$. Trong Python có mẹo gọn: so sánh cặp (len(a), a) với (len(b), b), vì Python so sánh cặp theo phần tử đầu trước. Dùng int(a) < int(b) cũng đúng, nhưng phải bỏ giới hạn 4300 chữ số và chậm hơn.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string chuanHoa(string s){
    int p = 0;
    while(p + 1 < (int)s.size() && s[p] == '0') p++;
    return s.substr(p);
}

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string a, b;
    cin >> a >> b;
    a = chuanHoa(a); b = chuanHoa(b);
    if(a.size() != b.size()) cout << (a.size() < b.size() ? "<" : ">");
    else if(a == b) cout << "=";
    else cout << (a < b ? "<" : ">");
    return 0;
}
import sys

a, b = sys.stdin.read().split()
a = a.lstrip('0') or '0'
b = b.lstrip('0') or '0'
x, y = (len(a), a), (len(b), b)     # so độ dài trước, rồi so từ điển
print('<' if x < y else ('>' if x > y else '='))
Bài 3Hiệu hai số lớnDễ

Cho hai số tự nhiên $a$ và $b$ (mỗi số không quá $10^5$ chữ số, không có số 0 thừa ở đầu). In ra $a - b$. Kết quả có thể âm.

Input
100 1000
Output
-900

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Dùng hàm so sánh ở phần 1. Nếu $a \ge b$ thì in tru(a, b), ngược lại in dấu - rồi đến tru(b, a). Đừng quên bỏ số 0 ở đầu kết quả: $1000 - 100 = 0900 \to 900$. Và khi $a = b$ phải in 0, không in -0 hay xâu rỗng. Python: print(int(a) - int(b)) tự xử lý cả dấu âm.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string chuanHoa(string s){
    int p = 0;
    while(p + 1 < (int)s.size() && s[p] == '0') p++;
    return s.substr(p);
}

int soSanh(string a, string b){
    a = chuanHoa(a); b = chuanHoa(b);
    if(a.size() != b.size()) return a.size() < b.size() ? -1 : 1;
    if(a == b) return 0;
    return a < b ? -1 : 1;
}

string tru(string a, string b){                 // yêu cầu a >= b
    string res = "";
    int i = a.size() - 1, j = b.size() - 1, muon = 0;
    while(i >= 0){
        int s = (a[i--] - '0') - muon;
        if(j >= 0) s -= b[j--] - '0';
        if(s < 0){ s += 10; muon = 1; }          // không đủ trừ: mượn 1 chục
        else muon = 0;
        res += char(s + '0');
    }
    while(res.size() > 1 && res.back() == '0')   // bỏ số 0 ở đầu (đang nằm ở cuối res)
        res.pop_back();
    reverse(res.begin(), res.end());
    return res;
}

int main(){
    string a, b;
    cin >> a >> b;
    a = chuanHoa(a); b = chuanHoa(b);
    if(soSanh(a, b) >= 0) cout << tru(a, b);
    else cout << "-" << tru(b, a);              // a < b: a - b = -(b - a)
    return 0;
}
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)

a, b = sys.stdin.read().split()
print(int(a) - int(b))              # Python tự xử lý cả số âm
Bài 4Nhân số lớn với số nhỏDễ

Cho số tự nhiên $a$ có không quá $10^5$ chữ số và số nguyên $k$ ($0 \le k \le 10^9$). In ra $a \cdot k$.

Input
99999999999999999999 99
Output
9899999999999999999901

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Hàm nhanNho ở phần 4: mỗi cột tính s = chữ số × k + nhớ. Số nhớ có thể tới gần $10^9$, nên hết chữ số rồi phải dùng vòng while viết nốt từng chữ số của nhớ, và mọi biến phải là long long. Trường hợp $k = 0$ hoặc $a = 0$ phải in đúng 0. Ví dụ: $(10^{20} - 1) \cdot 99 = 99 \cdot 10^{20} - 99$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string nhanNho(string a, long long k){          // a là số lớn, 0 <= k <= 10^9
    if(k == 0) return "0";
    string res = "";
    long long nho = 0;
    for(int i = a.size() - 1; i >= 0; i--){
        long long s = (a[i] - '0') * k + nho;   // cả chữ số nhân với k, cộng nhớ
        res += char(s % 10 + '0');
        nho = s / 10;                           // nhớ có thể lớn hơn 9!
    }
    while(nho > 0){                             // viết nốt phần nhớ còn lại
        res += char(nho % 10 + '0');
        nho /= 10;
    }
    reverse(res.begin(), res.end());
    return res;
}

int main(){
    string a; long long k;
    cin >> a >> k;
    cout << nhanNho(a, k);
    return 0;
}
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)

a, k = sys.stdin.read().split()
print(int(a) * int(k))
Bài 5Số dư của số khổng lồTrung bình

Cho số tự nhiên $a$ có không quá $10^6$ chữ số và số nguyên $k$ ($1 \le k \le 10^9$). In ra số dư khi chia $a$ cho $k$.

Input
123456789123456789 11
Output
0

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Không cần tính thương: duyệt từng chữ số từ trái sang phải và giữ r = (r * 10 + c) % k (mẹo cuối phần 5). Vì $r < k \le 10^9$ nên $r \cdot 10 + c$ vừa long long. Python có thể viết int(a) % k, nhưng đổi xâu một triệu chữ số sang số vừa phải bỏ giới hạn 4300 chữ số vừa mất cỡ một giây, nên duyệt từng chữ số an toàn hơn.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string a; long long k;
    cin >> a >> k;
    long long r = 0;
    for(char c : a) r = (r * 10 + (c - '0')) % k;   // chỉ giữ phần dư
    cout << r;
    return 0;
}
import sys

a, k = sys.stdin.read().split()
k = int(k)
r = 0
for ch in a:                        # giống chia số lớn, nhưng không cần thương
    r = (r * 10 + ord(ch) - 48) % k
print(r)
Bài 6Giai thừa lớnTrung bình

Cho số nguyên $n$ ($0 \le n \le 500$). In ra giá trị chính xác của $n! = 1 \cdot 2 \cdots n$ (quy ước $0! = 1$).

Input
25
Output
15511210043330985984000000

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Bắt đầu từ số lớn bằng 1, lần lượt nhân với $2, 3, \ldots, n$, mỗi lần là phép nhân số lớn với số nhỏ. Lưu bằng mảng chữ số đảo ngược để kết quả dài thêm chỉ việc push_back. $500!$ có 1135 chữ số, mỗi lần nhân duyệt hết mảng nên tổng chỉ cỡ $500 \cdot 1135$ phép tính. Python: nhân int bình thường, hoặc math.factorial(n).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<int> d = {1};                     // d[0] là hàng đơn vị; ban đầu số 1
    for(int x = 2; x <= n; x++){             // nhân dần với 2, 3, ..., n
        long long nho = 0;
        for(int i = 0; i < (int)d.size(); i++){
            long long s = (long long)d[i] * x + nho;
            d[i] = s % 10;
            nho = s / 10;
        }
        while(nho > 0){ d.push_back(nho % 10); nho /= 10; }
    }
    for(int i = d.size() - 1; i >= 0; i--) cout << d[i];   // in ngược lại
    return 0;
}
n = int(input())
f = 1
for x in range(2, n + 1):
    f *= x
print(f)          # 500! có 1135 chữ số, vẫn dưới giới hạn 4300 khi in
Bài 7Nhân hai số lớnTrung bình

Cho hai số tự nhiên $a$ và $b$, mỗi số có không quá $2000$ chữ số (không có số 0 thừa ở đầu). In ra $a \cdot b$.

Input
12345678901234567890 98765432109876543210
Output
1219326311370217952237463801111263526900

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Phần 4b: đảo hai số thành mảng chữ số, cộng dồn a[i] * b[j] vào res[i + j], cuối cùng xử lý nhớ một lượt và bỏ số 0 ở đầu. Tổng cộng $2000 \cdot 2000 = 4 \cdot 10^6$ phép nhân. Nhớ trường hợp một số bằng 0: kết quả phải là 0 chứ không phải 0000. Python: int(a) * int(b), tích có thể tới 4000 chữ số nên vẫn nên bỏ giới hạn 4300 cho chắc.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string nhan(string a, string b){
    int n = a.size(), m = b.size();
    vector<long long> res(n + m, 0);
    for(int i = 0; i < n; i++)                  // a[n-1-i] là chữ số hàng 10^i của a
        for(int j = 0; j < m; j++)              // b[m-1-j] là chữ số hàng 10^j của b
            res[i + j] += (a[n - 1 - i] - '0') * (b[m - 1 - j] - '0');
    for(int k = 0; k + 1 < n + m; k++){         // xử lý nhớ một lượt từ hàng đơn vị
        res[k + 1] += res[k] / 10;
        res[k] %= 10;
    }
    int k = n + m - 1;
    while(k > 0 && res[k] == 0) k--;            // bỏ số 0 ở đầu
    string s = "";
    for(; k >= 0; k--) s += char(res[k] + '0');
    return s;
}

int main(){
    string a, b;
    cin >> a >> b;
    cout << nhan(a, b);
    return 0;
}
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)

a, b = sys.stdin.read().split()
print(int(a) * int(b))
Bài 8Chữ số của 2 mũ nKhá

Cho số nguyên $n$ ($0 \le n \le 30000$). Gọi $X = 2^n$. In ra hai số: số chữ số của $X$ và tổng các chữ số của $X$.

Input
15
Output
5 26

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
$2^{15} = 32768$ có 5 chữ số, tổng $3 + 2 + 7 + 6 + 8 = 26$. Bắt đầu từ mảng chữ số {1}, nhân đôi $n$ lần (nhân số lớn với số nhỏ $k = 2$, nhớ chỉ là 0 hoặc 1). $2^{30000}$ có 9031 chữ số, tổng số phép tính cỡ $30000 \cdot 9031 / 2 \approx 1{,}4 \cdot 10^8$, C++ chạy chưa tới nửa giây. (Muốn nhanh hơn nữa, có thể nhân với $2^{20}$ mỗi lần, hoặc lưu mỗi ô 9 chữ số.) Python làm được trong một dòng str(2 ** n), nhưng $9031 > 4300$ chữ số nên bắt buộc phải bỏ giới hạn, nếu không chương trình báo lỗi ValueError.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<int> d = {1};                  // d[0] là hàng đơn vị
    for(int t = 0; t < n; t++){           // nhân đôi n lần
        int nho = 0;
        for(int i = 0; i < (int)d.size(); i++){
            int s = d[i] * 2 + nho;
            d[i] = s % 10;
            nho = s / 10;
        }
        if(nho) d.push_back(nho);
    }
    int tong = 0;
    for(int x : d) tong += x;
    cout << d.size() << " " << tong;
    return 0;
}
import sys
if hasattr(sys, 'set_int_max_str_digits'):
    sys.set_int_max_str_digits(0)       # 2^30000 có 9031 chữ số

n = int(input())
s = str(2 ** n)
print(len(s), sum(map(int, s)))
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 😇
Bản quyền thuộc về Thầy Nguyễn Việt Nam Sơn. Bài viết, lời giải, hình minh hoạ và mã nguồn trên trang là công sức biên soạn của thầy qua nhiều năm giảng dạy. Vui lòng không sao chép, đăng lại hay sử dụng dưới mọi hình thức khi chưa được sự đồng ý của tác giả. Nếu thấy hữu ích, hãy chia sẻ bằng đường link bài gốc nhé. Cảm ơn bạn!
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
  • Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
  • Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
  • Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
  • Dễ nản giữa chừng khi không có ai theo sát.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
  • Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
  • Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
  • Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.

Nhận xét