Professional Documents
Culture Documents
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ô!
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 kN, 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.
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ới số n được phân tích n p1 p2 p3 ... pr thì:
1 2 3 r
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)
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
CPRDIV.INP CPRDIV.OUT
7 3 2
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
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;
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;
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);
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.
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;
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';
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;
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;
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;
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.
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);
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);
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.
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';
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));
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.
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;
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;
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.
Để 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';
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';
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;
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;
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';
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;
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.
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.
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;
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.
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;
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);
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.
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);
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);
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.
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;
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.
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;
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);
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.
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.
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;
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.
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 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!