You are on page 1of 41

Chuyên đề: BÀI TẬP SỐ HỌC

Phần I. Giới thiệu

Trong quá trình giảng dạy và bồi dưỡng học sinh giỏi môn Tin học, chúng tôi nhận
thấy các bài tập về phần số học xuất hiện khá nhiều trong các đề thi học sinh giỏi Tin
học các cấp. Các bài tập số học cũng xuất hiện khá nhiều trên các trang web giải bài
qua mạng. Vì vậy tôi chọn chuyên đề bài tập số học để tham gia chia sẻ cùng hội thảo
các trường chuyên khu vực duyên hải đồng bằng Bắc bộ lần này.

Chuyên đề số học nói chung tương đối rộng, trong phạm vi chuyên đề này, tôi tập
trung vào các bài tập khá cơ bản về số học, chủ yếu là các bài tập liên quan đến số
nguyên tố.

Các bài tập được trình bày theo cấu trúc gồm: đề bài; các chương trình nguồn giải
mỗi bài tập được trình bày bằng ngôn ngữ lập trình Pascal, theo độ phức tạp giảm dần
(tương ứng giải quyết bài toán với dữ liệu lớn dần) phù hợp với việc yêu cầu đánh giá
học sinh cao dần, điều này giúp học sinh tư duy tìm hiểu thuật toán giải các bài toán
theo chiều hướng ngày một tốt hơn; tests của mỗi bài tập được gửi kèm theo đường
link, tests đảm bảo phản ánh được các thuật toán mà học sinh sử dụng.

Với khả năng và kinh nghiệm còn hạn chế của bản thân, chúng tôi muốn chuyên đề
này góp một phần nhỏ bé trong thành công của hội thảo cũng như là một nguồn tài liệu
nhỏ cho các em học sinh và các đồng nghiệp trong học tập và giảng dạy. Rất mong
nhận được những chia sẻ, góp ý của quý thầy cô!

Tests và chương trình tải tại link sau:

https://drive.google.com/file/d/1r8YhuMTzVKyzIu1_Op-Z_wdYD64Jf0fC/view
Phần II. Một số kiến thức lý thuyết

Cho 2 số nguyên a, b (b > 0). a chia hết cho b  kN, a = bk  a là một bội của
b, b là một ước của a.

Một số tự nhiên p (p > 0) là số nguyên tố nếu p có đúng hai ước phân biệt là 1 và p.

Số tự nhiên không phải là số nguyên tố gọi là hợp số.

Mọi số tự nhiên n (n > 1) luôn phân tích được (duy nhất) dạng tích của lũy thừa của
các số nguyên tố: n  p1 p2 p3 ... pr
1 2 3 r

Trong đó: p1, p2, …, pr là các số nguyên tố khác nhau đôi một; α1, α2, …, αr là các số
nguyên dương.

Ví dụ: số 84 được phân tích: 84 = 22.31.71

Với số n được phân tích n  p1 p2 p3 ... pr thì:
1 2 3 r

- Số lượng ước số của n là

Ví dụ: số lượng ước số của 84 là

Các ước đó là: 1, 2, 3, 4, 6, 7, 12, 14, 21, 28, 42, 84

Một số n có ước là i thì nó có thêm một ước là n div i. Có thể dùng tính chất này để
tính số lượng ước của một số n trong một số bài toán nhất định. Lưu ý: nếu n là số
chính phương thì nó có một ước là và có thêm một ước là n div (hai ước này
trùng nhau)

- Tổng các ước của n là

Ví dụ: tổng các ước của 84 là

- Tích của các ước của n là

Ví dụ: tích của các ước của 84 là

Phi hàm Euler

Hai số a và b được gọi là nguyên tố cùng nhau nếu UCLN(a,b)=1

Phi hàm Euler là số lượng các số thuộc đoạn [1,n] mà nguyên tố cùng nhau
với n, được tính theo công thức
Phần III. Bài tập

Bài 1: Mật khẩu – PASS.PAS


Để bảo vệ tài khoản mạng của mình, Tí dùng một mật khẩu là một dãy nhị phân độ
dài N. Nhưng việc nhớ một dãy nhị phân là tương đối khó khăn mà lưu lại thì Tí sợ
người khác nhìn thấy và bị lộ. Vì vậy, Tí quyết định thay vì lưu dãy nhị phân Tí lưu lại
một dãy số nguyên dương ngẫu nhiên và mỗi lần cần nhập mật khẩu Tí cho chạy một
chương trình để biến dãy số của mình thành mật khẩu với quy định: nếu số thứ i trong
dãy N số nguyên đó là số nguyên tố thì bít thứ i trong mật mã của Tí là 1, ngược lại thì
bit 0. Một ngày nọ chương trình của Tí bị lỗi nên Tí không thể tái tạo lại mật khẩu của
mình thông qua dãy số đã lưu. Bạn hãy giúp Tí viết lại chương trình trên.
Input: Cho trong tệp PASS.INP có cấu trúc: Dòng đầu tiên ghi số N (N ≤ 104); N dòng
tiếp theo, dòng thứ i chứa một số nguyên Ai (1 ≤ Ai ≤ 109)
Output: Ghi ra tệp PASS.OUT gồm N dòng, dòng thứ i ghi bít thứ i của mật mã.
Ví dụ:
PASS.INP PASS.OUT
6 0
1 1
3 0
14 1
31 0
36 1
7331

Bài 2: Đếm số nguyên tố trong đoạn – PCOUNTAB.PAS


Với hai số nguyên dương A, B cho trước (A ≤ B). Đếm số lượng các số nguyên tố
thuộc đoạn [A,B].
Input: Tệp PCOUNTAB.INP gồm: Dòng đầu tiên ghi số k (1 ≤ k ≤ 103) là số các
đoạn [A,B]; k dòng tiếp theo, mỗi dòng ghi 2 số A, B. (1 ≤ A ≤ B ≤ 107)
Output: Tệp PCOUNTAB.OUT gồm k dòng, dòng thứ i ghi một số là số các số
nguyên tố trong đoạn [A, B] thứ i đã cho.
Ví dụ:
PCOUNTAB.INP PCOUNTAB.OUT
3 8
2 20 17
20 100 25
1 100

Bài 3: Số nguyên tố ghép – MPRIME.PAS


Xét dãy A các số nguyên tố
2, 3, 5, 7, 11, 13, 17, 19,...
và dãy B gồm các số thu được từ dãy A bằng cách ghép hai số liên tiếp trong A:
23, 57, 1113, 1719, ...
Trong dãy B có những phần tử là số nguyên tố. Chẳng hạn 23, 3137, 8389, 157163...
Các số nguyên tố trong dãy B gọi là số nguyên tố ghép.
Yêu cầu: Cho trước số nguyên dương K ≤ 500, hãy tìm số nguyên tố ghép thứ K.
Input: Tệp MPRIME.INP gồm 1 dòng duy nhất ghi số nguyên dương K
Output: Tệp MPRIME.OUT gồm 1 số duy nhất là số nguyên tố ghép thứ K.
Ví dụ:
MPRIME.INP MPRIME.OUT
2 3137

Bài 4. Số siêu nguyên tố - SPRIME.PAS


Một số tự nhiên N được gọi là siêu nguyên tố nếu bản thân nó là một số nguyên tố
và tất cả các số thu được bằng cách xóa lần lượt các chữ số bên phải của nó đều là số
nguyên tố.
Ví dụ: Số 317 là một số siêu nguyên tố vì:
317 là 1 số nguyên tố
Xóa 1 chữ số bên phải: 31 là 1 số nguyên tố
Xóa 2 chữ số bên phải: 3 là 1 số nguyên tố
Cho 2 số nguyên a, b. Hãy liệt kê tất cả các số siêu nguyên tố thuộc đoạn [a, b].
Input: Tệp SPRIME.INP gồm một dòng ghi 2 số nguyên dương a, b (0<a,b <107)
Output: Tệp SPRIME.OUT liệt kê theo thứ tự tăng các số siêu nguyên tố thuộc đoạn
[a, b], mỗi số trên một dòng, hoặc ghi “NO” trong trường hợp không có số nào thuộc
đoạn đó.
Ví dụ:
SPRIME.INP SPRIME.OUT
3 57 3
5
7
23
29
31
37
53

