Bài toán số nguyên tố tối ưu
Trang 1Tối ưu các bài toán về số nguyên tố
Đinh Hữu Công
Trong số 11-2001 tácgiả Nguyễn Văn Trường đã giới thiệu các thuật toán về số nguyên tố.Nhưng một số thuật toán này còn bị hạn chế về thời gian và bộ nhớvới n lớn Tuỳ vào từng bài toán cụ thể ta có thể tối ưu hoá nhữngthuật toán này dựa vào những nhận xét và chứng minh toán học
Bài toán 1: Cho số tựnhiên n Hãy phân tích số n thành tích các thừa số nguyên tố
Input: file Phantich.inp với số n duy nhất (n < 231)
Ouput: file Phantich.out Dòng 1 là k, số thừa số nguyên tố khác nhautrong phân tích K
dòng tiếp theo dòng thứ i gồm 1 cặp số (p,q) cáchnhau 1 dấu trắng trong đó p là thừa số nguyên tố và q là số mũ tươngứng của nó trong phân tích (q > 0)
Thuật toán của bài này rất đơn giản:
+ Cho biến chạy Temp=0,
+ Lặp: Chừng nào temp≤n thì làm
- Temp:=số nguyên tố liền sau Temp;
- While (n mod Temp=0) do
Begin
n:=n div Temp;
Tăng số mũ của Temp;
End;
Hiệuquả của cài đặt chủ yếu phụ thuộc vào công đoạn tìm số nguyên tốliền sau Temp Cách nhanh nhất là dùng 1 mảng để lưu trữ các số nguyêntố từ 1 đến n Với n=1 tỷ ta phải lưu (109) = 50.847.534 số nguyên tố nên ta không thể có đủ bộnhớ để thực hiện điều này Một cách khác là ta tuần tự tăng biếnTemp và kiểm tra Temp có nguyên tố không, cách này không tốn bộ nhớnhưng không khả thi vì thời gian thực hiện quá lâu Một cách tự nhiên,ta muốn dung hoà cả hai phương pháp trên Vì vậy ta sẽ tìm cách giảmkhối lượng lưu trữ và số lần kiểm tra nguyên tố:
Bước 1: Kiểm tra n Nếu n nguyên tố hoặc n=1thìvấn đề trở nên quá đơn giản Nếu n là
hợp số thì chuyển sang bước2
Bước 2: Vì n là hợp số nên ta phải có n=a*b(1<a ≤ b) suy ra a ≤ sqrt(n)≤ 215,5 < 1 Vậy ta chỉ cần lưu các số nguyên tố nhỏ hơn 6341
Vìn< 231 nên số thừa số nguyên tố nhỏ hơn 10 (2*3*5* 31 >231), do đó ta chỉ phải kiểm tra nguyên tố khoảng 10 lần
Chươngtrình của bài này như sau:
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M65500,0,655360}
{Phantich so n<2^31 thanh thua so nguyen to}
usescrt;
Fin=′phantich.inp′;
Fou=′phantich.out′;
var p:array[1 10] of longint;
Trang 2q:array[1 10] of integer; { Số thừa số nguyên tố lớn nhất là 10?} Prime:array[0 maxPrime+1] of boolean;
n:longint;
count:integer;
temp:word;
f,g:text;
procedureEratosten;
vari,j:word;
begin
Fillchar(Prime,sizeof(Prime),true);
Prime[0]:=false;
Prime[1]:=false;
for i:=2 to trunc(sqrt(maxPrime)) do
if Prime[i] then
for j:=2 to (maxPrime div i) do
Prime[i*j]:=false;
end;
functionisPrime(n:longint):boolean;
vari,step:word;
begin
isPrime:=false;
if (n
begin
if Prime[n] then isPrime:=true;
exit;
end;
if ((n mod 2=0) or (n mod 3=0)) then Exit;
i:=5;
step:=2;
while (i≤sqrt(n)) do
begin
if (n mod i=0) then
exit;
i:=i+step;
step:=6-step;
end;
isPrime:=true;
end;
procedureSolve;
begin
Assign(f,Fin);
Reset(f);
Readln(f,n);
Close(f);
Assign(g,Fou);
Rewrite(g);
Trang 3if n<2 then
begin
writeln(g,0);
Close(g);
Halt;
end;
temp:=2;
count:=0;
Fillchar(p,sizeof(p),0);
Fillchar(q,sizeof(q),0);
while (n<>1) do
begin
if isPrime(n) then
begin
Inc(count);
p[count]:=n;
q[count]:=1;
exit;
end
else
begin
while ((n mod temp<>0) and (temp< i> begin
Inc(temp);
while not Prime[temp] do
Inc(temp);
end;
Inc(count);
p[count]:=temp;
while (n mod temp=0) do
begin
Inc(q[count]);
n:=n div temp;
end;
end;
end;
end;
procedureResult;
vari:integer;
begin
Writeln(g,count);
for i:=1 to count do
Writeln(g,p[i],' ',q[i]);
Close(g);
end;
BEGIN
Trang 4Eratosten;
Solve;
Result;
END
Saukhi làm bài toán 1 ta sẽ dễ dàng làm được bài toán số 2 trong đềkhối 10 cuộc thi Olympic 30-4 lần VIII
Đề bài như sau:
Cho số nguyên dương n (1 < n < 231) Tìm số nguyêndương a nhỏ nhất sao cho a a chia hết cho n
Input: file Chiahet.inpchứa 1 số duy nhất n
Output: file Chiahet.out chứa số a
Bàitoán 2: Tìm số mũ của số nguyên tố p trong cách phân tích của n! ra thừa sốnguyên
tố (n ≤ 231)
Cũng như bài toán 1, việc duyệt các số nguyên là bội của p từ1 đến n để chia cho p tìm số
mũ rồi lấy tổng các số mũ này là khôngthể thực hiên được
Ví dụ:
Nếun=231 còn p = 2 thì ta phải duyệt qua (n div p) số, đại lượngnày bằng 230
=1.073.741.824 tức là hơn 1 tỷ số cần phải duyệt.Nội chỉ việc cho biến i chạy qua 230 số này mà chưa làm gìcả cũng đã mất khoảng 10 giây rồi (nếu không tin bạn hãy viết
chươngtrình chỉ gồm 1 lệnh cho i chạy từ 1 đến 1 tỉ không làm gì cả vàviết hàm đếm thời gian xem)
Như vậy ta cần phải tìm 1 cách khác để tính số mũ của p Gọisi là số số chia hết cho pi
trong khoảng 1 n Số này bằng tổng của số các số chia hết cho p, p2, p3…trong khoảng 1 n Dễ thấy số mũ của p là s1+s2+…+sk với k lớn nhất sao cho pk≤ n
Vídụ: n=29 vàp =3 thì k=3, s1 =9, s2 =3, s3 =1 → Số mũ của 3 là s1+s2+s3 =13
Mặtkhác các số chia hết cho pi trong khoảng 1 n là: 1* pi, 2*pi, 3* pi, …, * pi → có
số Đây chính là nội dung củađịnh lý Lơgiăngđrơ: Số mũ của số nguyên tố p trong phân tích n!thành các thừa số nguyên tố là:
cho pi trong khoảng 1 (ndiv p)) áp dụng điều này với i=1 ta có được chươngtrình hàm tính số mũ của p trong phân tích ra thừa số nguyên tố củan!
functionSoMu(p:longint;n:longint):integer;
varcount:integer;
begin
count:=0;
Trang 5while n<>0 do
begin
n:=n div p;
Count:=count + n;
end;
SoMu:=count;
end;
số nguyên nhỏ nhất sao cho n < pk
Cụthể trong bài này trường hợp lớn nhất là n = 231; p = 2 thì
k = log2231 = 31 so với 230 (nhỏ hơn34 triệu lần) !
Bài toán 3: Tìm số chữ số 0 tận cùng và chữ số tận cùng khác 0 của n! (n< 231) Dữ liệu nhập từ bàn phím số n và in kết quả sốchữ số 0 tận cùng và chữ số tận cùng khác 0 của n!
ra màn hình
Cơsở thuật toán: Ta thấy n! khi phân tích ra thừa số nguyên tố thìcó dạng
n! = 2SoMu(2,n) * 5SoMu(5,n) *…
Trong công thức Lơgiăngđrơ nếu p1< p2 thìSoMu(p1,n) ≥ SoMu(p2,n) nên số chữ số 0 tận cùng của n! chính làSoMu(5,n)
Đặt m = SoMu(2,n) - SoMu(5,n).Bỏ đi các chữ số 0 tận cùng thì ta có: Chữ số tận cùng
m mod 4 = 0, 1, 2, 3 thì 2m mod 10 = 6, 2, 4, 8 (trừtrường hợp m = 0) Công việc còn lại
là tìm hàm TanCung(n) (Nếu bâygiờ chỉ nói tận cùng thì ta hiểu là số đó đã được chia cho 2 và5 đến dư)
Ví dụ: Tận cùng của 10 là 1, của 14 là 7 …
Nếun tương đối nhỏ thì ta có thể duyệt bằng 1 vòng for để tìm tận cùngcủa n! Nhưng n
có thể lên tới 2 tỷ nên ta xây dựng hàm TanCung(n) nhưsau (với ví dụ n = 27):
Nếu n < 10 thì duyệt bình thường Xét n ≥10, ta tính tận cùng của tích (n mod 10) số
(temp=21*22*23*24*25*26*27=21*11*23*3*1*13*27=1*1*3*3*1*3*7=9)
temp:=1;
fori:=n+1-n mod 10 to n do
begin
tg:=i;
while not ođ(tg) do
tg:=tg shr 1;
while (tg mod 5=0) do
tg:=tg div 5;
temp:=temp*(tg mod 10) mod 10;
end;
temp:=temp mod 10;
Trang 6n:=n-n mod 10;
VậyTanCung(n) = [temp*TanCung(n − n mod 10)] mod 10
Gánlại n:= n − n mod 10(n = 20) Ta phân hoạch các số từ 1 đến n thành 3phần:
-Các số không chia hết cho 2 và 5: Các số này có tận cùng là 1, 3,7, 9 ( 1, 3, 7, 9, 11, 13,
17, 19 ) mỗi loại có (n div 10) số nên tận cùngcủa tích các số này bằng tận cùng của (1*3*7*9)n div 10 = 9n div 10 Nếu (n div 10) chẵnthì số này bằng 1, lẻ thì bằng 9 (n=20, số này là 1)
if ođ(n div 10) then temp:=temp*9;{else temp:=temp*1}
(n=20 → temp=1*9=9)
-Các số là bội của 2 có dạng 2, 2*2, 2*3,…, 2*(n div 2) (2, 4, 6, 8, 10,12, 14, 16, 18, 20) Tận cùng của tích này cũng chính là tận cùng của1*2*3*…*(n div 2) =TanCung(n div 2) (Vì đã loại đi tất cả các thừa số2)
(n=20ta có 1*2*3*…*10 = TanCung(10))
-Các số là bội của 5 nhưng lẻ (không là bội của 2) có dạng 5, 5*3,5*5…5*t (t lẻ sao cho 5*t < n) (n=20 → t=3;gồm 2 số: 5, 15) Tận cùng của tích này bằng tận cùng của tích các
số lẻ 1*3*5*7*…*t (n=20 là 1*3) (vì đã loại đi tất cả các thừa số 5) Ta tính nó bằng hàm TanCungLe(n) (Tận cùng của các số lẻ không quá n)
Theo cách phân hoạch ta có (các số n sau là đã gán lại n = n− n mod 10):
TanCung(n):=temp*TanCung(n div 2)*TanCungLe(n div 5) mod 10
TanCung(27)=9*TanCung(10)*TanCungLe(4) mod 10
Cách xây dựng hàm TanCungLe(n) cũng tương tự như trên: n <10 duyệt bình thường Xét n ≥10, tính tận cùng của các số lẻ trong khoảng (n-n mod 10) n lưu vào biến temp Gán lại n:= n − n mod 10 Ta phân hoạch các số lẻ từ 1 n thành 2 phần:
-Các số không chia hết cho 5 có tận cùng la 1, 3, 7, 9 mỗi loại có (ndiv 10) số tính như phần trên
if ođ(n div 10) then temp:=temp*9;{else temp:=temp*1}
-Các số là bội của 5 có dạng 5, 5*3, 5*5,…5*t (t lẻ sao cho 5*t < n)
Tậncùng tích t số này chính là TanCungLe(n div 5)
Tacó TanCungLe(n)=temp * TanCungLe((n- n mod 10) div 5) mod 10;
Chương trình bài này cài đặt như sau:
{Timchu so cuoi cung khac khong cua n!}
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M65500,0,655360}
programChu_so_tan_cung_khac_0_cua_n_giai_thua;
usescrt;
varn,m,tc:longint;
functionTanCungLe(n:longint):integer;
vartemp,tg,i:longint;
begin
if n<=10 then
begin
temp:=1;
for i:=1 to n do
if (ođ(i) and (i<>5)) then
temp:=temp*i;
TanCungLe:=temp mod 10;
Trang 7Exit;
end;
if not ođ(n) then n:=n-1;
if ođ(n div 10) then
temp:=9
else temp:=1;
for i:=n+1-n mod 10 to n do
if ođ(i) then
begin
tg:=i;
while (tg mod 5=0) do
tg:=tg div 5;
temp:=temp*(tg mod 10) mod 10;
end;
temp:=temp mod 10;
n:=n-n mod 10;
TanCungLe:=temp*TanCungLe(n div 5) mod 10; end;
functionTanCung(n:longint):integer;
vartemp,tg,i:longint;
begin
if n<=10 then
begin
temp:=1;
for i:=1 to n do
begin
tg:=i;
while not ođ(tg) do
tg:=tg shr 1;
if (tg mod 5 =0) then
tg:=tg div 5;
temp:=(temp*tg);
end;
TanCung:=temp mod 10;
Exit;
end;
if ođ(n div 10) then temp:=9
else temp:=1;
for i:=n+1-n mod 10 to n do
begin
tg:=i;
while not ođ(tg) do
tg:=tg shr 1;
while (tg mod 5=0) do
tg:=tg div 5;
temp:=temp*(tg mod 10) mod 10;
Trang 8end;
temp:=temp mod 10;
n:=n-n mod 10;
TanCung:=temp*TanCung(n div 2)*TanCungLe(n div 5) mod 10;
end;
functionSoMu(p:longintr;n:longint):longint;
varcount:longint;
begin
count:=0;
while n<>0 do
begin
n:=n div p;
Count:=count+n;
end;
SoMu:=count;
end;
procedureSolve;
begin
Write('N=');Readln(n);
m:=SoMu(2,n)-SoMu(5,n);
if m=0 then tc:=1
else
case (m mod 4) of
0 : tc:=6;
1 : tc:=2;
2 : tc:=4;
3 : tc:=8;
end;
tc:=(tc*TanCung(n)) mod 10;
Writeln('Chu so tan cung khac 0 cua n! la : ',tc);
end;
BEGIN
Clrscr;
Solve;
Readln;
END
Với n < 231 bất kì thì thời gian tính toánkhông quá 1 giây Nếu làm bình thường bằng cách duyệt như với n ≤ 10 ở cài đặt trênthì chạy n = 10 triệu mất khoảng 73 giây (Gấp nhau đến hàng trăm lần).Như đã thấy, với cùng một bài toán vấn đề nhưng thuật toán và cáchcài đặt khác nhau thì hiệu quả của chương trình cài đặt có thể sẽkhác biệt nhau rất rõ ràng Vì vậy những kiến thức toán học dể chứngminh thuật toán và chương trình là rất cần thiết khi ta muốn tối ưuhoá một thuật toán nào đó Lần sau tôi sẽ xin trình bày với các bạnvề những kiến thức số học được ứng dụng trong giải các bài toántin