Khoá học C++ nâng cao
Bài học: Số Học Cơ Bản & Chuyên Sâu
🏆 +100 XP tiềm năng Thoát

Số Học Cơ Bản & Chuyên Sâu

2 khối nội dung 45 phút học tập MỞ BẢNG TRẮNG

Số Học Cơ Bản & Chuyên Sâu

Nội dung bài học

1. Khái niệm & bản chất của tối ưu số học trong lập trình thi đấu

Số học trong lập trình thi đấu (Competitive Programming) không đơn thuần là các phép toán số học cơ bản, mà là nghệ thuật khai thác các cấu trúc đại số và tính chất chia hết để giảm độ phức tạp tính toán từ hàm mũ $\mathcal{O}(2^N)$ hoặc đa thức $\mathcal{O}(N)$ xuống thời gian logarit $\mathcal{O}(\log N)$ hoặc $\mathcal{O}(1)$.

Ở Level 2, ta không dừng lại ở việc kiểm tra nguyên tố hay tìm ước số đơn lẻ, mà tập trung vào Tái kết hợp & Xử lý đa truy vấn với khối lượng dữ liệu cực lớn:

  • Khai thác thuật toán Euclid: Rút gọn không gian bài toán, tìm ước chung lớn nhất $\gcd(A, B)$ trong $\mathcal{O}(\log(\min(A, B)))$ và giải phương trình Diophantine nghiệm nguyên qua thuật toán Euclid mở rộng.
  • Sàng ước số nguyên tố nhỏ nhất (SPF): Tiền xử lý $\mathcal{O}(MAX \log \log MAX)$ để phân tích hàng triệu số thành thừa số nguyên tố với tốc độ $\mathcal{O}(\log N)$ mỗi số.
  • Sàng nguyên tố phân đoạn (Segmented Sieve): Vượt qua ranh giới bộ nhớ RAM để tìm chính xác mọi số nguyên tố trong đoạn $[L, R]$ với $R \le 10^{12}$ và $R - L \le 10^6$.

2. Thuật toán Euclid & Bản chất toán học của GCD / LCM

2.1. Định lý Euclid & Tính chất bất biến (Invariant)

Định lý: Với mọi cặp số nguyên không âm $A, B$ ($B \ne 0$), ước chung lớn nhất của chúng luôn thỏa mãn: $$\gcd(A, B) = \gcd(B, A \bmod B)$$ $$\gcd(A, 0) = A$$

Mỗi bước lấy dư $A \bmod B$ thực chất là loại bỏ tất cả các bội số của $B$ ra khỏi $A$, giữ lại phần dư $R < B$. Khi số dư bằng $0$, số chia cuối cùng chính là $\gcd(A, B)$.

Ví dụ minh họa 1: Tìm $\gcd(105, 45)$

  1. $\gcd(105, 45) = \gcd(45, 105 \bmod 45) = \gcd(45, 15)$
  2. $\gcd(45, 15) = \gcd(15, 45 \bmod 15) = \gcd(15, 0) = \mathbf{15}$
Bước $k$ Số Bị Chia $A$ Số Chia $B$ Phép Chia Lấy Dư $A \bmod B$ Trạng Thái $(A_{next}, B_{next})$
1 105 45 $105 \bmod 45 = 15$ $(45, 15)$
2 45 15 $45 \bmod 15 = 0$ $(15, 0)$
Kết quả 15 0 Dừng (B = 0) $\gcd = 15$

2.2. Mẫu cài đặt C++ chuẩn thi đấu (Không đệ quy)

long long gcd_calc(long long a, long long b) {
    while (b != 0) {
        long long r = a % b;
        a = b;
        b = r;
    }
    return a;
}

Phạm vi áp dụng: Mẫu gcd_calc ở trên nhận các số không âm. Nếu dữ liệu có thể chứa số âm, hãy chuẩn hóa bằng abs trước khi gọi; GCD được quy ước là số không âm.

2.3. Bẫy lỗi tràn số khi tính Bội chung nhỏ nhất ($\text{lcm}$)

Công thức toán học: $\text{lcm}(A, B) = \frac{A \times B}{\gcd(A, B)}$.

Cảnh báo bẫy lỗi: BẪY TRÀN TÍCH A * B TRONG PHÉP TÍNH LCM

Nếu viết return (a * b) / gcd(a, b);, khi $A, B \approx 10^{10}$, tích $A \times B \approx 10^{20}$ sẽ vượt quá giới hạn $9.22 \times 10^{18}$ của kiểu long long $\implies$ TRÀN SỐ ÂM / KẾT QUẢ SAI HOÀN TOÀN.