Bài 5. Đếm các thừa số nguyên tố - COUNTPRD.PAS


Cho số nguyên dương N (2 ≤ N < 1010), hỏi có bao nhiêu số nguyên tố khác nhau
trong phân tích N ra tích các thừa số nguyên tố.
Ví dụ: 10 = 2*5 . Có 2 số nguyên tố khác nhau trong phân tích 10 ra tích các thừa số
nguyên tố là: 2 và 5.
Input: Vào từ file văn bản COUNTPRD.INP gồm một dòng chứa số nguyên N.
Output: Đưa ra file văn bản COUNTPRD.OUT số các số nguyên tố.
Ví dụ:
COUNTPRD.INP COUNTPRD.OUT
10 2
Bài 6. Đếm ước nguyên tố - CPRDIV.PAS
Cho hai số nguyên dương M và N (1 ≤ M ≤ N ≤ 60000) và số S được xác định
bằng công thức sau: S = n!/(m!(n−m)!)
Yêu cầu: Đếm số lượng ước nguyên tố của S
Input: Tệp CPRDIV.INP gồm một dòng ghi hai số N và M cách nhau một dấu cách.
Output: Tệp CPRDIV.OUT gồm một dòng ghi 1 số duy nhất là số lượng ước nguyên
tố của S.
Ví dụ:

CPRDIV.INP CPRDIV.OUT
7 3 2

Bài 7. Tìm K - KFIND.PAS


Cho hai số nguyên dương N và M. Hãy tìm số nguyên dương K sao cho N! chia
hết cho MK nhưng không chia hết cho MK+1. (2 ≤ M< N ≤ 106)
Input: được cho bởi tệp văn bản KFIND.INP gồm một dòng ghi hai số nguyên dương
N, M cách nhau bởi một dấu cách.
Output: Ghi ra tệp văn bản KFIND.OUT số nguyên K tìm được.
Ví dụ:
KFIND.INP KFIND.OUT
231 125 18
1111 111 30

Bài 8. Số có 3 ước - TNUM.PAS


Một số nguyên dương có đúng 3 ước số nguyên dương khác nhau được gọi là số
TNUM. Cho trước một dãy N (1 <= N <= 105) số nguyên dương, xác định các số đã
cho có phải là số TNUM hay không?
Input: Cho trong tệp TNUM.INP có cấu trúc như sau:
- Dòng đầu tiên ghi số N
- Dòng tiếp theo ghi N số nguyên a1 a2 ... an cách nhau bởi một dấu cách (1≤ai≤
12
10 )
Output: Ghi ra tệp TNUM.OUT gồm N dòng, dòng thứ i ghi YES nếu số thứ i là số
TNUM, ngược lại thì ghi NO
Ví dụ:
TNUM.INP TNUM.OUT
3 YES
456 NO
NO
Bài 9: Số có ít nhất 3 thừa số nguyên tố – TPRIMEFAC.PAS
Một số nguyên dương S được gọi là số TPRIMEFAC nếu nó có ít nhất 3 thừa số
nguyên tố khác nhau trong phân tích S thành tích các thừa số nguyên tố. Biết số 30 là
số TPRIMEFAC đầu tiên.
Yêu cầu: cho số nguyên dương N, tìm số TPRIMEFAC thứ N.
Input: Cho trong tệp TPRIMEFAC.INP có cấu trúc:
Dòng đầu tiên ghi số T (T ≤ 104) là số lượng test; T dòng tiếp theo, mỗi dòng
chứa một số nguyên dương N (1 ≤ N ≤ 104)
Output: Ghi ra tệp TPRIMEFAC.OUT gồm T dòng, mỗi dòng ghi số TPRIMEFAC
thứ N tìm được tương ứng với số cho trong tệp Input.
Ví dụ:
TPRIMEFAC.INP TPRIMEFAC.OUT
2 30
1 42
2
Bài 10: Dãy thừa số nguyên tố thứ nhất – FPFACTSEQ.PAS
Tí rất hứng thú với số nguyên tố và các dãy số. Sau khi học dãy Fibo, Tí quyết
định thành lập một dãy số T cho riêng mình, với mô tả như sau:
T[0] = T[1] = 0
Với mọi i > 1 thì T[i] được tính theo công thức: T[i] = T[i-1] + F[i], trong đó F[i]
là giá trị của thừa số nguyên tố nhỏ nhất trong phân tích i thành tích các thừa số
nguyên tố.
Thật không may là Tí không giỏi lập trình, bạn hãy lập trình giúp Tí tìm dãy số
trên.
Input. Tệp FPFACTSEQ.INP có cấu trúc:
Dòng đầu tiên ghi số k (k ≤ 100) là số test. k dòng tiếp theo, mỗi dòng ghi một số
nguyên dương N (1 < N < 107)
Output. Tệp FPFACTSEQ.OUT gồm k dòng, mỗi dòng ghi một số là số T[N] với N
tương ứng trong tệp Input.
Ví dụ:
FPFACTSEQ.INP FPFACTSEQ.OUT
3 7
4 2
2 21
7

Bài 11: Sự phân bố các số nguyên tố – PDISTRIBU.PAS


Trong số học, định lý số nguyên tố mô tả sự phân bố tiệm cận của các số nguyên
tố. Gọi π(x) là số lượng số nguyên tố không lớn hơn x. Định lý số nguyên tố chỉ ra

rằng:
Bạn hãy lập trình để kiểm chứng xem định lý số nguyên tố ước lượng chính xác đến
mức nào. Cụ thể, cho một số x, tính phần trăm sai số của biểu thức |π(x) - x/lnx| / π(x)
Input. Tệp PDISTRIBU.INP gồm một số dòng là số lượng bộ test (không quá 103),
mỗi dòng ghi một số x (2 ≤ x ≤ 108). Kết thúc dữ liệu là số 0.
Output. Tệp PDISTRIBU.OUT gồm nhiều dòng, mỗi dòng ghi kết quả tính được
tương ứng với số x cho trong tệp Input, lấy một chữ số thập phân.
Ví dụ:
PDISTRIBU.INP PDISTRIBU.OUT
2 188.5
3 36.5
5 3.6
1234567 7.7
0

Bài 12: Phân tích thừa số nguyên tố – PFACTOR.PAS


Giáo viên ra bài tập về nhà tuần này cho Tí là: cho một số nguyên N và yêu cầu Tí
tìm những thừa số nguyên tố của N!
Input. Cho trong tệp PFACTOR.INP gồm một số duy nhất là số N (2 ≤ N ≤ 104).
Output. Ghi ra tệp PFACTOR.OUT gồm một dòng là biểu diễn của phân tích N!
thành tích các thừa số nguyên tố.
Ví dụ:
PFACTOR.INP PFACTOR.OUT
10 2^8 * 3^4 * 5^2 * 7^1

Bài 13: Mật mã số nguyên tố – PRIPASS.PAS


