Bài 2.5. Bài tập tổng hợp về lý thuyết số

Bài 2.5. Bài tập tổng hợp về lý thuyết số
Chào mừng bạn trở lại với series blog về Cấu trúc dữ liệu và Giải thuật!
Trong các bài trước, chúng ta đã cùng nhau khám phá những khái niệm cơ bản nhưng mạnh mẽ của lý thuyết số trong lập trình, như số nguyên tố, ước chung lớn nhất (GCD), bội chung nhỏ nhất (LCM), và các phép toán theo modulo. Lý thuyết là nền tảng, nhưng để thực sự nắm vững và áp dụng chúng một cách hiệu quả, không có gì tốt hơn là thực hành.
Bài viết này sẽ là nơi chúng ta cùng nhau vận dụng những kiến thức đã học thông qua một loạt các bài tập tổng hợp, từ cơ bản đến nâng cao một chút. Mỗi bài tập sẽ đi kèm với ví dụ minh họa bằng C++ và giải thích chi tiết về cách thuật toán hoạt động. Hãy sẵn sàng để bắt tay vào code!
Bài tập 1: Kiểm tra một số có phải là số nguyên tố?
Đây là bài toán kinh điển khi bắt đầu với lý thuyết số. Một số nguyên dương n
lớn hơn 1 được gọi là số nguyên tố nếu nó chỉ có đúng hai ước dương phân biệt là 1 và chính nó.
Ý tưởng thuật toán:
Để kiểm tra xem n
có phải là số nguyên tố hay không, chúng ta chỉ cần kiểm tra xem n
có ước nào khác 1 và chính nó hay không. Cách đơn giản nhất là duyệt từ 2 đến n-1
và kiểm tra tính chia hết. Tuy nhiên, chúng ta có thể tối ưu đáng kể.
Nếu n
có một ước d
lớn hơn sqrt(n)
, thì chắc chắn nó phải có một ước n/d
nhỏ hơn sqrt(n)
. Do đó, chúng ta chỉ cần kiểm tra các ước từ 2 đến sqrt(n)
.
Các trường hợp đặc biệt:
- Số 0 và 1 không phải là số nguyên tố.
- Số 2 là số nguyên tố duy nhất chẵn.
Code minh họa (C++):
#include <iostream>
#include <cmath> // Cần cho sqrt
bool isPrime(int n) {
// 0 và 1 không phải là số nguyên tố
if (n <= 1) {
return false;
}
// 2 là số nguyên tố duy nhất chẵn
if (n == 2) {
return true;
}
// Các số chẵn lớn hơn 2 không phải là số nguyên tố
if (n % 2 == 0) {
return false;
}
// Kiểm tra các ước lẻ từ 3 đến sqrt(n)
// Bước nhảy 2 để bỏ qua các số chẵn
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) {
return false; // Tìm thấy ước, n không phải số nguyên tố
}
}
// Nếu không tìm thấy ước nào, n là số nguyên tố
return true;
}
int main() {
int num;
std::cout << "Nhap mot so nguyen duong: ";
std::cin >> num;
if (isPrime(num)) {
std::cout << num << " la so nguyen to." << std::endl;
} else {
std::cout << num << " khong phai la so nguyen to." << std::endl;
}
return 0;
}
Giải thích code:
- Hàm
isPrime(int n)
nhận vào một số nguyênn
. - Dòng
if (n <= 1)
: Xử lý trường hợpn
là 0 hoặc 1, trả vềfalse
ngay lập tức. - Dòng
if (n == 2)
: Xử lý trường hợpn
là 2, trả vềtrue
. - Dòng
if (n % 2 == 0)
: Xử lý các số chẵn lớn hơn 2. Vì 2 là số chẵn nguyên tố duy nhất, mọi số chẵn khác chắc chắn chia hết cho 2 và không phải là số nguyên tố, nên trả vềfalse
. - Vòng lặp
for (int i = 3; i * i <= n; i += 2)
: Đây là phần kiểm tra chính.- Bắt đầu từ
i = 3
vì đã xử lý số 2. - Điều kiện lặp
i * i <= n
(tương đươngi <= sqrt(n)
) áp dụng tối ưu hóa đã nói ở trên. - Bước nhảy
i += 2
giúp chúng ta chỉ kiểm tra các số lẻ, vì nếun
có ước chẵn thì nó đã bị loại ở bước trước, và một số lẻ chỉ có thể có ước lẻ. Điều này giúp giảm số lần lặp đi một nửa.
- Bắt đầu từ
- Trong vòng lặp,
if (n % i == 0)
kiểm tra xemn
có chia hết choi
hay không. Nếu có, tức làn
có một ước khác 1 và chính nó (lài
), nên nó không phải số nguyên tố, và chúng ta trả vềfalse
. - Nếu vòng lặp kết thúc mà không tìm thấy ước nào, điều đó có nghĩa là
n
chỉ chia hết cho 1 và chính nó (đối vớin > 1
), nên hàm trả vềtrue
. - Hàm
main
chỉ đơn giản là nhận input, gọi hàmisPrime
, và in kết quả.
Độ phức tạp: Độ phức tạp thời gian của thuật toán này là O(sqrt(n)).
Bài tập 2: Tìm ước chung lớn nhất (GCD) của hai số
Ước chung lớn nhất (GCD - Greatest Common Divisor) của hai số nguyên dương a
và b
là số nguyên dương lớn nhất mà cả a
và b
đều chia hết cho nó.
Ý tưởng thuật toán:
Thuật toán nổi tiếng và hiệu quả nhất để tìm GCD là Thuật toán Euclid. Dựa trên nguyên tắc:
GCD(a, b) = GCD(b, a % b)
nếu b != 0
GCD(a, 0) = a
Nguyên tắc này cho phép chúng ta giảm kích thước của các số một cách nhanh chóng cho đến khi một số trở thành 0.
Code minh họa (C++):
Chúng ta có thể cài đặt thuật toán Euclid theo hai cách: đệ quy hoặc lặp. Cả hai đều tương đương về mặt logic. Dưới đây là phiên bản lặp (iterative) thường được ưa chuộng trong lập trình thi đấu vì tránh được overhead của đệ quy.
#include <iostream>
#include <numeric> // C++17 trở lên có std::gcd
// Hàm tính GCD sử dụng thuật toán Euclid (phiên bản lặp)
int gcd(int a, int b) {
// Đảm bảo a và b là số dương
a = std::abs(a);
b = std::abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int main() {
int num1, num2;
std::cout << "Nhap hai so nguyen duong: ";
std::cin >> num1 >> num2;
int result = gcd(num1, num2); // Hoặc std::gcd(num1, num2) nếu dùng C++17
std::cout << "GCD(" << num1 << ", " << num2 << ") = " << result << std::endl;
return 0;
}
Giải thích code:
- Hàm
gcd(int a, int b)
nhận hai số nguyêna
vàb
. a = std::abs(a); b = std::abs(b);
: GCD thường được định nghĩa cho số dương. Dòng này đảm bảo chúng ta làm việc với giá trị tuyệt đối.- Vòng lặp
while (b != 0)
: Vòng lặp tiếp tục miễn là số thứ hai (b
) chưa bằng 0. - Trong vòng lặp:
int temp = b;
: Lưu giá trị hiện tại củab
vào biến tạmtemp
.b = a % b;
: Cập nhậtb
thành phần dư củaa
chiab
. Đây là bước cốt lõi của thuật toán Euclid.a = temp;
: Cập nhậta
thành giá trị cũ củab
(đã lưu trongtemp
).
- Khi
b
trở thành 0, vòng lặp dừng lại. Theo thuật toán Euclid, giá trị hiện tại củaa
chính là GCD ban đầu của hai số. Hàm trả vềa
. - Hàm
main
nhận input, gọi hàmgcd
, và in kết quả.
Lưu ý: Từ C++17 trở đi, thư viện <numeric>
cung cấp sẵn hàm std::gcd
. Tuy nhiên, việc tự cài đặt giúp bạn hiểu rõ thuật toán.
Độ phức tạp: Độ phức tạp thời gian của thuật toán Euclid là O(log(min(a, b))), cực kỳ hiệu quả ngay cả với số lớn.
Bài tập 3: Tìm bội chung nhỏ nhất (LCM) của hai số
Bội chung nhỏ nhất (LCM - Least Common Multiple) của hai số nguyên dương a
và b
là số nguyên dương nhỏ nhất mà cả a
và b
đều là ước của nó.
Ý tưởng thuật toán:
Có một mối liên hệ tuyệt vời giữa GCD và LCM của hai số a
và b
:
GCD(a, b) * LCM(a, b) = |a * b|
Từ đó, chúng ta có thể suy ra công thức tính LCM:
LCM(a, b) = (|a * b|) / GCD(a, b)
Để tính LCM, chúng ta chỉ cần sử dụng lại hàm gcd
đã xây dựng ở bài tập trước.
Lưu ý về tràn số (Overflow): Khi tính a * b
, kết quả có thể rất lớn và gây tràn số nếu a
và b
là các số nguyên lớn. Để tránh điều này, chúng ta nên thực hiện phép chia cho GCD trước khi nhân:
LCM(a, b) = (|a| / GCD(a, b)) * |b|
Hoặc LCM(a, b) = (|b| / GCD(a, b)) * |a|
. Thứ tự nào cũng được vì |a|
và |b|
đều chia hết cho GCD(a, b)
.
Code minh họa (C++):
#include <iostream>
#include <numeric> // Cần cho std::gcd (hoặc sử dụng hàm gcd tự định nghĩa)
#include <cmath> // Cần cho std::abs
// Hàm tính GCD (sử dụng lại từ Bài tập 2, hoặc dùng std::gcd)
int gcd(int a, int b) {
a = std::abs(a);
b = std::abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// Hàm tính LCM
long long lcm(long long a, long long b) {
if (a == 0 || b == 0) return 0; // LCM của 0 và bất kỳ số nào là 0
// Sử dụng công thức LCM(a, b) = (|a*b|) / GCD(a, b)
// Áp dụng công thức tránh tràn số: (|a| / GCD(a, b)) * |b|
// Sử dụng long long để chứa kết quả có thể lớn
return (std::abs(a) / gcd(a, b)) * std::abs(b);
}
int main() {
long long num1, num2; // Sử dụng long long để tránh tràn số khi nhập
std::cout << "Nhap hai so nguyen: ";
std::cin >> num1 >> num2;
long long result = lcm(num1, num2);
std::cout << "LCM(" << num1 << ", " << num2 << ") = " << result << std::endl;
return 0;
}
Giải thích code:
- Hàm
lcm(long long a, long long b)
nhận hai sốa
vàb
. Chúng ta dùng kiểulong long
vì LCM có thể lớn hơn nhiều so vớiint
. - Dòng
if (a == 0 || b == 0) return 0;
: LCM của 0 và bất kỳ số nào là 0. - Dòng
return (std::abs(a) / gcd(a, b)) * std::abs(b);
: Đây là công thức tính LCM đã được điều chỉnh để tránh tràn số.gcd(a, b)
: Gọi hàmgcd
để tính ước chung lớn nhất.std::abs(a)
vàstd::abs(b)
: Đảm bảo làm việc với giá trị tuyệt đối.std::abs(a) / gcd(a, b)
: Thực hiện phép chia trước. Kết quả này chắc chắn là số nguyên vìGCD(a, b)
là ước của|a|
.* std::abs(b)
: Sau đó nhân với|b|
. Phép nhân này ít có khả năng gây tràn số hơn là nhân|a| * |b|
ngay từ đầu, đặc biệt khi kết quả cuối cùng vừa vặn vớilong long
.
- Hàm
main
nhận input (dùnglong long
) và in kết quả.
Độ phức tạp: Độ phức tạp thời gian của việc tính LCM bằng cách này chủ yếu phụ thuộc vào độ phức tạp của thuật toán GCD, tức là O(log(min(|a|, |b|))).
Bài tập 4: Tính lũy thừa theo modulo (Modular Exponentiation)
Bài toán: Cho ba số nguyên base
, exp
(số mũ không âm), và mod
(số modulo, mod > 0
). Tính (base^exp) % mod
.
Bài toán này cực kỳ quan trọng trong nhiều lĩnh vực của lý thuyết số ứng dụng, đặc biệt là mật mã học (ví dụ: thuật toán RSA).
Ý tưởng thuật toán:
Cách ngây thơ nhất là tính base^exp
rồi lấy modulo. Tuy nhiên, base^exp
có thể là một số khổng lồ, vượt xa khả năng lưu trữ của máy tính ngay cả với long long
, dù cho base
, exp
, và mod
có thể tương đối nhỏ.
Chúng ta cần áp dụng các tính chất của phép toán modulo:
(a * b) % m = ((a % m) * (b % m)) % m
Từ đó, (base^exp) % mod = (base * base * ... * base) % mod
(với exp
lần nhân). Chúng ta có thể nhân dần và lấy modulo sau mỗi phép nhân để giữ cho các số luôn nhỏ hơn mod
.
Tuy nhiên, cách này vẫn có độ phức tạp O(exp), quá chậm nếu exp
là số lớn.
Thuật toán Binary Exponentiation (hay Exponentiation by Squaring - Lũy thừa nhị phân) giúp chúng ta giảm độ phức tạp xuống còn O(log(exp)). Ý tưởng dựa trên việc phân tích số mũ exp
dưới dạng nhị phân:
- Nếu
exp
chẵn,base^exp = (base^2)^(exp / 2)
. - Nếu
exp
lẻ,base^exp = base * (base^2)^((exp - 1) / 2)
.
Chúng ta có thể thực hiện điều này lặp đi lặp lại, đồng thời áp dụng phép modulo sau mỗi phép nhân.
Code minh họa (C++):
#include <iostream>
// Hàm tính (base^exp) % mod sử dụng Binary Exponentiation
long long power(long long base, long long exp, long long mod) {
long long res = 1; // Kết quả ban đầu là base^0 = 1
// Đảm bảo base và mod là dương (hoặc xử lý base âm theo luật modulo)
// Với exp >= 0 và mod > 0, base âm có thể được xử lý bằng (base % mod + mod) % mod
base %= mod;
if (base < 0) base = (base + mod) % mod;
while (exp > 0) {
// Nếu bit cuối cùng của exp là 1 (exp lẻ), nhân base vào kết quả
if (exp % 2 == 1) {
res = (res * base) % mod;
}
// Bình phương base, đồng thời lấy modulo
base = (base * base) % mod;
// Chia exp cho 2 ( dịch phải 1 bit)
exp /= 2; // Hoặc exp >>= 1;
}
return res;
}
int main() {
long long base, exp, mod;
std::cout << "Nhap co so (base), so mu (exp), va modulo (mod): ";
std::cin >> base >> exp >> mod;
// Xử lý trường hợp exp âm nếu cần (bài toán này chỉ xét exp >= 0)
if (exp < 0) {
std::cout << "So mu phai khong am trong bai tap nay." << std::endl;
return 1;
}
if (mod <= 0) {
std::cout << "Modulo phai la so duong." << std::endl;
return 1;
}
long long result = power(base, exp, mod);
std::cout << "(" << base << "^" << exp << ") % " << mod << " = " << result << std::endl;
return 0;
}
Giải thích code:
- Hàm
power(long long base, long long exp, long long mod)
nhận ba giá trị. Sử dụnglong long
để tránh tràn số trong các phép nhân trung gian trước khi lấy modulo. long long res = 1;
: Biếnres
lưu trữ kết quả cuối cùng, ban đầu là 1 (tương ứng vớibase^0
).base %= mod; if (base < 0) base = (base + mod) % mod;
: Đưabase
về phạm vi[0, mod - 1]
trước khi bắt đầu tính toán để giữ cho các số nhỏ. Xử lýbase
âm cho đúng với định nghĩa modulo.- Vòng lặp
while (exp > 0)
: Lặp cho đến khi số mũexp
về 0. Mỗi lần lặp xử lý một bit củaexp
từ phải sang trái. if (exp % 2 == 1)
: Kiểm tra bit cuối cùng củaexp
. Nếu là 1 (tứcexp
lẻ), điều này có nghĩa là lũy thừa hiện tại củabase
(đã được bình phương nhiều lần) cần được nhân vào kết quả cuối cùngres
.res = (res * base) % mod;
thực hiện phép nhân và lấy modulo để giữres
nhỏ.base = (base * base) % mod;
: Bình phươngbase
(tương ứng với việc xử lýbase^2
cho bit tiếp theo) và lấy modulo.exp /= 2;
: Chiaexp
cho 2 (dịch bit sang phải), chuyển sang xử lý bit tiếp theo của số mũ.- Khi
exp
bằng 0,res
chứa kết quả(base^exp) % mod
. - Hàm
main
nhận input, gọi hàmpower
, và in kết quả.
Độ phức tạp: Độ phức tạp thời gian của thuật toán Binary Exponentiation là O(log(exp)), nhanh hơn đáng kể so với O(exp).
Bài tập 5: Sàng Eratosthenes (Sieve of Eratosthenes)
Bài toán: Tìm tất cả các số nguyên tố nhỏ hơn hoặc bằng một giới hạn cho trước N
.
Nếu cần tìm nhiều số nguyên tố trong một phạm vi, việc kiểm tra từng số bằng hàm isPrime
(như Bài tập 1) sẽ rất chậm. Sàng Eratosthenes là một thuật toán hiệu quả hơn nhiều cho nhiệm vụ này.
Ý tưởng thuật toán:
Thuật toán Sàng Eratosthenes hoạt động dựa trên nguyên tắc: Một số hợp số luôn có thể được biểu diễn dưới dạng tích của các số nguyên tố.
- Tạo một danh sách (hoặc mảng boolean) chứa tất cả các số từ 2 đến
N
, ban đầu đánh dấu tất cả là "có thể là số nguyên tố". - Bắt đầu với số nguyên tố nhỏ nhất là 2. Đánh dấu tất cả các bội số của 2 (lớn hơn 2) là "không phải số nguyên tố".
- Tìm số chưa bị đánh dấu tiếp theo trong danh sách. Số này chắc chắn là số nguyên tố.
- Lấy số nguyên tố vừa tìm được (ví dụ là
p
), đánh dấu tất cả các bội số củap
(lớn hơnp
) là "không phải số nguyên tố". Có thể bắt đầu đánh dấu từp*p
thay vì2*p
,3*p
,... vì các bội nhỏ hơnp*p
đã bị đánh dấu bởi các số nguyên tố nhỏ hơn. - Lặp lại bước 3 và 4 cho đến khi tìm được số chưa bị đánh dấu lớn hơn
sqrt(N)
. Tại điểm này, tất cả các số chưa bị đánh dấu còn lại trong danh sách đều là số nguyên tố.
Code minh họa (C++):
#include <iostream>
#include <vector>
#include <cmath> // Cần cho sqrt (hoặc i*i <= n)
// Hàm thực hiện sàng Eratosthenes để tìm các số nguyên tố đến N
std::vector<bool> sieve(int N) {
// Tạo mảng boolean, ban đầu giả định tất cả đều là số nguyên tố
// Kích thước N+1 để dễ dàng truy cập theo chỉ số
std::vector<bool> is_prime(N + 1, true);
// 0 và 1 không phải là số nguyên tố
is_prime[0] = is_prime[1] = false;
// Bắt đầu từ 2
for (int p = 2; p * p <= N; ++p) {
// Nếu p vẫn chưa bị đánh dấu (tức là p là số nguyên tố)
if (is_prime[p] == true) {
// Đánh dấu tất cả các bội của p là không phải số nguyên tố
// Bắt đầu từ p*p vì các bội nhỏ hơn (2p, 3p,...) đã bị đánh dấu bởi các số nguyên tố nhỏ hơn
for (int i = p * p; i <= N; i += p)
is_prime[i] = false;
}
}
return is_prime;
}
int main() {
int limit;
std::cout << "Nhap gioi han tren (N) de tim so nguyen to: ";
std::cin >> limit;
if (limit < 0) {
std::cout << "Gioi han phai khong am." << std::endl;
return 1;
}
std::vector<bool> primes = sieve(limit);
std::cout << "Cac so nguyen to nho hon hoac bang " << limit << " la:" << std::endl;
for (int p = 2; p <= limit; ++p) {
if (primes[p]) {
std::cout << p << " ";
}
}
std::cout << std::endl;
return 0;
}
Giải thích code:
- Hàm
sieve(int N)
nhận giới hạn trênN
. std::vector<bool> is_prime(N + 1, true);
: Khởi tạo mộtvector<bool>
kích thướcN+1
.is_prime[i]
sẽ làtrue
nếui
được xem là số nguyên tố,false
nếu là hợp số. Ban đầu tất cả đều được đặt làtrue
.is_prime[0] = is_prime[1] = false;
: Đánh dấu 0 và 1 không phải số nguyên tố.- Vòng lặp ngoài
for (int p = 2; p * p <= N; ++p)
: Duyệt qua các số từ 2. Điều kiệnp * p <= N
(tứcp <= sqrt(N)
) là một tối ưu hóa quan trọng. Chúng ta chỉ cần duyệt đếnsqrt(N)
để tìm các số nguyên tố lần đầu tiên đánh dấu các hợp số. Các hợp số lớn hơnsqrt(N)
mà chưa bị đánh dấu sẽ có ước nguyên tố nhỏ hơnsqrt(N)
và đã bị đánh dấu bởi các ước đó. if (is_prime[p] == true)
: Kiểm tra xem sốp
hiện tại có còn được đánh dấu là số nguyên tố hay không. Nếu có,p
chắc chắn là một số nguyên tố.- Vòng lặp trong
for (int i = p * p; i <= N; i += p)
: Duyệt qua tất cả các bội số củap
bắt đầu từ `pp`*.i = p * p
: Bắt đầu từp*p
vì các bội nhỏ hơn (như2*p
,3*p
, ...) đã được đánh dấu bởi các số nguyên tố nhỏ hơnp
(như 2, 3, ...). Ví dụ: 6 bị đánh dấu bởi 2 và 3 trước khi chúng ta xét đến 6. Khi xétp=3
, chúng ta bắt đầu đánh dấu từ3*3=9
.i <= N
: Giới hạn đếnN
.i += p
: Nhảy tới các bội số tiếp theo củap
.
is_prime[i] = false;
: Đánh dấu bội sối
là hợp số.- Sau khi vòng lặp ngoài kết thúc, mảng
is_prime
chứa kết quả:is_prime[i]
làtrue
nếui
là số nguyên tố,false
nếu không. - Hàm
main
gọisieve
và sau đó duyệt mảng kết quả để in ra các số nguyên tố.
Độ phức tạp: Độ phức tạp thời gian của Sàng Eratosthenes là O(N log log N), hiệu quả hơn nhiều so với O(N * sqrt(N)) khi kiểm tra từng số một. Độ phức tạp không gian là O(N) để lưu trữ mảng is_prime
.
Bài tập ví dụ:
Ước số nguyên tố nhỏ nhất
Đề bài
Cho số tự nhiên N. Nhiệm vụ của bạn là in ra ước số nguyên tố nhỏ nhất của các số từ 1 đến N.
- Ước số nguyên tố nhỏ nhất của 1 là 1.
- Ước số nguyên tố nhỏ nhất của các số chẵn là 2.
- Ước số nguyên tố nhỏ nhất của các số nguyên tố là chính nó.
Input Format
Một số N được ghi trên một dòng (1 ≤ N ≤ 100000).
Output Format
Đưa ra kết quả theo từng dòng.
Sample Input 0
7
Sample Output 0
1
2
3
2
5
2
7
Okay, đây là hướng dẫn ngắn gọn để giải bài toán tìm ước số nguyên tố nhỏ nhất cho các số từ 1 đến N bằng C++:
Khởi tạo mảng: Tạo một mảng (ví dụ
std::vector<int>
) có kích thướcN+1
để lưu trữ ước số nguyên tố nhỏ nhất (SPF) cho mỗi số từ 1 đến N. Khởi tạo giá trị ban đầu cho mỗi phần tửi
trong mảng lài
.Xử lý trường hợp đặc biệt: Gán giá trị SPF cho 1 là 1.
Sàng lọc (Sieve): Sử dụng một thuật toán sàng lọc tương tự Sàng Eratosthenes:
- Lặp qua các số
p
từ 2 đến N. - Nếu
spf[p]
vẫn bằngp
(điều này có nghĩap
là số nguyên tố, vì nó chưa bị đánh dấu bởi ước nhỏ hơn nào):- Lặp qua các bội số
m
củap
, bắt đầu từp * p
(vì các bội số nhỏ hơnp*p
đã có ước nguyên tố nhỏ hơnp
). Tăngm
lênp
mỗi lần (m += p
). Tiếp tục đến khim
vượt quá N. - Đối với mỗi bội số
m
, nếuspf[m]
vẫn bằngm
(nghĩa làm
chưa được gán ước nguyên tố nhỏ hơnp
), hãy gánspf[m] = p
.
- Lặp qua các bội số
- Lặp qua các số
In kết quả: Duyệt qua mảng SPF từ chỉ số 1 đến N và in từng giá trị trên một dòng riêng biệt.
Comments