Tải bản đầy đủ (.doc) (46 trang)

Thuật toán số học C++

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (313.83 KB, 46 trang )

MỤC LỤC
2
Các bài toán nội dung số 2
2.1 Sàng Eratosthenes 2
2.2 Biểu diễn số 6
2.3 Bậc của thừa số 10
2.4 Phân tích ra thừa số nguyên tố 12
2.5 Bậc của thừa số nguyên tố trong giai thừa 18
2.6 Các số 0 tận cùng 21
2.7 Bậc của thừa số pk trong giai thừa 22
2.8 Bậc của thừa số u trong giai thừa 24
2.9 Tổng kết về bậc 27
2.10 Tổng ước 31
2.11 Tổng kết về ước 37
1
2
Các bài toán nội dung số
Ta kí sử dụng các kí hiệu sau đây:
Tập các số tự nhiên N = {0, 1, 2, }
Tập các số nguyên dương Z
+
= {1, 2, }
Tập các số nguyên âm Z

= {−1, −2, }
Tập các số nguyên Z = {0, ±1, ±2, }
2.1 Sàng Eratosthenes
Cho số nguyên dương n, nếu n là tích của hai số nguyên dương a và b, n = a⋅b, ta nói a (và b) là ước của n.
Kí hiệu U(n) là tập các ước của số nguyên dương n, S(n) là số ước của số nguyên dương n. Thí dụ,
U(6) = {1, 2, 3, 6}; S(6) = 4.
Số nguyên dương p được gọi là nguyên tố nếu p có đúng 2 ước (phân biệt) là 1 và p, S(p) = 2.


Hãy liệt kê các số nguyên tố trong khoảng 1 n.
Thuật toán
Thuật toán dưới đây do Eratosthenes đề xuất:
Ghi vào mảng nguyên p toàn bộ số nguyên tố trong khoảng 1 đến n.
Bước 1. Viết dãy số 1, 2, …, n.
Bước 2. Xóa số 1, vì 1 không phải là số nguyên tố.
Bước 3. Lặp từ 2 đến
n

3.1. Tìm tiếp số i đầu tiên chưa bị xóa: đó là số nguyên tố.
3.2. Xóa các bội của i kể từ i
2
đến n
Bước 4. Ghi nhận kết quả: Các số không bị xóa đều là nguyên tố.
Khởi trị mảng p để ghi nhận các số: nếu p[i] = 0 thì i là số nguyên tố, ngược lại, khi p[i] = 1, tức là khi i bị
xoá thì i không phải là số nguyên tố. Sau khi sinh các số nguyên tố ta duyệt lại p và chuyển các số nguyên tố
(thoả điều kiện p[i] = 0) lên đầu mảng. Hàm Sieve (sàng) cho ra số lượng các số nguyên tố trong khoảng 1 đến
n.
2
Thí dụ
int p[21];
int m = Sieve(20);
cho ta m = 8: có 8 số nguyên tố trong khoảng 1 20;
p[1] = 2; p[2] = 3; p[3] = 5; p[4] = 7; p[5] = 11; p[6] = 13; p[7] = 17; p[8] = 19;
Chương trình C++
// Sieve.cpp
// m = Sieve(n): cho ra day so nguyen to p[1 m]
// trong khoang 1 n
#include <iostream>
#include <math.h>

#include <string.h>
using namespace std;
const int MN = 500000; // Max(n) = 499999
int p[MN+1];

// The Sieve of Eratosthenes
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
p[0] = p[1] = DELETED;
int can = int(sqrt(n)); // can nguyen bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
j = 0;
for (i = 2; i <= n; ++i) {
if (p[i] == PRIME) p[++j] = i;
}
return j;
}
main() {
int n = 20;
int m = Sieve(n);
cout << endl << m << " so nguyen to tu 1 " << n << endl;
for (int i = 1; i <= m; ++i)
cout << p[i] << " ";
cout << endl << " Tong cong " << m << " so" << endl;


return 0;
}
Chương trình Pascal
(*
SIEVE.PAS: Liet ke cac so nguyen to
trong khoang 1 n theo thuat toan
Eratosthenes. Ghi vao mang p
*)
3
const MN = 500000; nl = #13#10; {xuong dong}
type int = longint;
var p: array[0 MN] of int; { Max(n) = 499999 }
function Sieve(n: int): int;
const PRIME = 0; DELETED = 1;
var i, j, can: int;
begin
can := round(sqrt(n));
fillchar(p, sizeof(p),PRIME);
p[0] := DELETED; p[1] := DELETED;
for i := 2 to can do
if p[i] = PRIME then
for j := i to (n div i) do p[i*j] := DELETED;
j := 0; { Chuyen cac so nguyen to ve dau mang p }
for i := 2 to n do
if p[i] = PRIME then
begin
inc(j); p[j] := i;
end;
Sieve := j;

end;
procedure Run;
var i, m: int;
begin
m := Sieve(20);
for i := 1 to m do write(p[i], ' ');
writeln(nl,' Tong cong ', m, ' so.');
end;
BEGIN
Run;
(* *)
write(nl,' F I N I ! ');
write(nl,'Nhan phim bat ki de ket thuc: ');
readln;
END.
Độ phức tạp (tính toán)
Hàm Sieve lặp
n
lần, mỗi lần duyệt n phần tử, vậy độ phức tạp là O(n
n
) thực hiện các phép nhân và
chia số nguyên 32 bit.
Phát triển
Bạn có thể ghi dãy số nguyên tố tìm được vào một file có tên định sẵn để tiện dùng cho các bài toán khác.
Thủ tục dưới đây giúp bạn thực hiện chức năng trên.
Chương trình C++
// Sieve.cpp
// m = Sieve(n): cho ra day so nguyen to p[1 m]
// trong khoang 1 n
#include <iostream>

#include <math.h>
#include <fstream> // De ghi file ban can thu vien nay
4
#include <string.h>
using namespace std;
const int MN = 500000;
int p[MN+1];

// The Sieve of Eratosthenes
int Sieve(int n)// xem chuong trinh truoc
// Sinh cac so nguyen to trong khoang 1 n, ghi file ten fn.
void SieveSave(char * fn, int n){
int s = Sieve(n);
ofstream f(fn);
f << s; // So luong cac so nguyen to
for (int i = 1; i <= s; ++i)
f << endl << p[i];
f.close();
}
main() {
SieveSave("primes.dat",200);

return 0;
}
Chương trình Pascal
(*
SIEVE.PAS: Liet ke cac so nguyen to
trong khoang 1 n theo thuat toan
Eratosthenes. Ghi vao mang p
*)

const MN = 500000; nl = #13#10; {xuong dong}
type int = longint;
var p: array[0 MN] of int;
function Sieve(n: int): int; { Xem chuong trinh truoc }
(* Sinh cac so nguyen to trong khoang 1 n
Ghi file fn *)
procedure SieveSave(fn: string; n: int);
var i, m: int;
f: text;
begin
m := Sieve(n);
assign(f,fn); rewrite(f); { Mo file fn }
writeln(f,m); { Ghi so luong so nguyen to }
{ Moi so ghi tren 1 dong }
for i := 1 to m do writeln(f,p[i]);
close(f);
end;
BEGIN
SieveSave('primes.dat',200);
(* *)
write(nl,' F I N I ! ');
write(nl,'Nhan phim bat ki de ket thuc: ');
readln;
END.
5
2.2 Biểu diễn số
Đếm số cách biểu diễn số nguyên dương n thành tổng các số nguyên tố liên tiếp.
Ví dụ :
12 = 5+7 (1 cách)
53 = 53 = 5+7+11+13+17 (2 cách, bản thân 53 là số nguyên tố);

41 = 41 = 2+3+5+7+11+13 = 11+13+17 (3 cách, bản thân 41 là số nguyên tố);
Số 20 không có cách nào vì các biểu diễn như 7 + 13 và 3 + 5 + 5 + 7 không gồm các số nguyên tố liên tiếp.
INPUT OUTPUT Giải thích
2
3
17
41
20
666
12
53
0
1
1
2
3
0
0
1
2
Input: Mỗi dòng một số nguyên dương không vượt
quá 100000, kết thúc là số 0 (không xử lý).
Output: Số cách biểu diễn thành tổng các số nguyên
tố liên tiếp.
Thuật toán
Chia để trị.
Kí hiệu p[i] là số nguyên tố thứ i trong dãy các số nguyên tố, s[i] là tổng i số nguyên tố đầu tiên trong dãy các
số nguyên tố,
s[i] = p[1] + p[2] + + p[i]
Thí dụ

p[1] = 2; p[2] = 3; p[3] = 5; p[4] = 7;
s[1] = 2; s[2] = 2 + 3 = 5; s[3] = 2 + 3 + 5 = 10; s[4] = 2 + 3 + 5 + 7 = 17;
Với qui ước s[0] = 0, ta có các nhận xét sau:
1. s là dãy tăng chặt : s[1] < s[2] < …< s[m] < …;
2. s[i] = s[i−1] + p[i]; i = 1, 2,
3. Nếu i < j thì s[j] − s[i] = p[i+1] + p[i+2] + + p[j], tức là s[j] − s[i] là tổng của j−i số nguyên tố liên
tiếp tính từ p[i+1].
Với số n cho trước, nếu ta xác định được 2 chỉ số i < j sao cho n = s[j]−s[i] thì n chính là tổng của j−i số
nguyên tố liên tiếp. Ta ghi nhận một cách phân tích.
Nếu biết dãy s[0 m] và với mỗi i ta tìm được trong dãy s một chỉ số j thỏa điều kiện s[i] + n = s[j] thì ta có
ngay n = s[j] − s[i] chính là một phương án biếu diễn n thành tổng các số nguyên tố liên tiếp. Ta có thuật toán
sau:
6
 Bước 1. Gọi hàm Sieve để tính dãy s gồm m phần tử, mỗi s[i] là tổng các số nguyên tố liên tiếp tính
từ p[1] = 2.
 Bước 2. Với n cho trước, với mỗi i = 0 ta gọi hàm tìm kiếm nhị phân giá trị (s[i]+n) trong dãy
s[i+1 m]. Nếu giá trị này xuất hiện trong dãy thì ta ghi nhận thêm một dạng biểu diễn.
Việc còn lại là cài đặt các hàm trên.
Với hàm Sieve ta sửa lại đôi chút như sau.
Sieve: Thực hiện thuật toán Sàng Eratosthenes(MN) để liệt kê các số nguyên tố trong khoảng 1 MN với MN là
giới hạn của n, MN = 100000. Đồng thời ta tính các tổng s[i]. Hàm Sieve cho ra m là số lượng các số nguyên tố
trong khoảng 1 MN. Để ý rằng s[m] chính là tổng toàn bộ các số nguyên tố trong khoảng 1 MN. Bạn nhớ gán
s[0] = 0 !.
Hàm Num(m,n) đếm số cách biểu diễn n thành tổng các số nguyên tố liên tiếp nhau trong dãy, trong đó m là
số lượng các số nguyên tố trong khoảng 1 MN. Để ý rằng với mỗi i, s[i]+n > s[i] nên nếu có xuất hiện tại phần
tử thứ j trong dãy thỏa điều kiện s[j] = s[i]+n thì ta phải có j > i. Do đó ta chỉ cần tìm nhị phân s[i]+n trong dãy
s[i+1 m], tức là từ vị trí thứ i đến m là đủ.
Chương trình C++
// Psum.cpp
// So cach bieu dien n thanh tong lien tiep

// các so nguyen to lien tiep
#include <iostream>
#include <fstream>
#include <math.h>
#include <string.h>
using namespace std;
const char * fn = "PSUM.INP"; // inout file
const int MN = 100000; // gioi han
int s[MN+1];
// Sang cac so nguyen to
// The Sieve of Eratosthenes
// s[i] = tong i so nguyen to dau tien
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
memset(s,PRIME,sizeof(s));
int can = int(sqrt(n));
int i, j;
for (i = 2; i <= can; ++i)
if (s[i] == PRIME)
for (j = i*i; j <= n; j += i) s[j] = DELETED;
j = 0; s[j] = 0;
for (i = 2; i <= n; ++i)
if (s[i] == PRIME){
s[j+1] = s[j]+i;
7
++j;
}
return j;
}

// tim nhi phan trong mang sap tang s[d c]
// chi so d thoa s[d] = k
int BinSearch(int d, int c, int k) {
int g; // chi so giua d va c
while (d < c) {
g = (d+c) / 2;
if (s[g] < k) d = g+1;
else c = g;
}
return (s[d] == k) ? d : -1;
}
// So cach bieu dien n thanh tong
// cac so nguyen to lien tiep
int Num(int m, int n) {
int d; // so cach bieu dien
int i;
d = 0;
for (i = 0; s[i]+n <= s[m]; ++i)
if (BinSearch(i+1, m, s[i]+n) > i) ++d;
return d;
}
void Run() {
int m, n;
m = Sieve(MN); // m: so luong cac so ng to trong khoang 1 MN
// s[m] = tong cac so nguyen to trong khoang 1 MN
cout << endl << s[m] << endl;
ifstream f(fn); // Mo input file
while (1) {
f >> n; // doc n
if (n == 0) break; // Het file

cout << "\n So cach bieu dien " << n << " : " << Num(m, n);
} // while
f.close();
}
main() {
Run();
cout << "\n Fini !!\n";
8
return 0;
}
Chương trình Pascal
(*
PSUM.PAS : So cach bieu dien n thanh
tong lien tiep cac so nguyen to
*)
const MN = 100000; inf = 'psum.inp'; nl = #13#10;
type Li = longInt;
var
s: array[0 MN] of Li;
{ Sinh cac so nguyen to trong khoang 1 n
Tinh s[i] = tong i so nguyen to dau tien.
Ham cho ra so luong cac so nguyen to thu duoc }
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
var i, can, j: Li;
begin
fillchar(s,sizeof(s),PRIME);
can := round(sqrt(n));
for i := 2 to can do
if s[i] = PRIME then

for j := i to (n div i) do
s[i*j] := DELETED;
j := 0; s[0] := 0;
for i := 2 to n do
if s[i] = PRIME then
begin
s[j+1] := s[j]+i;
inc(j);
end;
Sieve := j;
end;
{ Tim nhi phan vi tri xuat hien gia tri k
trong mang sap tang chat s[d c] }
function BinSearch(d,c,k: Li): Li;
var g: Li; { diem giua d va c }
begin
while (d < c) do
begin
g := (d+c) div 2;
if s[g] < k then d := g+1 else c := g;
9
end;
if s[d] = k then BinSearch := d else BinSearch := -1;
end;
{ So cach viet n thanh tong
cac so nguyen to lien tiep }
function Num(m,n: Li):Li;
var i, d, sn: Li;
begin
d := 0;

for i := 0 to m do
begin
sn := s[i]+n;
if sn > s[m] then break;
if BinSearch(i+1,m,sn) > i then inc(d);
end;
Num := d;
end;
procedure Run;
var f: text;
n, m: Li;
begin
m := Sieve(MN);
assign(f,inf); reset(f);
while(true) do
begin
readln(f,n);
if n = 0 then break;
writeln(' So cach bieu dien ', n, ': ',Num(m,n));
end;
close(f);
end;
BEGIN
Run;
{ }
writeln(nl,' F I N I ! ');
writeln(nl,' Bam phim tuy y de ket thuc: ');
readln;
END.
2.3 Bậc của thừa số

Cho hai số nguyên dương n và v, v > 1. Ta nói số nguyên không âm d là bậc của thừa số v trong n nếu d là
số mũ lớn nhất sao cho v
d
là ước của n. Ta kí hiệu Degree(n,v) = d.
10
Vì 1
d
= 1 với mọi số nguyên d ≥ 0 nên ta qui ước Degree(n,1) = 0.
Thí dụ
n = 5040 = 2
4
⋅3
2
⋅5⋅7, do đó
Bậc của 2 trong 5040 là 4, Degree (5040,2) = 4;
Bậc của 3 trong 5040 là 2, Degree (5040,3) = 2;
Bậc của 4 = 2
2
trong 5040 là 2, Degree (5040,4) = 2;
Bậc của 8 = 2
3
trong 5040 là 1, Degree (5040,8) = 1;
Bậc của 5 trong 5040 là 1, Degree (5040,5) = 1;
Bậc của 6 = 2⋅3 trong 5040 là 2, Degree (5040,6) = 2;
Bậc của 7 trong 5040 là 1, Degree (5040,7) = 1;
Bậc của 23 trong 5040 là 0, Degree (5040,23) = 0;
Thuật toán
Giả sử d = Degree(n,v). Ta có, n chia hết cho v
i
với i = 1 d.

Đếm số lần n chia hết cho v, mỗi lần ta cập nhật lại n = n / v .
// Bac cua thua so v trong n
int Degree(int n, int v){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d, n /= v;
}
return d;
}
Chương trình C++
// Degree.cpp
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
// Bac cua thua so v trong n
int Degree(int n, int v){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;
}

void Test(){
int n = 98000; // n = 2^4.5^3.7^2;
cout << endl << "n = " << n;
cout << endl << " v = 2, d = " << Degree(n,2); // 4

cout << endl << " v = 5, d = " << Degree(n,5); // 3
cout << endl << " v = 7, d = " << Degree(n,7); // 2
11
cout << endl << " v = 10, d = " << Degree(n,10); // 3
cout << endl << " v = 14, d = " << Degree(n,14); // 2
cout << endl << " v = 17, d = " << Degree(n,17); // 0
}

main() {
Test();
cout << "\n\n F I N I ! \n";
return 0;
}
Chương trình Pascal
(*
Degree.cpp
*)
const nl = #13#10;
type Li = longint;
{ Bac cua thua so v trong n }
function Degree(n, v: Li): Li;
var d: Li;
begin
d := 0; Degree := 0;
if (n = 0) OR (v < 2) then exit; { Qui uoc }
while (n mod v = 0) do
begin
inc(d); n := n div v;
end;
Degree := d;

end;
procedure Test;
var n: Li;
begin
n := 98000; { n = 2^4.5^3.7^2 }
writeln(nl, ' n = ', n);
writeln(nl, ' Bac cua 2: ', Degree(n,2)); { 4 }
writeln(nl, ' Bac cua 5: ', Degree(n,5)); { 3 }
writeln(nl, ' Bac cua 7: ', Degree(n,7)); { 2 }
writeln(nl, ' Bac cua 10: ', Degree(n,10)); { 3 }
writeln(nl, ' Bac cua 14: ', Degree(n,14)); { 2 }
writeln(nl, ' Bac cua 17: ', Degree(n,17)); { 0 }
end;
BEGIN
Test;
writeln(nl, ' F I N I ! ', nl);
readln;
END.
2.4 Phân tích ra thừa số nguyên tố
Thuật toán dưới đây phân tích số nguyên dương n giới hạn tối đa 2 triệu thành tích k số nguyên tố p
i
với số mũ
tương ứng m
i
, i = 1 k.
12
Thí dụ
Với input n = 5292, chương trình sẽ hiển thị
2^2
3^3

7^2.
với ý nghĩa: 5292 = 2
2
⋅3
3
⋅7
2

Thuật toán
 Bước 1. Gọi hàm Sieve(MN) để nhận các số nguyên tố trong khoảng 1 MN, với MN là giới hạn tối
đa của n.
 Bước 2. Lần lượt xét các số nguyên tố p[i]: Nếu n chia hết cho p[i] thì gọi hàm CDegree(n, p[i]) để
xác định bậc của p[i] trong n.
Hàm CDegree(n, p) hoạt động giống như hàm Degree(n,p) ngọai trừ một thay đổi nhỏ sau đây. Ngoài việc
xác định bậc của số nguyên tố p trong n, hàm CDegree(n,p) còn cập nhật lại giá trị của n như sau. Giả sử m =
CDegree(n,p) thì sau khi kết thúc hàm ta thu được trị mới của n là n / p
m
. Thí dụ, với n = 168 = 2
3
⋅3⋅7 và p = 2
thì sau lời gọi
m = Degree(168,2) ta sẽ thu được m = 3 và n = 168/2
3
= 3⋅7 = 21
và như vậy giá trị mới của n sẽ được sử dụng cho các lần gọi tiếp theo. Kỹ thuật này mang tên là cuốn chiếu
với ý nghĩa xử lý dữ liệu theo từng phần.
Chương trình C++
// TSNT.CPP
// Phan tich ra thua so nguyen to
#include <iostream>

#include <math.h>
using namespace std;
const int MN = 2000000; // gioi han
int p[MN+1]; // cac so nguyen to
// The Sieve of Eratosthenes
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
int can = int(sqrt(n)); // can bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
j = 0;
for (i = 2; i <= n; ++i) {
if (p[i] == PRIME) p[++j] = i;
}
return j;
}
// Bac cua thua so v trong n
int CDegree(int& n, int v){
13
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;

}
void Decompose(int n){
if (n < 2 || n > MN) return;
int i;
i = 0;
while (n > 1){
++i;
if (n % p[i] == 0)
cout << endl << p[i] << "^" << CDegree(n, p[i]);
}
}

void Test(){
int n = 2*2*3*3*3*7*7; // = 5292;
Sieve(MN); // Nho goi ham nay truoc
cout << endl << " n = " << n;
cout << " = 2*2*3*3*3*7*7";
Decompose(n);
}

main() {
Test();
cout << "\n Fini !!\n"; system("pause");
return 0;
}
Chương trình Pascal
(*
TSNT.PAS: Phan tich ra thua so nguyen to
*)
const MN = 2000000; nl = #13#10; {xuong dong}

type Li = longint;
var p: array[0 MN] of Li;
function Sieve(n: longint): Li;
const PRIME = 0; DELETED = 1;
var i, j, can: Li;
begin
can := round(sqrt(n));
fillchar(p, n*sizeof(Li),PRIME);
p[0] := DELETED; p[1] := DELETED;
for i := 2 to can do
if p[i] = PRIME then
for j := i to (n div i) do p[i*j] := DELETED;
j := 0; { Dua cac so nguyen to ve dau mang p }
for i := 2 to n do
if p[i] = PRIME then
begin
inc(j); p[j] := i;
end;
Sieve := j;
end;
14
{ Bac cua thua so v trong n }
function CDegree(var n: Li; v: Li): Li;
var d: Li;
begin
d := 0; CDegree := d;
if (n = 0) OR (v < 2) then exit;
while (n mod v = 0) do
begin
inc(d); n := n div v;

end;
CDegree := d;
end;
procedure Decompose(n: Li);
var i: Li;
begin
if (n < 2) OR (n > MN) then exit;
i := 0;
while (n > 1) do
begin
inc(i);
if (n mod p[i] = 0) then
writeln(p[i],'^', CDegree(n,p[i]));
end;
end;
procedure Test();
var n: Li;
begin
n := 5292; { n = 2^2*3^3*7^2 }
Sieve(MN); { Nho goi ham nay truoc }
Decompose(n);
end;
BEGIN
Test;
(* *)
write(nl,' F I N I ! ');
write(nl,'Nhan phim bat ki de ket thuc: ');
readln;
END.
Không dùng sàng có được không ?

Sử dụng sàng các số nguyên tố bạn có thể thực hiện cho nhiều test chỉ với một lần gọi hàm Sieve.
Nếu chỉ cần test duy nhất một lần với giá trị n cho trước, bạn có thể không dùng Sieve.
Bạn lần lượt xét các giá trị p = 2, 3, 4, 5, Nếu n chia hết cho số p đầu tiên thì p chính là số nguyên tố. Thật
vậy, giả sử p không phải là số nguyên tố. Khi đó, gọi v là ước nguyên tố nhỏ nhất của p. Ta có: p chia hết cho v
và v < p. Vì n chia hết cho p và p chia hết cho v nên n cũng chia hết cho v. Vì v < p nên p không thể là ước số
đầu tiên của n. Đó là điều mâu thuẫn. Sau khi xác định được ước p đầu tiên của n, bạn gọi hàm CDegree(n,p) để
tính bậc của p trong n. Lúc này n đã được nhận giá trị mới là n / p
k
, k là bậc của p trong n. Bạn tiếp tục xét với
các p+1, đến khi n = 1 bạn dừng thuật toán.
Chương trình C++
// TSNT.CPP
// Phan tich ra thua so nguyen to
15
#include <iostream>
#include <string.h>
using namespace std;
// Bac cua thua so v trong n
int CDegree(int& n, int v){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;
}
void Decom(int n){
int p = 2;
int d;
while (n > 1){

d = CDegree(n, p);
if (d > 0) {
cout <<"\n " << p << "^" << d;
}
++p;
}
}

void Test(){
int n = 2*2*3*3*3*7*7; // = 5292;
cout << endl << " n = " << n;
cout << " = 2*2*3*3*3*7*7";
Decom(n);
}

main() {
Test();
cout << "\n Fini !!\n";
return 0;
}
Chương trình Pascal
(*
TSNT.PAS: Phan tich ra thua so nguyen to
*)
type Li = longint;
procedure Decompose(n: Li);
var p, m: Li;
begin
if (n < 2) then exit;
p := 2; m := 0;

while (n mod p = 0) do
begin
inc(m); n := n div p;
end;
if (m > 0) then writeln(p, '^', m);
p := 1;
while (n > 1) do
begin
p := p + 2; m := 0;
while (n mod p = 0) do
16
begin
inc(m); n := n div p;
end;
if (m > 0) then writeln(p, '^', m);
end;
end;
BEGIN
Decompose(2*2*2*3*3*7*7);
writeln(' F I N I ! ');
readln;
END.

Cải tiến
Bạn có để ý hàm Decom nói trên xử lí một chi tiết khá ngây thơ. Đó chính là giới hạn của vòng lặp chứa p.
Hãy tưởng tượng ta gọi hàm Decom(n) với n = 199967. Đây là số nguyên tố, do đó trong hàm trên p sẽ được
duyệt từ 2 đên 199967, tức là vòng lặp phải kiểm tra xấp xỉ 199967 lần. Như vậy là quá lãng phí.
Ta đã biết n là số nguyên tố khi và chỉ khi n không có ước nào trong khoảng 2
n
. Vậy là chỉ cần duyệt p