Peter muốn tạo ra một số số nguyên tố cho hệ thống mật mã của mình nhưng
không lưu các số này vì sợ bị lộ. Vì vậy Peter chỉ lưu hai số nguyên A và B với ý đồ là
mật mã của mình là các số nguyên tố trong đoạn từ A tới B. Bạn biết A và B, tìm dãy
số nguyên tố trong mật mã của Peter.
Input. Tệp PRIPASS.INP có cấu trúc:
Dòng đầu tiên ghi số t (t ≤ 10) là số test. t dòng tiếp theo, mỗi dòng ghi hai số A và B
(1 ≤ A ≤ B ≤ 109, B-A ≤ 105)
Output. Tệp PRIPASS.OUT với mỗi test trong tệp Input, ghi ra dãy số nguyên tố của
Peter từ bé đến lớn, mỗi số trên một dòng. Ngăn cách giữa các test ghi một dòng trống.
Ví dụ:
PRIPASS.INP PRIPASS.OUT
2 3
35 5
1 10
2
3
5
7
Bài 14: Số siêu không nguyên tố – NSPRIME.PAS
Tim định nghĩa một số nguyên dương là số siêu không nguyên tố nếu xem số đó là
một chuỗi các kí tự chữ số thì không có dãy con (theo thứ tự và không cần liên tục)
nào của nó lập thành một số nguyên tố. Bạn được cho hai số A và B, hãy giúp Tim tìm
các số siêu không nguyên tố thuộc đoạn từ A tới B.
Input. Tệp NSPRIME.INP có cấu trúc:
Dòng đầu tiên chứa số t là số lượng bộ test (1 ≤ t ≤ 105), t dòng tiếp theo mỗi dòng ghi
hai số A và B (1 ≤ A, B ≤ 105).
Output. Tệp NSPRIME.OUT gồm t dòng, mỗi dòng ghi một số là số lượng các số siêu
không nguyên tố tương ứng thuộc đoạn A, B cho trong tệp Input.
Ví dụ:
NSPRIME.INP NSPRIME.OUT
2 2
47 4
29

Bài 15. Đoạn K_nguyên tố - KPRIMESUB.PAS


Một đoạn [L..R] được gọi là một đoạn K_Nguyên tố nếu có ít nhất K số nguyên tố
thuộc đoạn đó. Cho một số nguyên dương N và một số nguyên dương K. Hỏi có bao
nhiêu đoạn con của đoạn [2..N] là đoạn K_Nguyên tố?
Input: cho trong tệp KPRIMESUB.INP có cấu trúc như sau:
Dòng đầu tiên chứa số T là số test.
T dòng tiếp theo, mỗi dòng chứa hai số nguyên dương N và K cách nhau một dấu
cách.
Output: ghi ra tệp KPRIMESUB.OUT gồm T dòng, mỗi dòng tương ứng là câu trả
lời với bộ dữ liệu vào cho trong tệp input.
Ràng buộc: 1 ≤ T ≤ 102, 2 ≤ N ≤ 105, 0 ≤ K ≤ 104
Ví dụ:
KPRIMESUB.INP KPRIMESUB.OUT
3 4
52 9
51 8
93

Bài 16. Biến đổi số nguyên tố - PRIMECHANGE.PAS


Cho hai số nguyên tố khác nhau, mỗi số có bốn chữ số. Người ta cho rằng hoàn
toàn có thể biến đổi từ số này thành số kia sau một số bước theo quy tắc: Tại mỗi bước
ta chỉ thay đổi một chữ số trong số trước đó sao cho số tạo được trong mỗi bước đều là
một số nguyên tố có bốn chữ số.
Bài toán đặt ra là với một cặp số nguyên tố đầu vào, cần ít nhất bao nhiêu bước biến đổi theo
qui tắc trên để biến số này thành số kia.
Ví dụ hai số 1033 và 8179 thì cần ít nhất 6 bước biến đổi như sau:
1033
1733
3733
3739
3779
8779
8179
Input: cho trong tệp PRIMECHANGE.INP có cấu trúc như sau: Dòng đầu tiên chứa
số T là số test. T dòng tiếp theo, mỗi dòng chứa hai số nguyên tố có 4 chữ số là M và
N cách nhau một dấu cách.
Output: ghi ra tệp PRIMECHANGE.OUT gồm T dòng, mỗi dòng tương ứng là số
bước ít nhất để biến đổi M thành N cho trong tệp input.
Ràng buộc: 1 ≤ T ≤ 102
Ví dụ:
PRIMECHANGE.INP PRIMECHANGE.OUT
3 6
1033 8179 7
1373 8017 0
1033 1033
Phần IV. Thuật toán và chương trình

Bài 1: Mật khẩu – PASS.PAS


Thuật toán 1: kiểm tra số n có phải là số nguyên tố không bằng cách kiểm tra các ước
trong phạm vi từ 2 đến trunc(sqrt(n)):
const fi = 'pass.inp';
fo = 'pass.out';
var n,a,i : longint;

function ktnt(n : longint) : boolean;


var i : longint;
begin
if n = 1 then exit(false);
if n < 4 then exit(true);
for i := 2 to trunc(sqrt(n)) do
if (n mod i = 0) then exit(false);
exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n);
for i := 1 to n do
begin
readln(a);
if ktnt(a) then writeln(1) else writeln(0);
end;
close(input); close(output);
END.
Thuật toán 2: kiểm tra số n có phải là số nguyên tố không bằng cách chỉ kiểm tra các
ước có dạng 6k  1:
const fi = 'pass.inp';
fo = 'pass.out';
var n,a,i : longint;

function ktnt(n : longint) : boolean;


var i : longint;
begin
if n = 1 then exit(false);
if (n = 2) or (n = 3) then exit(true);
if (n mod 2 = 0) or (n mod 3 = 0) then exit(false);
i := 5;
while i <= trunc(sqrt(n)) do
if (n mod i = 0) or (n mod (i + 2) = 0) then exit(false) else inc(i,6);
exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n);
for i := 1 to n do
begin
readln(a);
if ktnt(a) then writeln(1) else writeln(0);
end;
close(input); close(output);
END.
Bài 2: Đếm số nguyên tố trong đoạn – PCOUNTAB.PAS
Thuật toán 1: dùng hàm kiểm tra nguyên tố và đếm:
const fi = 'pcountab.inp';
fo = 'pcountab.out';
var k,a,b,i,j,dem : longint;

function ktnt(n : longint) : boolean;


var i : longint;
begin
if n = 1 then exit(false);
if (n = 2) or (n = 3) then exit(true);
if (n mod 2 = 0) or (n mod 3 = 0) then exit(false);
i := 5;
while i <= trunc(sqrt(n)) do
if (n mod i = 0) or (n mod (i + 2) = 0) then exit(false) else inc(i,6);
exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(k);
for i := 1 to k do
begin
readln(a,b); dem := 0;
for j := a to b do
if ktnt(j) then inc(dem);
writeln(dem);
end;
close(input); close(output);
END.

Thuật toán 2: dùng sàng nguyên tố rồi đếm trực tiếp trên sàng:
const fi = 'pcountab.inp';
fo = 'pcountab.out';
nmax = trunc(1e7);
var k,a,b,i,j,dem : longint;
nt : array[1..nmax] of boolean;

procedure snt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(nmax)) do
if nt[i] then
begin
j := i*i;
while j <= nmax do
begin
nt[j] := false; j := j + i;
end;
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
snt;
readln(k);
for i := 1 to k do
begin
readln(a,b); dem := 0;
for j := a to b do
if nt[j] then inc(dem);
writeln(dem);
end;
close(input); close(output);
END.
Thuật toán 3: dùng sàng và dùng mảng tính trước:
const fi = 'pcountab.inp';
fo = 'pcountab.out';
nmax = trunc(1e7);

var k,a,b,i : longint;


nt : array[1..nmax] of boolean;
pc : array[0..nmax] of longint;

procedure snt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(nmax)) do
if nt[i] then
begin
j := i*i;
while j <= nmax do
begin
nt[j] := false; j := j + i;
end;
end;
pc[0] := 0; pc[1] := 0;
for i := 2 to nmax do
if nt[i] then pc[i] := pc[i-1] + 1 else pc[i] := pc[i-1];
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
snt;
readln(k);
for i := 1 to k do
begin
readln(a,b);
writeln(pc[b] - pc[a-1]);
end;
close(input); close(output);
END.

Bài 3: Số nguyên tố ghép – MPRIME.PAS


Thuật toán 1: dùng sàng nguyên tố, ghép và kiểm tra:
const fi = 'mprime.inp';
fo = 'mprime.out';
var a : array[0..20000] of longint;
b : array[0..200000] of boolean;
c : array[1..500] of int64;
k : integer;