Quy tắc an toàn tuyệt đối: Luôn chia trước khi nhân vì $A$ luôn chia hết cho $\gcd(A, B)$: cpp long long lcm_calc(long long a, long long b) { if (a == 0 || b == 0) return 0; return (a / gcd_calc(a, b)) * b; }


Sơ đồ cơ chế Sàng SPF

3. Sàng ước số nguyên tố nhỏ nhất (SPF — Smallest Prime Factor)

3.1. Động lực: Xử lý $10^5$ truy vấn phân tích thừa số nguyên tố

Trong các kỳ thi HSG, ta thường gặp bài toán: Cho $Q = 10^5$ truy vấn, mỗi truy vấn cho một số $N \le 10^6$, yêu cầu phân tích $N$ thành thừa số nguyên tố.

  • Cách ngây thơ $\mathcal{O}(\sqrt{N})$: Mỗi truy vấn thử chia đến $\sqrt{N} \implies \text{Tổng thời gian } \mathcal{O}(Q \sqrt{N}) \approx 10^5 \times 10^3 = 10^8 \text{ phép tính} \implies$ Nguy cơ TLE.
  • Kỹ thuật Sàng SPF: Tiền xử lý 1 lần mảng spf[x] lưu ước số nguyên tố nhỏ nhất của $x$ trong $\mathcal{O}(MAX \log \log MAX)$. Khi có truy vấn $N$, ta chỉ việc nhảy liên tiếp theo spf[N] $\implies$ Mỗi truy vấn chỉ mất $\mathcal{O}(\log N)$ bước!

3.2. Bảng mô phỏng phân tích số $N = 84$ bằng SPF

  • spf[84] = 2 $\implies 84 / 2 = 42$
  • spf[42] = 2 $\implies 42 / 2 = 21$
  • spf[21] = 3 $\implies 21 / 3 = 7$
  • spf[7] = 7 $\implies 7 / 7 = 1$ (Dừng) $$\implies 84 = 2^2 \times 3^1 \times 7^1 \quad (\text{Chỉ mất đúng 4 bước chia!})$$
Bước $k$ Giá Trị $N$ Hiện Tại spf[N] (Ước NT nhỏ nhất) $N_{next} = N / \text{spf}[N]$ Thừa Số Thu Được
1 84 2 42 $2$
2 42 2 21 $2$
3 21 3 7 $3$
4 7 7 1 $7$

3.3. Cài đặt C++ Sàng SPF & Phân tích thừa số tối ưu

const int MAXN = 1000000;
int spf[MAXN + 1];

void sieve_spf() {
    for (int i = 1; i <= MAXN; ++i) spf[i] = i;
    for (int i = 2; i * i <= MAXN; ++i) {
        if (spf[i] == i) { // i là số nguyên tố
            for (int j = i * i; j <= MAXN; j += i) {
                if (spf[j] == j) spf[j] = i; // Gán ước NT nhỏ nhất đầu tiên chạm tới
            }
        }
    }
}

// Phân tích n thành vector 2 chiều: mỗi phần tử gồm [thừa_số_nguyên_tố, số_mũ]
vector<vector<long long>> factorize_spf(int n) {
    vector<vector<long long>> factors;
    while (n > 1) {
        long long p = spf[n];
        long long count = 0;
        while (n % p == 0) {
            count++;
            n /= p;
        }
        factors.push_back({p, count});
    }
    return factors;
}

Mô phỏng Sàng số nguyên tố phân đoạn