trong giới hạn trên.
Chú ý rằng khi kết thúc, nếu n > 1 tức là n là số nguyên tố đồng thời là ước nguyên tố cuối cùng của chính nó,
ta phải ghi ước đó ra.
Ngoài ra ta để ý rằng 2 là số nguyên tố chẵn duy nhất do đó, sau khi xét n với 2 ta chỉ cần xét các số p lẻ.
Chương trình C++
// TSNT.CPP
// Phan tich ra thua so nguyen to
#include <iostream>
#include <math.h>
using namespace std;
// Bac cua thua so v trong n
int CDegree(int& n, int v){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;
}
void Decom(int n){
if (n < 2) return;
int can = int(sqrt(n)); // can nguyen bac 2 cua n
int p, m;
p = 2; m = CDegree(n,p);
if (m > 0) cout << endl << p << "^" << m;
for (p = 3; p <= can && n > 1; p += 2) {
m = CDegree(n,p);
if (m > 0) cout << endl << p << "^" << m;
}
if (n > 1) cout << endl << n;

}

main() {
int n = 2*2*3*3*3*7*7*199967;
cout << endl << " n = " << n;
17
Decom(n);
cout << "\n Fini !!\n";
return 0;
}
Chương trình Pascal
(*
TSNT.PAS: Phan tich ra thua so nguyen to
*)
const nl = #13#10; {xuong dong}
type Li = longint;
{ Bac cua thua so v trong n }
function CDegree(var n: Li; v: Li): Li;
var d: Li;
begin
CDegree := 0; d := 0;
if (n = 0) OR (v < 2) then exit; { Qui uoc }
while (n mod v = 0) do
begin
inc(d); n := n div v;
end;
CDegree := d;
end;
procedure Decom(n: Li);
var d, p, can: Li;

begin
p := 2;
d := CDegree(n,p);
if (d > 0) then writeln(p, '^', d);
p := 1; can := round(sqrt(n));
while (p <= can) and (n > 1) do
begin
p := p + 2;
d := CDegree(n,p);
if (d > 0) then writeln(p,'^', d);
end;
if (n > 1) then writeln(n);
end;
BEGIN
Decom(2*2*3*3*3*7*7*199967);
write(nl,' F I N I ! ');
readln;
END.
2.5 Bậc của thừa số nguyên tố trong giai thừa
Cho số nguyên dương n và số nguyên tố p. Ta cần tính bậc của p trong giai thừa n!. Ta kí hiệu
FPDegree(n,p) = d.
Thí dụ
7! = 1⋅2⋅3⋅4⋅5⋅6⋅7 = 1⋅2⋅3⋅2
2
⋅5⋅(2⋅3) ⋅7 = 2
4
⋅3
2
⋅5⋅7 = 5040, do đó
Bậc của 2 trong 7! là 4, FPDegree(7, 2) = 4;

Bậc của 3 trong 7! là 2, FPDegree(7, 3) = 2;
18
Bậc của 5 trong 7! là 1, FPDegree(7, 5) = 1;
Bậc của 7 trong 7! là 1, FPDegree(7, 7) = 1;
Bậc của 17 trong 7! là 0, FPDegree(7, 17) = 0.
Thuật toán
Chia liên tiếp n cho p gặp 0 thì dừng, d = tổng của các thương.
Thí dụ, Tính bậc của 2 trong 7!
7 chia cho 2 được thương 3
3 chia cho 2 được thương 1
1 chia cho 2 được thương 0
Vậy d = FPDegree(7, 2) = 3 + 1 = 4
// Bac cua thua so nguyen to p trong n!
int FPDegree(int n, int p) {
int d = 0;
while (n != 0) {
n = n/p;
d += n;
}
return d;
}
Gợi ý chứng minh thuật toán
Ta viết lần lượi các số 1, 2, …, n vào một bảng theo từng dòng, mỗi dòng p số. Ta thu được k dòng trọn vẹn
là những dòng chứa đủ p số, trong đó k = n/p. Để ý đến cột cuối cùng của bảng ta thấy các giá trị trong cột này
lần lượt là 1p, 2p, …, kp. Vậy trong tích n! = 1⋅2⋅3⋅…⋅n chứa k thừa số nguyên tố p, cụ thể là n! = b⋅(1p⋅2p⋅…
⋅kp) = b⋅(1⋅2⋅ ⋅k)p
k
= b⋅p
k
⋅(k!), với b là một nhân tử nào đó. Lại tiếp tục phân tích k theo p như đã làm với n và

p ta thu được thêm k/p thừa số p nữa
1 2 … p
p+1 … … 2p
2p+1 … … 3p
… …
… … kp