procedure sangnt;
var i,j : longint;
begin
fillchar(b,sizeof(b),true);
b[1] := false;
for i := 2 to trunc(sqrt(200000)) do
if b[i] then
begin
j := i * i;
while j <= 200000 do
begin
b[j] := false; j := j + i;
end;
end;
j := 1;
for i := 1 to 200000 do
if b[i] then begin a[j] := i; inc(j); end;
end;
function gx(a,b : longint) : int64;
var s1,s2 : string;
i : longint;
begin
str(a,s1);str(b,s2);
val(s1 + s2,gx);
end;

function nt(a : int64) : boolean;


var i : longint;
begin
if a = 1 then exit(false);
if a < 4 then exit(true);
for i := 2 to trunc(sqrt(a)) do
if a mod i = 0 then exit(false);
exit(true);
end;

procedure xl;
var i,j : longint;
begin
j := 0; i := 1;
while j < k do
begin
if nt(gx(a[i],a[i + 1])) then
begin
inc(j);
c[j] := gx(a[i],a[i + 1]);
end;
inc(i,2);
end;
write(c[k]);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(k);
sangnt;
xl;
close(input); close(output);
END.

Thuật toán 2: như thuật toán 1 nhưng không dùng mảng c lưu các số nguyên tố ghép
mà chỉ cần dùng một biến res để lưu kết quả:
const fi = 'mprime.inp';
fo = 'mprime.out';

var a : array[0..20000] of longint;


b : array[0..200000] of boolean;
k : integer;
res : int64;

procedure sangnt;
var i,j : longint;
begin
fillchar(b,sizeof(b),true);
b[1] := false;
for i := 2 to trunc(sqrt(200000)) do
if b[i] then
begin
j := i * i;
while j <= 200000 do
begin
b[j] := false;
j := j + i;
end;
end;
j := 1;
for i := 1 to 200000 do
if b[i] then
begin
a[j] := i;
inc(j);
end;
end;

function gx(a,b : longint) : int64;


var s1,s2 : string;
i : longint;
begin
str(a,s1);str(b,s2);
val(s1 + s2,gx);
end;

function nt(a : int64) : boolean;


var i : longint;
begin
if a = 1 then exit(false);
if a < 4 then exit(true);
for i := 2 to trunc(sqrt(a)) do
if a mod i = 0 then exit(false);
exit(true);
end;

procedure xl;
var i,j : longint;
begin
j := 0; i := 1;
while j < k do
begin
if nt(gx(a[i],a[i + 1])) then
begin
inc(j);
if j = k then res := gx(a[i],a[i + 1])
end;
inc(i,2);
end;
write(res);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(k);
sangnt;
xl;
close(input); close(output);
END.

Thuật toán 3: như thuật toán 2 nhưng cải tiến hàm kiểm tra số nguyên tố giúp chương
trình chạy nhanh hơn:
const fi = 'mprime.inp';
fo = 'mprime.out';
nmax = 500000;

var b : array [1..nmax] of boolean;


p : array [1..50000] of longint;
res : int64;
n,np,k : longint;

procedure taop;
var i,j : longint;
begin
b[1] := false;
for i := 2 to nmax do b[i] := true;
for i := 2 to trunc(sqrt(nmax)) do
if b[i] then
begin
j := i*i;
while j <= nmax do
begin
b[j] := false; j := j + i;
end;
end;
np := 0;
for i := 2 to nmax do
if b[i] then begin inc(np); p[np] := i; end;
end;

function ktnt(c : int64) : boolean;


var i : longint;
begin
for i := 1 to np do
begin
if c mod p[i] = 0 then exit(false);
if int64(p[i])*p[i] >= c then exit(true);
end;
end;

procedure tinh;
var st,st1 : string;
so : int64;
i,dem : longint;
begin
res := 0; dem := 0; i := 1;
while dem < k do
begin
str(p[i],st); str(p[i+1],st1);
st := st + st1; val(st,so);
if ktnt(so) then
begin
inc(dem);
if dem = k then res := so;
end;
i := i + 2;
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taop;
read(k); tinh; write(res);
close(input); close(output);
END.

Bài 4. Số siêu nguyên tố - SPRIME.PAS


Thuật toán 1: dùng hàm kiểm tra số nguyên tố và hàm kiểm tra số siêu nguyên tố rồi
đếm:
const fi = 'sprime.inp';
fo = 'sprime.out';
var a,b,i,dem : longint;

function nt(k : longint) : boolean;


var i : longint;
begin
if k = 1 then exit(false);
if k < 4 then exit(true);
for i:= 2 to trunc(sqrt(k)) do
if k mod i = 0 then exit(false);
exit(true);
end;

function snt(k : longint) : boolean;


begin
snt := true;
while k > 0 do
begin
if nt(k) then k := k div 10
else exit(false);
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(a,b); dem := 0;
for i := a to b do
if snt(i) then begin inc(dem); writeln(i); end;
if dem = 0 then write('NO');
close(input); close(output);
END.

Thuật toán 2: sàng nguyên tố cho đoạn [1,b] và dùng sàng này để kiểm tra các số siêu
nguyên tố:
const fi = 'sprime.inp';
fo = 'sprime.out';
nmax = trunc(1e7);

var a,b,i,n,dem : longint;


nt : array[1..nmax] of boolean;

procedure sangnt;
var i,j : longint;
begin
for i := 1 to namx do nt[i] := true;
nt[1] := false;
for i := 2 to trunc(sqrt(nmax)) do
if nt[i] then
begin
j := i*i;
while j <= nmax do
begin
nt[j] := false;
j := j + i;
end;
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sangnt;
read(a,b); dem := 0;
for i := a to b do
begin
n := i;
while nt[n] do n := n div 10;
if n = 0 then begin inc(dem); writeln(i); end;
end;
if dem = 0 then write('NO');
close(input); close(output);
END.

Thuật toán 3: dùng sàng nguyên tố kết hợp sàng các số siêu nguyên tố:
const fi = 'sprime.inp';
fo = 'sprime.out';
nmax = trunc(1e7);

var nt,snt : array[0..nmax] of boolean;


a,b,dem,i : longint;

procedure sangsnt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true); nt[1] := false;
fillchar(snt,sizeof(snt),false);
for i := 1 to trunc(sqrt(nmax)) do
if nt[i] then
for j := 1 to (nmax - i) div i do nt[i + i*j] := false;
snt[2] := true; snt[3] := true; snt[5] := true; snt[7] := true;
for i := 10 to nmax do snt[i] := snt[i div 10] and nt[i];
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sangsnt;
readln(a,b); dem := 0;
for i := a to b do
if snt[i] then begin inc(dem); writeln(i); end;
if dem = 0 then write('NO');
close(input); close(output);
END.

Bài 5. Đếm các thừa số nguyên tố - COUNTPRD.PAS


Thuật toán 1: Duyệt kiểm tra các ước nguyên tố.
const fi = 'countprd.inp';
fo = 'countprd.out';

var i,n : int64;


res : longint;

function ktnt(n : int64) : boolean;


var i : longint;
begin
for i := 2 to trunc(sqrt(n)) do
if n mod i = 0 then exit(false);
exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(n); i := 2;
while i <= n do
begin
if n mod i = 0 then
if ktnt(i) then res := res + 1;
inc(i);
end;
write(res);
close(input); close(output);
END.

Thuật toán 2: Dùng thuật toán phân tích thừa số nguyên tố để đếm.
const fi = 'countprd.inp';
fo = 'countprd.out';

var i,res,k,m : longint;


n : int64;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n);
i := 2; res:=0; m := trunc(sqrt(n));
while (n > 1) and (i <= m) do
if n mod i <> 0 then inc(i)
else
begin
inc(res);
while n mod i = 0 do n := n div i;
end;
if n <> 1 then inc(res);
write(res);
close(input); close(output);
END.

Thuật toán 3: sàng trước các số nguyên tố rồi dùng thuật toán phân tích thừa số
nguyên tố để đếm.
const fi = 'countprd.inp';
fo = 'countprd.out';
nmax = trunc(sqrt(1e10));

var i,m,n,res : int64;


nt : array[1..nmax] of boolean;
a : array[1..nmax] of longint;

procedure sangnt;
var i,j,dem : longint;
begin
for i := 1 to nmax do nt[i] := true;
nt[1] := false;
for i := 2 to trunc(sqrt(nmax)) do
if nt[i] then
begin
j := i*i;
while j <= nmax do
begin
nt[j] := false;
inc(j,i);
end;
end;
dem := 0;
for i := 2 to nmax do
if nt[i] then
begin
inc(dem); a[dem] := i;
end;
m := dem;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(n); i := 1;
sangnt;
while (n <> 1) and (i <= m) do
begin
if n mod a[i] <> 0 then inc(i)
else
begin
inc(res);
while n mod a[i] = 0 do n := n div a[i];
inc(i);
end;
end;
if i > m then inc(res);
write(res);
close(input); close(output);
END.

Bài 6. Đếm ước nguyên tố - CPRDIV.PAS


Ta phân tích N!, M!, (N-M)! thành tích của các số nguyên tố như sau:
N! = 2x1 * 3x2 * …
M! = 2y1 * 3y2 * …
(N-M)!= 2z1 * 3z2 *…
Khi đó S = 2x1-y1-z1 * 3x2-y2-z2 * …
=> số lượng ước nguyên tố của S là số bộ xi,yi,zi ở công thức trên thỏa mãn xi-yi-zi >0

Thuật toán 1: phân tích trực tiếp và đếm.


const fi = 'cprdiv.inp';
fo = 'cprdiv.out';

var m,n,i,dem : longint;


x,y,z,d : array[1..100000] of longint;
p : array[0..100000] of boolean;

procedure sangnt;
var i,j:Longint;
begin
fillchar(p,sizeof(p),true);
p[1]:=false;
for i:=2 to trunc(sqrt(60000)) do
if p[i] then
begin
j := i*i;
while j <= 60000 do
begin
p[j] := false;
j := j+i;
end;
end;
end;

procedure mu(t : longint);


var j,i : longint;
begin
if p[t] then begin inc(d[t]); exit; end;
j := 2;
while t > 1 do
if t mod j = 0 then
begin
t := t div j;
inc(d[j]);
end
else inc(j);
end;
BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n,m);
sangnt;
fillchar(d,sizeof(d),0);
for i := 2 to n do mu(i);
for i := 2 to n do x[i] := d[i];
fillchar(d,sizeof(d),0);
for i := 2 to m do mu(i);
for i := 2 to m do y[i] := d[i];
fillchar(d,sizeof(d),0);
for i := 2 to n-m do mu(i);
for i := 2 to n-m do z[i] := d[i];
for i := 1 to n do
if x[i]-y[i]-z[i] > 0 then inc(dem);
write(dem);
END.

Thuật toán 2: dùng công thức Lagrăng tính số mũ của số nguyên tố P trong phân tích
thành tích các thừa số nguyên tố của N! = [N/P] + [N/(P2)] + … [N/(Pk)].
const fi = 'cprdiv.inp';
fo = 'cprdiv.out';
type mang = array[1..10000] of longint;
var m,n,ts,res,d : longint;
pm,pn,pnm,nt : mang;
mm,mn,mnm : mang;
kt : array[1..60000] of boolean;

procedure sangnt;
var i,j : longint;
begin
fillchar(kt,sizeof(kt),true);
kt[1] := false;
i := 2;
while i <= trunc(sqrt(60000)) do
begin
if kt[i] then
begin
j := i*i;
while j <= 60000 do
begin
kt[j] := false;
inc(j,i);
end;
end;
inc(i);
end;
for i := 2 to 60000 do
if kt[i] then
begin
inc(d); nt[d] := i;
end;
end;

function mu(n,s : longint) : longint;


var tmp : longint;
begin
tmp := 0;
while n >= s do
begin
tmp := tmp + n div s;
n := n div s;
end;
exit(tmp);
end;

procedure ptgt(so : longint; var a : mang);


var i : longint;
begin
for i := 1 to d do
a[i] := mu(so,nt[i]);
end;

procedure tinh;
var i : longint;
begin
res := 0;
for i := 1 to d do
if mn[i] - mm[i] - mnm[i] > 0 then inc(res);
write(res);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(n,m);
sangnt;
ptgt(n,mn);
ptgt(m,mm);
ptgt(n-m,mnm);
tinh;
close(input); close(output);
END.

Bài 7. Tìm K - KFIND.PAS


Ta có:

Để n! chia hết cho MK ta phải có yi - Kxi ≥ 0 với mọi i, 1 ≤ i ≤ r. Hay ta phải có:

Vì K là số nguyên nên K cần tìm là giá trị bé nhất trong tất cả các thương yi DIV xi.
Thuật toán 1: phân tích trực tiếp N! và M thành tích các thừa số nguyên tố rồi tìm K
theo lập luận ở trên.
uses math;
const fi = 'kfind.inp';
fo = 'kfind.out';

var sl,slm : array[0..1000001] of longint;


res,i,n,m : longint;

procedure ptichn(x : longint);


var y,j : longint;
begin
y := x; j := 2;
while y >= j do
begin
while y mod j = 0 do
begin
y := y div j;
inc(sl[j]);
end;
inc(j);
end;
end;

procedure ptichm;
var j : longint;
begin
j := 2;
while m >= j do
begin
while m mod j = 0 do
begin
m := m div j;
inc(slm[j]);
end;
inc(j);
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n,m);
for i := 1 to n do
begin sl[i] := 0; slm[i] := 0; end;
for i := 2 to n do ptichn(i);
ptichm;
res := maxlongint;
for i := 2 to n do
if sl[i] * slm[i] <> 0 then res := min(res,sl[i] div slm[i]);
writeln(res);
close(input); close(output);
END.

Thuật toán 2: phân tích N! sử dụng công thức Lagrăng như trình bày ở bài 6.
const fi = 'kfind.inp';
fo = 'kfind.out';

var i,t,d,dem,n,m,dm,dn,min : longint;


nt : array[1..100000] of longint;
pm,pn,xm,xn : array[1..10000] of longint;

procedure TaoNT;
var t,i,j : longint;
begin
d := 2; nt[1] := 2; nt[2] := 3;
for i := 5 to 1000000 do
begin
j := 1;
while (nt[j] * nt[j] < i) and (i mod nt[j] <> 0) do inc(j);
if nt[j] * nt[j] > i then
begin inc(d); nt[d] := i; end;
end;
end;

procedure PTM;
var i,j,dem,d : Longint;
begin
i := 1;
while m <> 1 do
begin
if m mod nt[i] = 0 then
begin
dem := 0; inc(dm);
while (m mod nt[i] = 0) do
begin
dem := dem + 1;
m := m div nt[i];
end;
pm[dm] := nt[i]; xm[dm] := dem;
end;
inc(i);
end;
end;

function mu(n,s : longint) : longint;


var tmp : longint;
begin
tmp := 0;
while n >= s do
begin
tmp := tmp + n div s;
n := n div s;
end;
exit(tmp);
end;

procedure PTNGT;
var i : longint;
begin
for i := 1 to dm do
xn[i] := mu(n,pm[i]);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
read(n,m);
TaoNT;
PTM;
PTNGT;
min := xn[1] div xm[1];
for i := 2 to dm do
if min > xn[i] div xm[i] then min := xn[i] div xm[i];
write(min);
close(input); close(output);
END.
Bài 8. Số có 3 ước - TNUM.PAS
Thuật toán 1: dùng hàm kiểm tra số TNUM (đếm số ước)
const fi = 'tnum.inp';
fo = 'tnum.out';
var n,i : longint;
m : int64;

function kttnum(m : int64) : boolean;


var i,dem : int64;
begin
dem := 0; i := 2;
while i <= m div 2 do
begin
if m mod i = 0 then inc(dem);
if dem > 1 then exit(false);
inc(i);
end;
if dem = 0 then exit(false) else exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(n);
for i := 1 to n do
begin
read(m);
if kttnum(m) then writeln('YES') else writeln('NO');
end;
close(input); close(output);
END.
Thuật toán 2: nhận định: số có đúng 3 ước khi có một số là ước nguyên tố của số đó
và bình phương của ước này bằng chính số đó => tạo mảng các số nguyên tố, tìm kiếm
nhị phân.
const maxp = 1000001;
fi = 'tnum.inp';
fo = 'tnum.out';

var nt : array[1..maxp] of boolean;


a : array[1..maxp] of longint;
dem,n : longint;

procedure taont;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(maxp)) do
if nt[i] then
begin
j := i * i;
while j <= maxp do
begin
nt[j] := false;
j := j + i;
end;
end;
for i := 2 to maxp do
if nt[i] then begin inc(dem); a[dem] := i; end;
end;

function tknp(s:int64;l,r:longint) : boolean;


var i,j,mid : longint;
begin
while l <= r do
begin
mid := (l+r) div 2;
if sqrt(s) = a[mid] then exit(true)
else if sqrt(s) > a[mid] then l := mid + 1 else r := mid - 1;
end;
exit(false);
end;

procedure xuli;
var i,j : longint;
so : int64;
begin
readln(n);
for i := 1 to n do
begin
read(so);
if tknp(so,1,dem) then writeln('YES')
else writeln('NO');
end;
end;
BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taont;
xuli;
close(input); close(output);
END.

Thuật toán 3: dùng sàng nguyên tố và kiểm tra.


const maxp = 1000001;
fi = 'tnum.inp';
fo = 'tnum.out';

var nt : array[1..maxp] of boolean;


n : int64;

procedure sangnt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(maxp)) do
if nt[i] then
begin
j := i * i;
while j <= maxp do
begin
nt[j] := false;
j := j + i;
end;
end;
end;
procedure xuli;
var i,j : longint;
so : int64;
begin
readln(n);
for i := 1 to n do
begin
read(so); n := trunc(sqrt(so));
if (nt[n]) and (n*n = so) then writeln('YES')
else writeln('NO');
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sangnt;
xuli;
close(input); close(output);
END.

Bài 9: Số có ít nhất 3 thừa số nguyên tố – TPRIMEFAC.PAS


Thuật toán 1: dùng sàng nguyên tố và hàm kiểm tra số TPRIMEFAC để tính trước
mảng các số TPRIMEFAC
const fi = 'tprimefac.inp';
fo = 'tprimefac.out';

var t,n,i : longint;


nt,tp : array[1..10000] of longint;
kt : array[1..20060] of boolean;

procedure taont;
var i,j,dem : longint;
begin
fillchar(kt,sizeof(kt),true);
kt[1] := false; dem := 0;
for i := 2 to trunc(sqrt(20060)) do
if kt[i] then
begin
j := i*i;
while j <= 20060 do
begin
kt[j] := false;
j := j + i;
end;
end;
for i := 2 to 20060 do
if kt[i] then begin inc(dem); nt[dem] := i; end;
end;

function kttp(n : longint) : boolean;


var i,c : longint;
begin
c := 0; i := 1;
while n <> 1 do
begin
if n mod nt[i] = 0 then
begin
inc(c); if c >= 3 then exit(true);
while n mod nt[i] = 0 do n := n div nt[i];
end;
inc(i);
end;
exit(false);
end;

procedure taotp;
var i,j,dem : longint;
begin
tp[1] := 30; dem := 1; j := 31;
while dem <= 10000 do
begin
while not kttp(j) do inc(j);
inc(dem); tp[dem] := j; inc(j);
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taont;
taotp;
readln(t);
for i := 1 to t do
begin
readln(n);
writeln(tp[n]);
end;
close(input); close(output);
END.
Thuật toán 2: kết hợp kiểm tra số TPRIMEFAC trong khi sàng
const fi = 'tprimefac.inp';
fo = 'tprimefac.out';
nmax = trunc(1e6);
var tp,b : array[0..nmax] of longint;
t,dem,n : longint;

procedure taotp;
var i,j : longint;
begin
dem := 0;
for i := 1 to nmax do b[i] := 0;
for i := 2 to trunc(sqrt(nmax)) do
if b[i] = 0 then
for j := 1 to (nmax - i) div i do b[i + i*j] := b[i + i*j] + 1;
for i := 1 to nmax do
if b[i] >= 3 then
begin
inc(dem); tp[dem] := i;
if dem > 10000 then break;
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taotp;
readln(t);
while t > 0 do
begin
t := t - 1;
readln(n);
writeln(tp[n]);
end;
close(input); close(output);
END.

Bài 10: Dãy thừa số nguyên tố thứ nhất – FPFACTSEQ.PAS


Thuật toán 1: sàng các số nguyên tố, dùng hàm tìm thừa số nguyên tố nhỏ nhất và tính
trước dãy.
const fi = 'fpfactseq.inp';
fo = 'fpfactseq.out';
maxn = 10000000;
var t,n,i,dem,t1,t2 : longint;
nt : array[1..maxn] of longint;
f : array[0..maxn] of int64;
kt : array[1..maxn] of boolean;

procedure taont;
var i,j : longint;
begin
fillchar(kt,sizeof(kt),true);
kt[1] := false;
for i := 2 to trunc(sqrt(maxn)) do
if kt[i] then
begin
j := i*i;
while j <= maxn do
begin
kt[j] := false;
j := j + i;
end;
end;
for i := 2 to maxn do
if kt[i] then begin inc(dem); nt[dem] := i; end;
end;

function fpf(m : longint) : longint;


var i : longint;
begin
if kt[m] then exit(m);
for i := 1 to dem do
if m mod nt[i] = 0 then exit(nt[i]);
end;

procedure taof;
var i : longint;
begin
f[0] := 0; f[1] := 0;
for i := 2 to maxn do
f[i] := f[i-1] + fpf(i);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taont;
taof;
readln(t);
for i := 1 to t do
begin
readln(n);
writeln(f[n]);
end;
close(input); close(output);
END.
Thuật toán 2: kết hợp tìm thừa số nguyên tố nhỏ nhất trong khi sàng và tính trước dãy.
const fi = 'fpfactseq.inp';
fo = 'fpfactseq.out';
nmax = trunc(1e7);

var lp,pc : array[0..nmax] of longint;


t : array[0..nmax] of int64;
pr : array[0..6000000] of longint;
n,i,dem : longint;

procedure snt;
var i,j : longint;
begin
fillchar(lp,sizeof(lp),0); dem := 0;
for i := 2 to nmax do
begin
if lp[i] = 0 then
begin
lp[i] := i;
dem := dem + 1; pr[dem] := i;
end;
j := 1;
while (j <= dem) and (pr[j] <= lp[i]) and (i*pr[j] <= nmax) do
begin
lp[i*pr[j]] := pr[j]; j := j + 1;
end;
end;
t[0] := 0; t[1] := 0;
for i := 2 to nmax do t[i] := t[i-1] + lp[i];
end;

BEGIN
assign(input,fi) ; reset(input);
assign(output,fo); rewrite(output);
snt;
readln(n);
for i := 1 to n do
begin
readln(n); writeln(t[n]);
end;
close(input); close(output);
END.

Bài 11: Sự phân bố các số nguyên tố – PDISTRIBU.PAS


Thuật toán 1: sàng đến 108 và tính trước dãy π(x).
const fi = 'pdistribu.inp';
fo = 'pdistribu.out';
maxn = 100000000;
var nt : array[1..maxn] of boolean;
pc : array[1..maxn] of longint;
x : longint;