4. Sàng nguyên tố phân đoạn (Segmented Sieve

4.1. Bản chất & Ranh giới bộ nhớ

Khi bài toán yêu cầu tìm/đếm các số nguyên tố trong đoạn $[L, R]$ với: $$1 \le L \le R \le 10^{12} \quad \text{và} \quad R - L \le 10^6$$

  • Tại sao không thể tạo mảng bool is_prime[10^12]? $\implies$ Vì $10^{12}$ byte $\approx 1000\text{GB}$ RAM, vượt giới hạn $256\text{MB}$ của đề bài.
  • Định lý toán học: Mọi hợp số $X \in [L, R]$ đều có ít nhất một ước nguyên tố $p \le \sqrt{R} \le 10^6$.

4.2. Thuật toán Sàng đoạn 3 bước

  1. Bước 1: Dùng sàng Eratosthenes chuẩn tìm tất cả số nguyên tố $p \le \sqrt{R} \le 10^6$.
  2. Bước 2: Cấp phát mảng đánh dấu vector<bool> is_prime_range(R - L + 1, true). Số $X \in [L, R]$ được ánh xạ về chỉ số X - L trong mảng ($0 \le X - L \le 10^6$). vector<bool> thường nén mỗi cờ theo bit, nên với $10^6$ vị trí phần đánh dấu chỉ khoảng $10^6$ bit $\approx 0.125$ MB; bộ nhớ thực tế phụ thuộc kiểu lưu trữ và overhead của container.
  3. Bước 3: Với mỗi số nguyên tố $p \le \sqrt{R}$, tìm bội số nhỏ nhất của $p$ mà $\ge L$: $$\text{start} = \max\left(p \times p, \left\lceil \frac{L}{p} \right\rceil \times p\right) = \max\left(p \times p, \left\lfloor \frac{L + p - 1}{p} \right\rfloor \times p\right)$$ Gạch bỏ tất cả các bội số $\text{start}, \text{start} + p, \text{start} + 2p, \dots \le R$.

4.3. Cài đặt C++ Sàng đoạn

vector<long long> segmented_sieve(long long L, long long R) {
    long long limit = sqrt(R);
    vector<bool> mark(limit + 1, true);
    vector<long long> primes;
    for (long long p = 2; p <= limit; ++p) {
        if (mark[p]) {
            primes.push_back(p);
            for (long long j = p * p; j <= limit; j += p) mark[j] = false;
        }
    }

    vector<bool> is_prime_range(R - L + 1, true);
    for (long long p : primes) {
        long long start = max(p * p, ((L + p - 1) / p) * p);
        for (long long j = start; j <= R; j += p) {
            is_prime_range[j - L] = false;
        }
    }

    if (L == 1) is_prime_range[0] = false; // Bắt buộc: Số 1 không phải là số nguyên tố

    vector<long long> result;
    for (long long i = 0; i <= R - L; ++i) {
        if (is_prime_range[i]) {
            result.push_back(L + i);
        }
    }
    return result;
}

5. Thuật toán Euclid mở rộng & Phương trình Diophantine

5.1. Định lý Bézout & Nghiệm nguyên

Định lý Bézout: Với hai số nguyên $A, B$, luôn tồn tại hai số nguyên $x, y$ sao cho: $$A \cdot x + B \cdot y = \gcd(A, B)$$

Phương trình Diophantine tuyến tính $A \cdot x + B \cdot y = C$ có nghiệm nguyên khi và chỉ khi $C$ chia hết cho $\gcd(A, B)$.

5.2. Công thức hồi quy nghiệm extgcd

Giả sử đệ quy tìm được $(x_1, y_1)$ thỏa mãn: $B \cdot x_1 + (A \bmod B) \cdot y_1 = g$.
Vì $A \bmod B = A - \lfloor A/B \rfloor \cdot B$, ta có công thức hồi quy nghiệm $(x, y)$: $$\begin{cases} x = y_1 \\ y = x_1 - \lfloor A / B \rfloor \cdot y_1 \end{cases}$$

long long extgcd_nonnegative(long long a, long long b, long long &x, long long &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    long long x1, y1;
    long long g = extgcd_nonnegative(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return g;
}

long long extgcd(long long a, long long b, long long &x, long long &y) {
    long long sa = (a < 0 ? -1 : 1);
    long long sb = (b < 0 ? -1 : 1);
    long long aa = (a < 0 ? -a : a);
    long long bb = (b < 0 ? -b : b);
    long long g = extgcd_nonnegative(aa, bb, x, y);
    x *= sa;
    y *= sb;
    return g;
}

Hàm extgcd_nonnegative xử lý phần đệ quy trên số không âm; hàm extgcd bên ngoài khôi phục dấu của $A, B$. Vì vậy phương trình $A x + B y = C$ có thể nhận $A, B$ âm, sau khi xử lý riêng trường hợp $A = B = 0$.


6. Mẫu cài đặt chuẩn thi đấu (Competitive Template)

#include <bits/stdc++.h>
using namespace std;

// Sàng SPF tiền xử lý
const int MAXN = 1000000;
int spf[MAXN + 1];

void init_spf() {
    for (int i = 1; i <= MAXN; ++i) spf[i] = i;
    for (int i = 2; i * i <= MAXN; ++i) {
        if (spf[i] == i) {
            for (int j = i * i; j <= MAXN; j += i) {
                if (spf[j] == j) spf[j] = i;
            }
        }
    }
}

int main() {
    // Fast I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    init_spf();

    int q;
    if (!(cin >> q)) return 0;

    while (q--) {
        int n;
        cin >> n;
        // Phân tích n trong O(log N)
        while (n > 1) {
            int p = spf[n];
            int cnt = 0;
            while (n % p == 0) {
                cnt++;
                n /= p;
            }
            cout << p << "^" << cnt << " ";
        }
        cout << "\n";
    }
    return 0;
}

7. Ranh giới áp dụng: Khi nào dùng SPF vs Sàng đoạn vs Phân tích $\mathcal{O}(\sqrt{N})$?

Phương Pháp Phạm Vi Dữ Liệu Số Lượng Truy Vấn Bộ Nhớ RAM Khi Nào Sử Dụng?
Thử chia $\mathcal{O}(\sqrt{N})$ $N \le 10^{14}$ $Q \le 10^3$ (Ít truy vấn) $\mathcal{O}(1)$ Số lớn, ít truy vấn độc lập
Sàng SPF $\mathcal{O}(\log N)$ $N \le 10^6$ $Q \le 10^6$ (Rất nhiều truy vấn) Khoảng $4\text{MB}$ với int Số vừa phải, truy vấn liên tục
Sàng đoạn $[L, R]$ $R \le 10^{12}, R - L \le 10^6$ $1$ truy vấn đoạn lớn Khoảng $0.125\text{MB}$ cho $10^6$ bit với vector<bool> Cần đếm/tìm số nguyên tố trên dải số lớn

Ghi chú bộ nhớ: Đây chỉ là phần mảng đánh dấu. Nếu dùng bool, char hoặc container khác thay cho vector<bool>, bộ nhớ sẽ lớn hơn; cần tính theo kiểu dữ liệu thực tế.


Visual Assets / Hình ảnh trực quan

Các hình dưới đây là kế hoạch minh họa cho bản phát hành; trạng thái hiện tại là TBD.

Asset Mục đích minh họa Nội dung chính Vị trí đặt trong Lesson Trạng thái
Sơ đồ thuật toán Euclid Làm rõ quá trình giảm cặp $(A,B)$ $(A,B) \to (B,A \bmod B)$ Sau mục 2.1 TBD
Bảng mô phỏng SPF với $N=84$ Cho thấy mỗi lần chia theo ước nguyên tố nhỏ nhất $84\to42\to21\to7\to1$ Sau mục 3.2 TBD
Sơ đồ ba bước Segmented Sieve Phân biệt sàng cơ sở và sàng trên đoạn lớn Sinh prime cơ sở → ánh xạ đoạn → gạch bội Sau mục 4.2 TBD
Sơ đồ truy hồi Extended Euclid Theo dõi hệ số Bézout qua các lần quay lui $(x_1,y_1)\to(x,y)$ Sau mục 5.2 TBD
Sơ đồ chọn công cụ Giúp chọn đúng thuật toán theo giới hạn SPF / sàng đoạn / thử chia Trước mục 7 TBD
1
A
B
C
D
2
A
B
C
D
3
A
B
C
D
4
A
B
C
D
5
A
B
C
D
6
A
B
C
D
7
A
B
C
D
8
A
B
C
D
9
A
B
C
D
10
A
B
C
D
11
A
B
C
D
12
A
B
C
D

Bài tập lập trình vận dụng

22 Bài tập thực hành

Nhấn vào nút "Làm bài" bên dưới để mở giao diện làm bài trực tuyến (Online Judge) và thực hiện viết mã nguồn cho bài tập này.

Ước chung & bội chung cơ bản
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Rút gọn mảng phân số lớn
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Sàng ước số nguyên tố nhỏ nhất (SPF)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Phân tích thừa số truy vấn nhanh
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đếm ước số & tổng ước số nhanh
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Sàng nguyên tố đoạn [l, r]
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Cặp số nguyên tố sinh đôi trong đoạn
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tìm nghiệm nguyên phương trình Diophantine
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Nghiệm nguyên dương nhỏ nhất của phương trình Diophantine
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Hàm phi Euler $\phi(n)$ nhanh với SPF
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Phân tích thừa số nguyên tố của giai thừa (định lý Legendre)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đếm số có số lượng ước là số lẻ trong đoạn
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tìm cặp số biết GCD và LCM có tổng nhỏ nhất
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Khoảng cách lớn nhất giữa hai số nguyên tố liên tiếp
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đếm số cách đổi tiền bằng phương trình Diophantine
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tính tổng GCD của n với tất cả các số từ 1 đến n
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Định lý thặng dư trung hoa (chinese remainder theorem — CRT)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Bậc của số nguyên theo modulo m (multiplicative order)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Can nguyen nguyen thuy primitive root
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tinh uoc nguyen to lon nhat
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Phuong trinh pell co ban
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Phan tich Legendre nang cao
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài