Số Học Cơ Bản & Chuyên Sâu
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)$
- $\gcd(105, 45) = \gcd(45, 105 \bmod 45) = \gcd(45, 15)$
- $\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ằngabstrướ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 * BTRONG PHÉP TÍNH LCMNế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ểulong 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; }
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 theospf[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;
}
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
- 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$.
- 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 - Ltrong 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. - 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,charhoặc container khác thay chovector<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 |