procedure sang;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(maxn)) do
if nt[i] then
begin
j := i*i;
while j <= maxn do
begin
nt[j] := false;
j := j + i;
end;
end;
pc[1] := 0;
for i := 2 to maxn do
if nt[i] then pc[i] := pc[i-1] + 1 else pc[i] := pc[i-1];
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sang;
readln(x);
while x <> 0 do
begin
writeln(abs(pc[x]-x/ln(x))/pc[x]*100:0:1);
readln(x);
end;
close(input); close(output);
END.
Thuật toán 2: sàng cải tiến đến 108 và tính trước dãy π(x).
const fi = 'pdistribu.inp';
fo = 'pdistribu.out';
nmax = trunc(1e8);

var lp,pc : array[0..nmax] of longint;


pr : array[0..nmax div 2] of longint;
n,i : longint;

procedure snt;
var i,j,dem : longint;
begin
fillchar(lp,sizeof(lp),0); dem := 0;
for i := 2 to nmax do
begin
if lp[i] = 0 then
begin
lp[i] := i;
dem := dem + 1; pr[dem] := i;
end;
j := 1;
while (j <= dem) and (pr[j] <= lp[i]) and (i*pr[j] <= nmax) do
begin
lp[i*pr[j]] := pr[j]; j := j + 1;
end;
end;
pc[2] := 1;
for i := 3 to nmax do
if lp[i] = i then pc[i] := pc[i-1] + 1 else pc[i] := pc[i-1];
end;

BEGIN
assign(input,fi) ; reset(input);
assign(output,fo); rewrite(output);
snt;
readln(n);
while n <> 0 do
begin
writeln(abs(pc[n]-n/ln(n))/pc[n]*100:0:1);
readln(n);
end;
close(input); close(output);
END.
Thuật toán 3: thuật toán 1 và 2 sàng đến 108 vì vậy thời gian chạy chương trình rất
chậm (gần 2s). Vì vậy thuật toán 3 xử lý như sau: sắp tăng theo các giá trị xi, chỉ sàng
đến 106, nếu xi <= 106 thì tính như thuật toán 1 và 2, nếu xi > 106 thì kết hợp dùng hàm
sang1 bên dưới để đếm thêm số lượng các số nguyên tố từ xi-1 đến xi
const fi = 'pdistribu.inp';
fo = 'pdistribu.out';
nmax = trunc(1e6);

var a,c : array[0..1001] of longint;


ans : array[0..1001] of extended;
bo : array[0..nmax] of boolean;
co : array[0..nmax] of longint;
b : array[0..100*nmax] of boolean;
n, i, count : longint;

procedure qsort(dd,cc : longint);


var i,j,g,t : longint;
begin
if dd >= cc then exit;
i := dd; j := cc; g := a[(i + j) div 2];
repeat
while g > a[i] do inc(i);
while g < a[j] do dec(j);
if i <= j then
begin
t := a[i]; a[i] := a[j]; a[j] := t;
t := c[i]; c[i] := c[j]; c[j] := t;
inc(i); dec(j);
end;
until i > j;
qsort(dd,j); qsort(i,cc);
end;

procedure sang(x : longint);


var i,j : longint;
begin
for i := 1 to x do bo[i] := true; bo[1] := false;
for i := 2 to trunc(sqrt(x)) do
if bo[i] then for j := 1 to (x - i) div i do bo[i + i*j] := false;
for i := 2 to x do
if bo[i] then co[i] := co[i - 1] + 1 else co[i] := co[i - 1];
end;

function sang1(d,c : longint) : longint;


var i,j : longint;
begin
for i := 0 to c - d do b[i] := true;
for i := 2 to trunc(sqrt(c)) do
if bo[i] then
begin
if d mod i = 0 then j := d div i else j := (d div i) + 1;
while j*i <= c do
begin
b[j*i - d] := false;
j := j + 1;
end;
end;
j := 0;
for i := 0 to c - d do
if b[i] then j := j + 1;
exit(j);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
n := 0;
repeat
readln(i);
if i = 0 then break;
n := n + 1; a[n] := i; c[n] := n;
until i = 0;
qsort(1,n);
sang(nmax);
a[0] := 1; a[n+1] := nmax + 1;
count := 0;
for i := 1 to n + 1 do
begin
if a[i] > nmax then break;
ans[c[i]] := abs(co[a[i]] - a[i]/ln(a[i])) / co[a[i]] * 100;
end;
count := co[nmax]; a[i - 1] := nmax;
if i < n + 1 then
begin
for i := i to n do
begin
count := count + sang1(a[i - 1] + 1, a[i]);
ans[c[i]] := abs(count - a[i]/ln(a[i])) / count * 100;
end;
end;
for i:= 1 to n do writeln(ans[i]:0:1);
close(input); close(output);
END.

Bài 12: Phân tích thừa số nguyên tố – PFACTOR.PAS


Thuật toán 1: thuật toán trực tiếp (không trình bày)
Thuật toán 2: sàng các số nguyên tố, tính số mũ bằng công thức Lagrăng.
const fi = 'pfactor.inp';
fo = 'pfactor.out';
maxn = 10000;
var n,dem,i,d,t : longint;
kt : array[1..maxn] of boolean;
nt : array[1..maxn] of longint;
sm : array[1..maxn] of longint;

procedure taont;
var i,j : longint;
begin
fillchar(kt,sizeof(kt),true);
kt[1] := false; dem := 0;
for i := 2 to trunc(sqrt(maxn)) do
if kt[i] then
begin
j := i*i;
while j <= maxn do
begin
kt[j] := false;
j := j + i;
end;
end;
for i := 2 to maxn do
if kt[i] then begin inc(dem); nt[dem] := i; end;
end;

function mu(m,s : longint) : longint;


var tmp : longint;
begin
tmp := 0;
while m >= s do
begin
tmp := tmp + m div s;
m := m div s;
end;
exit(tmp);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taont; read(n); d := 0; t := 0;
for i := 1 to dem do sm[i] := mu(n,nt[i]);
for i := 1 to dem do
if sm[i] > 0 then inc(d);
for i := 1 to dem do
if sm[i] > 0 then
begin
if t = d-1 then write(nt[i],'^',sm[i])
else write(nt[i],'^',sm[i],' * ');
t := t + 1;
end;
close(input); close(output);
END.

Bài 13: Mật mã số nguyên tố – PRIPASS.PAS


Thuật toán 1: dùng hàm kiểm tra nguyên tố
const fi = 'pripass.inp';
fo = 'pripass.out';
var a,b,i,j,t : longint;
function ktnt(n : longint) : boolean;
var i : longint;
begin
if n = 1 then exit(false);
if (n = 2) or (n = 3) then exit(true);
if (n mod 2 = 0) or (n mod 3 = 0) then exit(false);
i := 5;
while i <= trunc(sqrt(n)) do
if (n mod i = 0) or (n mod (i + 2) = 0) then exit(false) else
inc(i,6);
exit(true);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(t);
for i := 1 to t do
begin
readln(a,b);
for j := a to b do
if ktnt(j) then writeln(j);
writeln;
end;
close(input); close(output);
END.
Thuật toán 2: dùng sàng đến 107, nếu b <= 107 thì dùng sàng để ghi ra, ngược lại thì
dùng hàm ktnt có kết hợp đánh dấu các snt bằng mảng f.
const fi = 'pripass.inp';
fo = 'pripass.out';
maxn = 10000000;

var t,a,b,i,j : longint;


f : array[0..1000000000] of boolean;

function ktnt(x : longint) : boolean;


var k : longint;
begin
if (x = 2) or (x = 3) then exit(true);
if (x = 1) or (x mod 2 = 0) or (x mod 3 = 0) then exit(false);
k := 5;
while k <= trunc(sqrt(x)) do
begin
if (x mod k = 0) or (x mod (k + 2) = 0) then exit(false);
inc(k,6);
end;
exit(true);
end;

procedure sangnt;
begin
for i := 1 to maxn do f[i] := true;
f[1] := false;
for i := 2 to trunc(sqrt(maxn)) do
if f[i] then
begin
j := i * i;
while j <= maxn do
begin
f[j] := false; j := j + i;
end;
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sangnt; readln(t);
for i := 1 to t do
begin
readln(a,b);
if b <= maxn then
begin
for j := a to b do
if f[j] then writeln(j);
end
else
begin
for j := a to b do
if f[j] then writeln(j)
else
if (ktnt(j)) then
begin
writeln(j);
f[j] := true;
end;
end;
writeln;
end;
close(input); close(output);
END.