Chương trình C++
// FPDegree.cpp
#include <iostream>
using namespace std;
// Bac cua thua so nguyen to p trong n!
int FPDegree(int n, int p) {
int d = 0;
while (n != 0) {
n /= p;
19
d += n;
}
return d;
}
void Test(){
int n = 7; // 7! = 1*2*3*4*5*6*7 = 2^4*3^2*5*7 = 5040
cout << endl << " n = " << n;
cout << endl << " 7! = 1*2*3*4*5*6*7 = 2^4 * 3^2 * 5 * 7";
cout << endl << " Bac cua 2: " << FPDegree(n, 2);// 4
cout << endl << " Bac cua 3: " << FPDegree(n, 3);// 2
cout << endl << " Bac cua 5: " << FPDegree(n, 5);// 1
cout << endl << " Bac cua 7: " << FPDegree(n, 7);// 1
cout << endl << " Bac cua 17: " << FPDegree(n, 17);// 0

}
main() {
Test();
cout << endl << "\n\n F I N I ! \n";

return 0;
}
Chương trình Pascal
(*
FPDeg.Pas
*)
const nl = #13#10;
type Li = longint;
{ Bac cua thua so nguyen to p trong n! }
function FPDegree(n, p: Li): Li;
var d: Li;
begin
d := 0;
while (n <> 0) do
begin
n := n div p;
d := d+n;
end;
FPDegree := d;
end;
procedure Test;
var n: Li;
begin
n := 7;
writeln(nl,' n = ', n);

writeln(' 7! = 1*2*3*4*5*6*7 = 2^4*3^2*5*7 = 5040 ');
write(nl,' Bac cua 2: ', FPDegree(n, 2)); { 4 }
write(nl,' Bac cua 3: ', FPDegree(n, 3)); { 2 }
write(nl,' Bac cua 5: ', FPDegree(n, 5)); { 1 }
write(nl,' Bac cua 7: ', FPDegree(n, 7)); { 1 }
write(nl,' Bac cua 17: ', FPDegree(n, 17)); { 0 }
end;
BEGIN
Test;
write(nl,nl,' F I N I ! ');
20
readln;
END.
2.6 Các số 0 tận cùng
Cho số nguyên dương n. Hãy viết hàm LastZero(n) tính số lượng các số 0 tận cùng của n!.
Thí dụ
LastZero(7) = 1, vì 7! = 5040,
LastZero(3) = 0, vì 3! = 6,
LastZero(15) = 3, vì 15! = 1307674368000.
Thuật toán
Để ý rằng LastZero(n) = số thừa số 10 trong n! = số thừa số 2⋅5 trong n!. Vậy ta có LastZero(n) = min
{FPDegree(n,2), FPDegree(n,5)} = FPDegree(n,5) vì 2 xuất hiện nhiều lần hơn 5 trong n! (bạn tự giải thích điều
này).
Chương trình C++
// LastZero.cpp: so so 0 tan cung cua n!
#include <iostream>
using namespace std;
// Bac cua thua so nguyen to p trong n!
int FPDegree(int n, int p) {
int d = 0;

while (n != 0) {
n /= p;
d += n;
}
return d;
}
// So so 0 tan cung cua n!
int LastZero(int n){
return FPDegree(n,5);
}

void Test(){
int n;
for(n = 1; n <= 20; ++n)
cout << "\n LastZero cua " << n << "! = " << LastZero(n);
}
main() {
Test();
cout << endl << "\n\n F I N I ! \n";

return 0;
}
Chương trình Pascal
21
(*
LastZero.Pas: so so 0 tan cung cua n!
*)
const nl = #13#10;
type Li = longint;
{ Bac cua thua so nguyen to p trong n! }

function FPDegree(n, p: Li): Li;
var d: Li;
begin
d := 0;
while (n <> 0) do
begin
n := n div p;
d := d+n;
end;
FPDegree := d;
end;
function LastZero(n: Li): Li;
begin
LastZero := FPDegree(n,5);
end;
procedure Test;
var n: Li;
begin
for n := 1 to 20 do
writeln(' LastZero cua ', n,'! = ',LastZero(n));
end;
BEGIN
Test;
write(nl,nl,' F I N I ! ');
readln;
END.
2.7 Bậc của thừa số p
k
trong giai thừa
Ta đã biết, nếu p là số nguyên tố thì p có bậc trong n! là m = FPDegree(n,p). Ta cần viết hàm

FPkDegree(n,p,k) tính bậc của p
k
trong n!.
Thí dụ
7! = 1⋅2⋅3⋅4⋅5⋅6⋅7 = 1⋅2⋅3⋅2
2
⋅5⋅(2⋅3) ⋅7 = 2
4
⋅3
2
⋅5⋅7 = 5040, do đó
Bậc của 4 = 2
2
trong 7! là 2, FPkDegree(7, 2, 2) = 2, vì 7! = (2
2
)
2
⋅3
2
⋅5⋅7;
Bậc của 8 = 2
3
trong 7! là 1, FPkDegree(7, 2, 3) = 1, vì 7! = 2
3
⋅2⋅3
2
⋅5⋅7;
Bậc của 25 = 5
2
trong 7! là 0, FPkDegree(7, 5, 2) = 0, vì trong tích 7! không chứa 5