Thuật toán 3: dùng sàng đến 107, nếu b <= 107 thì dùng sàng để ghi ra, ngược lại thì
chỉ sàng trên đoạn [a,b].
const fi = 'pripass.inp';
fo = 'pripass.out';
nmax = trunc(1e6);

var bo,bo1 : array[0..nmax] of boolean;


t,a,b,i,j : longint;

procedure snt(n : longint);


var i,j : longint;
begin
for i := 1 to n do bo[i] := true;
bo[1] := false;
for i := 1 to trunc(sqrt(n)) do
if bo[i] then
begin
j := i*i;
while j <= nmax do begin bo[j] := false; j := j + i; end;
end;
end;

procedure sntac(a,b : longint);


var i,j : longint;
begin
for i := 0 to b - a do bo1[i] := true;
for i := 2 to trunc(sqrt(b)) do
if bo[i] then
begin
j := a div i; if a mod j <> 0 then j := j + 1;
while i*j <= b do
begin
bo1[i*j - a] := false;
j := j + 1;
end;
end;
for i := 0 to b - a do if bo1[i] then writeln(i+a);
writeln;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
snt(nmax);
readln(t);
for i := 1 to t do
begin
readln(a,b);
if b > nmax then sntac(a,b)
else begin
for j := a to b do if bo[j] then writeln(j);
writeln;
end;
end;
close(input); close(output);
END.

Bài 14: Số siêu không nguyên tố – NSPRIME.PAS


Thuật toán: đệ quy quay lui để lập các dãy con, dùng sàng để kiểm tra
const fi = 'nsprime.inp';
fo = 'nsprime.out';
const nmax = 100000;
var t,n,a,b,tg,m : longint;
nt : array[0..nmax] of boolean;
f : array[1..nmax] of longint;
x : array[1..6] of 0..1;
ok : boolean;
s,st : string;

procedure snt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[0] := false; nt[1] := false;
for i := 2 to trunc(sqrt(nmax)) do
if nt[i] then
begin
j := i*i;
while j <= nmax do begin nt[j] := false; j := j + i; end;
end;
end;

procedure try(i:byte);
var j,k : byte;
mt : longint;
begin
for j := 0 to 1 do
begin
x[i] := j;
if i = length(s) then
begin
st := '';
for k := 1 to length(s) do
if x[k] = 1 then st := st + s[k];
if st <> '' then
begin
val(st,mt);
if nt[mt] then begin ok := false; exit; end;
end;
end
else try(i+1);
end;
end;

procedure taof;
var i : longint;
begin
f[1] := 1;
for m := 2 to nmax do
begin
str(m,s); st := ''; ok := true;
fillchar(x,sizeof(x),0);
try(1);
if ok then f[m] := f[m-1] + 1
else f[m] := f[m-1];
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
snt;
taof;
readln(t);
for t := 1 to t do
begin
readln(a,b);
if a > b then begin tg := a; a := b; b := tg; end;
writeln(f[b]-f[a-1]);
end;
close(input); close(output);
END.

Bài 15. Đoạn K_nguyên tố - KPRIMESUB.PAS


Thuật toán 1: sàng, tạo mảng tính trước và duyệt xét mọi đoạn con của đoạn [2,N]
const fi = 'kprimesub.inp';
fo = 'kprimesub.out';
maxn = 100000;
var t,n,k,res,i,p,q,tmp : longint;
kt : array[1..maxn] of boolean;
sp : array[1..maxn] of longint;

procedure taont;
var i,j,dem : longint;
begin
fillchar(kt,sizeof(kt),true);
kt[1] := false; dem := 0;
for i := 2 to trunc(sqrt(maxn)) do
if kt[i] then
begin
j := i*i;
while j <= maxn do
begin
kt[j] := false;
j := j + i;
end;
end;
sp[1] := 0;
for i := 2 to maxn do
if kt[i] then sp[i] := sp[i-1] + 1 else sp[i] := sp[i-1];
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
taont;
readln(t);
for i := 1 to t do
begin
readln(n,k); res := 0;
for p := 2 to n do
for q := p to n do
begin
tmp := sp[q] - sp[p];
if kt[p] then tmp := tmp + 1;
if tmp >= k then inc(res);
end;
writeln(res);
end;
close(input); close(output);
END.
Thuật toán 2: sàng, tính theo công thức
const fi = 'kprimesub.inp';
fo = 'kprimesub.out';
nmax = 100001;

var b : array[0..nmax] of 0..1;


t : longint;

procedure sang(x : longint);


var i,j : longint;
begin
for i := 1 to x do b[i] := 1; b[1] := 0;
for i := 2 to trunc(sqrt(x)) do
if b[i] = 1 then
for j := 1 to (x - i) div i do b[i + i*j] := 0;
end;

procedure tinh;
var i,j,d,n,k : longint;
dem : int64;
begin
readln(n,k);
if k = 0 then writeln((int64(n-1)*n) div 2)
else begin
d := 0; dem := 0; j := 2;
for i := 2 to n do
begin
d := d + b[i];
while (d >= k) do
begin
dem := dem + (n - i + 1);
d := d - b[j];
j := j + 1;
end;
end;
writeln(dem);
end;
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sang(nmax);
readln(t);
for t := 1 to t do tinh;
close(input); close(output);
END.

Bài 16. Biến đổi số nguyên tố - PRIMECHANGE.PAS


Thuật toán: BFS
const fi = 'primechange.inp';
fo = 'primechange.out';
nmax = 1000000;
var
nt : array[0..9999] of boolean;
i,s,t,test : longint;
q,c : array[0..nmax+1] of longint;
dau,cuoi : longint;

procedure sangnt;
var i,j : longint;
begin
fillchar(nt,sizeof(nt),true);
nt[1] := false;
for i := 2 to trunc(sqrt(9999)) do
if nt[i] then
begin
j := i*i;
while j <= 9999 do
begin
nt[j] := false; j := j + i;
end;
end;
end;

procedure qpush(x : longint);


begin
inc(cuoi);
q[cuoi] := x;
end;

function qpop : longint;


begin
qpop := q[dau];
inc(dau);
end;

procedure xuli;
var i,j,x,sobuoc,p,tmp,v : longint;
begin
dau := 1; cuoi := 0;
qpush(s);
fillchar(c,sizeof(c),0);
c[s] := 1;
while dau <= cuoi do
begin
x := qpop; i := 1;
while i <= 1000 do
begin
p := x div (i*10);
tmp := x mod i;
for j := 0 to 9 do
begin
v := p*i*10+i*j+tmp;
if (v > 1000) and (nt[v]) and (c[v]=0) then
begin
c[v] := c[x]+1;
if v = t then
begin
writeln(c[x]);
exit;
end;
qpush(v);
end;
end;
i:=i*10;
end;
end;
writeln(0);
end;

BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
sangnt;
readln(test);
for i := 1 to test do
begin
readln(s,t);
xuli;
end;
close(input); close(output);
END.
Lời kết

Trong chuyên đề này, tôi đã trình bày 16 bài tập; mỗi bài có đề bài, các thuật toán
và chương trình mẫu tương ứng, bộ test mỗi bài gồm 10 hoặc 15 tests được đính kèm
đường link. Tùy theo cấu hình và tốc độ máy chấm, người dùng có thể điều chình thời
gian chấm cho mỗi bài phù hợp để thấy được sự khác biệt giữa các thuật toán đã được
trình bày.
Mặc dù đã rất cố gắng, nhưng với kiến thức và kinh nghiệm còn hạn chế của bản
thân, một lần nữa rất mong nhận được sự đóng góp ý kiến, chia sẻ của quý thầy cô.
Xin chân thành cảm ơn!

You might also like