2
.
Thuật toán
Giả sử số nguyên tố p có bậc trong n! là m, tức là m = FPDegree(n,p). Nhóm m thừa số này thành các bộ k
thừa số p ta có:
FPkDegree(n, p, k) = FPDegree(n,p) / k
// Bac cua thua so nguyen to p trong n!
22
int FPkDegree(int n, int p, int k) {
return (k <= 0) ? 0 : FPDegree(n,p,k);
}
Chương trình C++
// FPkDegree.cpp
// Bac cua p^k trong n!, p nguyen to
#include <iostream>
using namespace std;
// Bac cua thua so nguyen to p trong n!
int FPDegree(int n, int p) {
int d = 0;
while (n != 0) {
n /= p;
d += n;
}
return d;
}
// Bac cua thua so p^k trong n!, p nguyen to
int FPkDegree(int n, int p, int k) {
return (k <= 0) ? 0 : FPDegree(n, p) / k;
}
void Test(){

int n = 7; // 7! = 1*2*3*4*5*6*7 = 2^4*3^2*5*7 = 5040
cout << endl << " n = " << n;
cout << endl << " 7! = 1*2*3*4*5*6*7 = 2^4*3^2*5*7.";
cout << endl << " Bac cua 2^2: " << FPkDegree(n,2,2);// 2
cout << endl << " Bac cua 2^3: " << FPkDegree(n,2,3);// 1
cout << endl << " Bac cua 2^4: " << FPkDegree(n,2,4);// 1
cout << endl << " Bac cua 2^5: " << FPkDegree(n,2,5);// 0
cout << endl << " Bac cua 3^1: " << FPkDegree(n,3,1);// 2
cout << endl << " Bac cua 3^2: " << FPkDegree(n,3,2);// 1
}
main() {
Test();
cout << endl << "\n\n F I N I ! \n";
cin.get();
return 0;
}
Chương trình Pascal
(*
FPDeg.Pas: Bac cua p^k trong n!, p ng to
*)
const nl = #13#10;
type Li = longint;
{ Bac cua thua so nguyen to p trong n! }
function FPDegree(n, p: Li): Li;
var d: Li;
begin
d := 0;
while (n <> 0) do
begin
23

n := n div p;
d := d+n;
end;
FPDegree := d;
end;
{ Bac cua p^k trong n!, p nguyen to }
function FPkDegree(n, p, k: Li): Li;
begin
if (k <= 0) then FPkDegree := 0
else FPkDegree := FPDegree(n, p) div k;
end;
procedure Test;
var n: Li;
begin
n := 7;
writeln(nl,' n = ', n);
writeln('7! = 1*2*3*4*5*6*7 = 2^4*3^2*5*7 = 5040 ');
write(nl,' Bac cua 2^2: ', FPkDegree(n,2,2)); { 2 }
write(nl,' Bac cua 2^3: ', FPkDegree(n,2,3)); { 1 }
write(nl,' Bac cua 2^4: ', FPkDegree(n,2,4)); { 1 }
write(nl,' Bac cua 2^5: ', FPkDegree(n,2,5)); { 0 }
write(nl,' Bac cua 3^1: ', FPkDegree(n,3,1)); { 2 }
write(nl,' Bac cua 3^2: ', FPkDegree(n,3,2)); { 1 }
end;
BEGIN
Test;
write(nl,nl,' F I N I ! ');
readln;
END.
2.8 Bậc của thừa số u trong giai thừa

Cho số nguyên dương n và số nguyên u > 1. Ta nói số nguyên không âm d là bậc của thừa số u > 1 trong
giai thừa của n nếu d là số mũ lớn nhất sao cho u
d
là ước của n!. Ta kí hiệu FDegree(n,u) = d.
Chú ý rằng ta không đòi hỏi u là số nguyên tố.
Thí dụ
7! = 1⋅2⋅3⋅4⋅5⋅6⋅7 = 1⋅2⋅3⋅2
2
⋅5⋅(2⋅3) ⋅7 = 2
4
⋅3
2
⋅5⋅7 = 5040, do đó
Bậc của 6 trong 7! là 2, FDegree(7, 6) = 2, vì 7! = (2⋅3)
2
⋅2
2
⋅5⋅7;
Bậc của 15 trong 7! là 1, FDegree(7, 15) = 1, vì 7! = 2
4
⋅3⋅(3⋅5)⋅7;
Bậc của 56 trong 7! là 1, FDegree(7, 5) = 1, vì 7! = 2⋅(8⋅7)⋅3
2
⋅5.
Thuật toán
Gỉa sử u được phân tích ra thừa số nguyên tố,
Ta có, theo bài trước,
24
u = p
1

k
1
p
2
k
2
…p
v
k
v
(2.1)
m
1
= FPkDegree(n, p
1
, k
1
);
m
2
= FPkDegree(n, p
2
, k
2
);

m
v
= FPkDegree(n, p
v

, k
v
);
Vậy bậc của u trong n! là
Ta có thuật toán sau:
 Phân tích u ra thừa số nguyên tố như trong công thức (2.1) và đồng thời tính min theo công thức (2.2).
Chương trình C++
// FDegree.cpp
#include <iostream>
#include <math.h>
using namespace std;

int Min(int a, int b){
return (a < b) ? a : b;
}
// Bac cua thua so v trong n
int CDegree(int& n, int v){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;
}
// Bac cua thua so nguyen to p trong n!
int FPDegree(int n, int p) {
int d = 0;
while (n != 0) {
n /= p;
d += n;

}
return d;
}
// Bac cua thua so p^k trong n!, p nguyen to
int FPkDegree(int n, int p, int k) {
return (k <= 0) ? 0 : FPDegree(n, p) / k;
}
// Bac cua u trong n!
int FDegree(int n, int u){
int p;
int k, kmin = n;
int canu = int(sqrt(u));
if (n < 2 || u <= 1) return 0;
p = 2;
k = CDegree(u, p); // u = 2^d
if (k > 0) // 2 la uoc cua u
kmin = Min(kmin, FPkDegree(n,p,k));
25
FDegree(n,u) = min{ m
i
| i = 1 v } (2.2)

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×