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

Các bài toán giải thuật nâng cao

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 (603.62 KB, 104 trang )

MỤC LỤC
3 Các bài toán nâng cao...........................................................................................................................................2
3.1 Lũy thừa 2, 3, 5..............................................................................................................................................2
3.2 Số hoàn thiện..................................................................................................................................................6
3.3 Phân tích số lớn............................................................................................................................................12
3.4 Bâc cao.........................................................................................................................................................16
3.5 Lũy thừa.......................................................................................................................................................20
3.6 Ba lô.............................................................................................................................................................27
3.7 Balô đơn giản...............................................................................................................................................37
3.8 Hình Vuông và Tam Giác............................................................................................................................40
3.9 Chiều dài của giai thừa................................................................................................................................43
3.10 Số ước chẵn lẻ............................................................................................................................................48
3.11 Operators (Toán tử)....................................................................................................................................49
3.12 Người thắng cử..........................................................................................................................................58
3.13 Cặp điểm gần nhất.....................................................................................................................................60
3.14 Mọi đường ngắn nhất.................................................................................................................................67
3.15 Đường đi và chu trình Euler......................................................................................................................71
3.16 Du hành......................................................................................................................................................86

1


3
Các bài toán nâng cao

3.1 Lũy thừa 2, 3, 5
Dijkstra E.
Xét dãy số s được sắp tăng gồm 1000 số đầu tiên là tích các lũy thừa của 2, 3 và 5, tức là các số có dạng
2a3b5c, a, b, c = 0, 1, 2, …
s = 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, …
Với mỗi giá trị nguyên dương k cho trước, hãy cho biết dãy s có bao nhiêu số có trên k ước.



Chú thích
Bài nguyên gốc do Dijkstra đề xuất chỉ đòi hỏi hiển thị dãy s. Chúng ta sửa lại nội dung đôi chút nhằm minh
họa các thao tác xử lý ước.

Thí dụ
Với n = 1000; k = 250 ta có 9 số có trên 250 ước số.
Số

Dạng lũy thừa

Số ước

23328000

283653

252

31104000

2103553

264

34992000

273753

256


37324800

2113652

252

38880000

283554

270

41472000

2123453

260

46656000

293653

280

48600000

263555

252


49766400

2133552

252

2


Thuật toán
Theo đầu bài, ta hãy tạm giả thiết là dãy s gồm vô hạn các số là tích các lũy thừa 2, 3 và 5, do đó s có các
tính chất sau:
 Dãy s được sắp tăng;
 Dãy s chứa 1 là số nhỏ nhất, ứng với 203050;
 Nếu s chứa số v = 2m3n5p thì s phải chứa 3 số 2v = 2m+13n5p, 3v = 2m3n+15p và 5v = 2m3n5p+1.
Do yêu cầu sắp tăng nên s sẽ có dạng sau đây:
1, ..., v, ... , 2v, ..., 3v, ..., 5v, ...
Chú ý rằng 2v < 3v nhưng không nhất thiết 2v phải đứng sát sau v. Giả sử ta có y = 2 m’3n’5p’ thuộc s và 2y =
m’+1 n’ p’
2 3 5 > v thì khi đó dãy s phải có dạng sau đây:
1,...,y,..., v, ..., 2y,...
Ta cũng có thể có z thuộc s và 3z > v, hoặc t thuộc s và 5t > v.
Tổng hợp lại ta thấy, nếu ta đã sinh được dãy s 1 = 1, s2 = 2,...,si−1 = v thì để tính phần tử si ta phải xác định
được ba số s[a], s[b], s[c] trong dãy s[1..i−1] sao cho:
 2s[a] là giá trị đầu tiên vượt quá v, tức là 2s[a] > v;
 3s[b] là giá trị đầu tiên vượt quá v, tức là 3s[b] > v;
 và 5s[c] là giá trị đầu tiên vượt quá v, tức là 5s[c] > v;
Khi đó, s[i] = max{2s[a], 3s[b], 5s[c]}
Một khi đã xác định được phần tử v = 2 m3n5p trong dãy s thì ta tính được ngay số ước của v là (m+1)(n+1)

(p+1).
Như vậy ta cần ba biến a, b và c để theo dõi các chỉ số trong dãy s ứng với các thừa số nguyên tố 2, 3 và 5
trong dãy. Ngoài mảng s, ta cần thêm 3 mảng m2, m3 và m5 để chứa các số mũ của mỗi phần tử v trong dãy s.
Chú ý rằng hàm Min3(x,y,z) không cho ta giá trị nhỏ nhất trong 3 số nguyên x, y và z mà cho ta vị trí chứa
min là 1, 2 hoặc 3 để hiểu rằng tham biến thứ mấy trong 3 tham biến x, y, z đạt trị min. Thí dụ, Min3(15, 12,
25) = 2; Min3(12,25,15) = 1; Min3(25,15,12) = 3.

Chương trình C++
// P235.CPP
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
const int MN = 1002;
int s[MN],m2[MN],m3[MN],m5[MN];
int Min3(int x, int y, int z){
int m = 1;
if (x > y) {
x = y; m = 2;
} // x = min (x,y)
return (x <= z) ? m : 3;
}
// Cac so dang luy thua cua 2, 3, 5
// co so uoc > k
int Pow235(int k) {
int i, v;

3



int a, b, c; // cac chi so cho 2, 3, 5
int d = 0; // dem cac so co so uoc > k
// Khoi tri
a = b = c = 1;
v = s[1] = 1; m2[a] = m3[b] = m5[c] = 0;
// Xu li day s[2..1000]
for (i = 2; i <= 1000; ++i) {
// Tinh s[i]
while(2*s[a] <= v) ++a;
while(3*s[b] <= v) ++b;
while(5*s[c] <= v) ++c;
switch(Min3(2*s[a],3*s[b],5*s[c])){
case 1: // 2*s[a] dat min
v = 2*s[a];
m2[i] = m2[a]+1;
m3[i] = m3[a];
m5[i] = m5[a];
break;
case 2: // 3*s[b] dat min
v = 3*s[b];
m2[i] = m2[b];
m3[i] = m3[b]+1;
m5[i] = m5[b];
break;
case 3: // 5*s[c] dat min
v = 5*s[c];
m2[i] = m2[c];
m3[i] = m3[c];
m5[i] = m5[c]+1;
break;

} // switch
s[i] = v;
// soUoc = (m2[i]+1)*(m3[i]+1)*(m5[i]+1);
if ((m2[i]+1)*(m3[i]+1)*(m5[i]+1) > k) ++d;
}
return d;
}
main() {
cout << Pow235(250);
cout << endl << "\n\n F I N I ! \n";
}

return 0;

Chương trình Pascal
(* -----------------------------------P235.PAS cac so dang luy thua 2,3,5
co so uoc > k
-------------------------------------*)
const MN = 1002;
type int = longInt;
TA = array[0..MN] of int;
var s, m2, m3, m5: TA;
function Min3(x, y, z :int): int;
var m: int;
begin

4


m := 1;

if (x > y) then
begin
x := y; m := 2;
end;
if (x <= z) then Min3 := m else Min3 := 3;
end;
{ Cac so dang luy thua cua 2, 3, 5
co so uoc > k }
function Pow235(k: int): int;
var i, v: int;
a, b, c: int; { cac chi so cho 2, 3, 5 }
d: int; { dem cac so co so uoc > k }
begin { Khoi tri }
d := 0; a := 1; b := 1; c := 1;
v := 1; s[1] := 1;
m2[a] := 0; m3[b] := 0; m5[c] := 0;
{ Xu li day s[2..1000] }
for i := 2 to 1000 do
begin { Tinh s[i] }
while(2*s[a] <= v) do inc(a);
while(3*s[b] <= v) do inc(b);
while(5*s[c] <= v) do inc(c);
case(Min3(2*s[a],3*s[b],5*s[c])) of
1: { 2*s[a] dat min }
begin
v := 2*s[a];
m2[i] := m2[a]+1;
m3[i] := m3[a];
m5[i] := m5[a];
end;

2: { 3*s[b] dat min }
begin
v := 3*s[b];
m2[i] := m2[b];
m3[i] := m3[b]+1;
m5[i] := m5[b];
end;
3: { 5*s[c] dat min }
begin
v := 5*s[c];
m2[i] := m2[c];
m3[i] := m3[c];
m5[i] := m5[c]+1;
end;
end { case };
s[i] := v;
{ soUoc = (m2[i]+1)*(m3[i]+1)*(m5[i]+1) }
if ((m2[i]+1)*(m3[i]+1)*(m5[i]+1) > k) then inc(d);
end { for };
Pow235 := d;
end;
BEGIN
writeln(Pow235(250));
writeln(' F I N I ! ');
readln;
END.

5



3.2 Số hoàn thiện
Số nguyên dương n được gọi là số hoàn thiện nếu tổng các ước của n gấp đôi n:
T(n) = 2n
Thí dụ, 6 là số hoàn thiện, vì 6 có các ước 1, 2, 3 và 6, và 1+2+3+6 = 2⋅ 6 = 12.
Đây cũng là số hoàn thiện nhỏ nhất.
Hãy tìm các số hoàn thiện trong 1 triệu số nguyên dương đầu tiên.

Thuật toán
Với mỗi số n trong khoảng từ 1..1000000 ta kiểm tra hệ thức
2n = TongUoc(n)
Để tính tổng ước của một số nguyên dương theo phương pháp gián tiếp, tức là dựa vào dãy số nguyên tố p
do sàng Eratosthenes tạo ra ta phải cải tiến hàm Sieve(n) đôi chút. Cụ thể là sau khi sinh các số nguyên tố từ
trong khoảng 1..n ta sinh thêm một số nguyên tố q nữa đặt ở cuối dãy, dĩ nhiên q > n. Số q này được gọi là lính
canh. Q có nhiệm vụ báo cho bạn biết giới hạn phải dừng khi bạn tổ chức các vòng lặp duyệt các số nguyên tố
trong dãy p đại loại như
while (p[i] <= n) …
Để kiểm tra tính nguyên tố của số n ta dựa vào nhận xét sau.

Nhận xét
Số nguyên n > 1 là nguyên tố khi và chỉ khi n không có ước nguyên tố trong khoảng 1..

n.

Thật vậy, nếu n nguyên tố thì n không có ước khác 1 nào nhỏ thua nó và do đó n không có ước nguyên tố
nào trong khoảng 1.. n .
Đảo lại, nếu n = a⋅b, a,b > 1 thì chí ít một trong hai số a hoặc b phải thuộc khoảng 1.. n . Giả sử số đó là a.
Ta có a ≤ n . Nếu a nguyên tố thì n có ước nguyên tố a trong khoảng 1.. n . Nếu a không phải là số nguyên
tố thì a có một ước nguyên tố p nào đó. Ta có ngay p < a ≤ n .
Giả sử ta đã có dãy số nguyên tố do hàm Sieve sinh ra là p[1] = 2; p[2] = 3; p[3] = 5,… và giả sử ta đã biết
số nguyên n > 1 không có ước nguyên tố nào từ p[1] đến p[d−1]. Dựa theo nhận xét trên để kiểm tra tính

nguyên tố của n ta chỉ còn phải xét tiếp các số nguyên tố từ p[d] đến n . Ta viết hàm Prime(n, d) kiểm tra tính
nguyên tố của n với giả thiết là n không có ước nguyên tố nào nhỏ thua p[d] như sau:
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}

Trong các phần tiếp theo bạn đọc sẽ thấy tính linh hoạt khi sử dụng hàm Prime(n, d) trong tính toán.
Tiếp theo ta cải tiến hàm TongUoc1(n) tính tổng các ước của n theo tiếp cận gián tiếp dựa trên dãy số
nguyên tố p như sau:
Lần lượt duyệt các số nguyên tố trong dãy p cho đến n , nếu gặp số nguyên tố pi là ước của n thì tính tổng
của thừa số pim theo hàm Tong(n, pi) trong đó m là bậc của p[i] trong n. Nhắc lại rằng hàm này đồng thời chia
liên tiếp n cho pi và do dó làm thay đổi trị của n, cụ thể là sau khi gọi hàm Tong(n, p i) ta sẽ thu được trị mới của
6


n là n’ = n/ pim. Nếu n’ = 1 thì ta kết thúc hàm. Nếu n’ là nguyên tố thì trong n, n’ có bậc 1. Vậy khi n’ nguyên
tố thì tổng các ước của n’ sẽ là n’+1.
Ta cũng nhận thấy rằng do n được chia liên tiếp cho các ước nguyên tố không vượt quá p i nên n’ không còn
ước nào trong khoảng từ p1 đến pi. Nhận xét này giúp ta thu gọn thủ tục kiểm tra tính nguyên tố của n’ như sau:
ta chỉ cần xét tiếp xem n’ có ước nguyên tố nào trong khoảng từ pi+1 đến n' là đủ.
// Tong cac uoc cua n: gian tiep qua Sieve
int TongUoc1(int n){
if (n == 1) return 1;
int can = int(sqrt(n));
int i, t = 1;
for (i = 1; n > 1 && p[i] <= can ; ++i){

if (n % p[i] == 0) t *= Tong(n,p[i]);
} //for
return (n > 1) ? t*(n+1) ; t;
}

Chương trình dưới đây minh họa hai tiếp cận tính tổng: gián tiếp (phương án 1) và trực tiếp (phương án 2).
Chương trình C++ gọi hàm tính thời gian để bạn đọc tiện đối sánh.
Đáp án: Trong một triệu số nguyên dương đầu tiên chỉ có 4 số hoàn thiện là
6, 28, 496 và 8128

Chương trình C++
// Perfect.cpp
// cac so hoan thien trong khoang 1..1000000
#include <iostream>
#include <math.h>
#include <time.h>
#include <string.h>
using namespace std;
const int MN = 1000;
const int Range = 1000000;
int p[MN+2]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;

}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
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)

7


}

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;
// Them 1 so nguyen to cuoi day
// lam linh canh
i = p[j]+2;
while(!Prime(i,2)) i += 2;
p[++j] = i;
return j;


// Tong = 1 + q + q^2 +...+q^m
// = ...q(q(q+1)+1)+1 ...
// n thay doi sau khi goi ham
int Tong(int& n, int q){
int t = 1;
while (n % q == 0){
t = t*q+1;
n /= q;
}
return t;
}
// Tong cac uoc cua n: gian tiep qua Sieve
int TongUoc1(int n){
if (n == 1) return 1;
int can = int(sqrt(n));
int i, t = 1;
for (i = 1; n > 1 && p[i] <= can; ++i){
if (n % p[i] == 0) t *= Tong(n,p[i]);
} //for
return (n > 1) ? t*(n+1) : t;
}
// Tong cac uoc cua n: truc tiep
int TongUoc2(int n){
int t = 0, u, can = int(sqrt(n));
for (u = 1; u <= can; ++u)
if (n % u == 0)
t += u + n/u;
return (can*can == n) ? t - can : t;
}
// Cac so hoan thien trong khoang 1..1000000

// Gian tiep qua Sieve
int Perfect1(){
int i, d = 0;
for (i = 6; i <= Range; ++i)
if (TongUoc1(i) == 2*i){
cout << " " << i;
++d;
}
cout << "\n Tong cong: " << d << " so";
return d;
}
// Cac so hoan thien trong khoang 1..1000000
// Truc tiep
int Perfect2(){

8


int i, d = 0;
for (i = 6; i <= Range; ++i)
if (TongUoc2(i) == 2*i){
cout << " " << i;
++d;
}
cout << "\n Tong cong: " << d << " so";
return d;
}
// Doi sanh hai phuong phap gian tiep va truc tiep
void Run() {
time_t t1,t2;

cout << "\n Cac so hoan thien < " << Range+1;
t1 = time(NULL);
cout << "\n Gian tiep: ";
Sieve(MN);
Perfect1();
cout << "\n xong gian tiep";
t2 = time(NULL);
cout << "\n Thoi gian tinh: "<< difftime(t2,t1);
// ---------------------------------------------t1 = time(NULL);
cout << "\n Truc tiep: ";
Perfect2();
t2 = time(NULL);
cout << "\n Thoi gian tinh: "<< difftime(t2,t1);
}
main() {
Run();
cout << endl << "\n\n F I N I ! \n";
}

return 0;

Chương trình Pascal
(* ------------------------------------------Perfect.cpp: Cac so hoan thien
trong khoang 1..1000000
------------------------------------------*)
const MN = 1000;
const Range = 1000000;
type Li = longInt;
TA = array[0..MN+2] of Li;
var p: TA; { Day so nguyen to do Sieve sinh }

Time : Li absolute $0000046c;
(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
begin
IsPrime := false; can := round(sqrt(n));
i := d;
while (p[i] <= can) do
begin
if (n mod p[i] = 0) then exit;
inc(i);
end;
IsPrime := true;

9


end;
(* The Sieve of Eratosthenes
Them 1 so nguyen to > n vao cuoi day
lam linh canh. *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
var i, j, can: Li;
begin
fillchar(p,sizeof(p), PRIME);
can := round(sqrt(n));
for i := 2 to can do
if (p[i] = PRIME) then

for j := i to (n div i) do
p[j*i] := DELETED;
{ Dua cac so nguyen to ve dau mang p }
j := 0;
for i := 2 to n do
if (p[i] = PRIME) then
begin
inc(j); p[j] := i;
end;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
i := p[j]+2;
while not IsPrime(i,2) do i := i + 2;
inc(j); p[j] := i;
Sieve := j;
end;
(* Tong cac uoc cua q^m, q la nguyen to cua n
= 1 + q + q^2 +...+q^m
= ...q(q(q+1)+1)+1 ...
n thay doi sau khi goi ham *)
function Tong(var n: Li; q: Li): Li;
var t: Li;
begin
t := 1;
while (n mod q = 0) do
begin
t := t*q+1;
n := n div q;
end;
Tong := t;

end;
(* Tong cac uoc cua n: gian tiep qua Sieve *)
function TongUoc1(n: Li): Li;
var i, t, can: Li;
begin
TongUoc1 := 1; t := 1;
if (n = 1) then exit;
can := round(sqrt(n));
i := 1;
while (n > 1) and (p[i] <= can) do
begin
if (n mod p[i] = 0) then t := t*Tong(n,p[i]);
inc(i);
end;
if (n > 1) then TongUoc1 := t*(n+1)
else TongUoc1 := t;

10


end;
(* Tong cac uoc cua n: truc tiep *)
function TongUoc2(n: Li): Li;
var t, u, can: Li;
begin
t := 0; can := round(sqrt(n));
for u := 1 to can do
if (n mod u = 0) then t := t+u+(n div u);
if (can*can = n) then TongUoc2 := t-can
else TongUoc2 := t;

end;
(* Cac so hoan thien trong khoang 1..1000000
Gian tiep qua Sieve *)
function Perfect1: Li;
var i, d: Li;
begin
d := 0;
for i := 6 to Range do
if (TongUoc1(i) = 2*i) then
begin
write(' ', i);
inc(d);
end;
writeln; writeln(' Tong cong: ', d, ' so');
Perfect1 := d;
end;
(* Cac so hoan thien trong khoang 1..1000000
Truc tiep *)
function Perfect2: Li;
var i, d: Li;
begin
d := 0;
for i := 6 to Range do
if (TongUoc2(i) = 2*i) then
begin
write(' ', i);
inc(d);
end;
writeln; writeln(' Tong cong: ', d, ' so');
Perfect2 := d;

end;
(* Doi sanh hai phuong phap gian tiep va truc tiep *)
procedure Run;
begin
writeln(' Gian tiep: ');
Sieve(MN);
Perfect1;
writeln(' Xong Gian tiep. ');
(* ---------------------------------------------- *)
writeln(' Truc tiep: ');
Perfect2;
end;
BEGIN
Run;
writeln(' F I N I ! ');
readln;

11


END.

Kết quả

Gian tiep: 6 28 496 8128
Tong cong: 4 so
xong gian tiep
Thoi gian tinh: 6
Truc tiep: 6 28 496 8128
Tong cong: 4 so

Thoi gian tinh: 9

3.3 Phân tích số lớn
Mục này trình bày một thuật toán cải tiến để có thể phân tích nhanh một số lớn ra thừa số nguyên tố. Chúng
ta cần đáp ứng được hai tiêu chí tưởng như đối nghịch nhau sau đây:
 Chỉ đòi hỏi Sieve sinh các số nguyên tố trong khoảng 1..150 ngàn, nhưng
 có thể phân tích ra thừa số các số đến 2 tỷ.
Chương trình sẽ được thực hiện trên 14 test sau đây. Với mỗi test chương trình hiển thị trên màn hình dạng
phân tích ra thừa số của giá trị n ghi trong cột 2. Thí dụ, với test 7, n = 17631601 trên màn hình sẽ có kết quả:
13^2.17^2.19^2
Test No

Input file: decomp2.inp

Output: màn hình

14

Số test

1

4

2^2

2

12


2^2.3

3

8

2^3

4

100

2^2.5^2

5

1024

2^10

6

255255

3.5.7.11.13.17

7

17631601


13^2.17^2.19^2

8

999999999

3^4.37.333667

9

387420489

3^18

10

872825381

23957.36433

11

461590641

3^2.51287849

12

828696141


3^2.7.13.23.29.37.41

13

625718308

2^2.156429577

14

805306368

2^28.3

Thuật toán
12


 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 = 150000. Để
ý rằng (15.104)2 = 225.108 vượt quá giới hạn của số nguyên dương 32 bit.
 Bước 2. Lần lượt xét các số nguyên tố p[i] trong khoảng 1.. n và với điều kiện n > 1: 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 đồng thời tính lại gía trị n
= n / p[i]m.

Chương trình C++
// Decomp.cpp Phan tich cac so hang ty ra thua so
#include <iostream>
#include <fstream>
#include <math.h>
#include <string.h>

using namespace std;
const int MN = 150000;
int p[MN+1]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
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;
// Them 1 so nguyen to cuoi day

// lam linh canh
i = p[j]+2;
while(!Prime(i,2)) i += 2;
p[++j] = i;
return j;
}
// Bac cua thua so v trong n
int CDegree(int& n, int v){
int d = 0;
if (n < 2 || v < 2) return 0; // Qui uoc
while(n % v == 0){

13


}

++d; n /= v;
}
return d;

// Phan tich a ra thua so nguyen to
void Decompose2(int a){
if (a == 1) return;
int can = int(sqrt(a));
int i, m; // m la bac cua p[i] trong n
for (i = 1; a > 1 && p[i] <= can; ++i){
if (a % p[i] == 0) {
m = CDegree(a,p[i]);
if (m > 1) cout << p[i] << "^" << m << ".";

else cout << p[i] << ".";
}
} //for
if (a > 1) cout << a << ".";
return;
}
void Run(){
int soTest, n, i;
Sieve(MN);
ifstream f("decomp2.inp");
f >> soTest;
cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
f >> n;
cout << "\n Test no " << i << ": n = " << n << " = ";
Decompose2(n);
}
f.close();
}
main() {
Run();
// -------------------------------cout << endl << "\n\n F I N I ! \n";
return 0;
}

Chương trình Pascal
(* ------------------------------------------Decomp.pas: Phan tich cac so hang ty
ra thua so.
------------------------------------------*)
const MN = 150000;

type Li = longInt;
TA = array[0..MN+2] of Li;
var p: TA; { Day so nguyen to do Sieve sinh }
(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
begin
IsPrime := false; can := round(sqrt(n));

14


i := d;
while (p[i] <= can) do
begin
if (n mod p[i] = 0) then exit;
inc(i);
end;
IsPrime := true;
end;
(* The Sieve of Eratosthenes
Them 1 so nguyen to > n vao cuoi day
lam linh canh. *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
var i, j: Li;
begin
fillchar(p,sizeof(p), PRIME);
for i := 2 to round(sqrt(n)) do

if (p[i] = PRIME) then
for j := i to (n div i) do
p[j*i] := DELETED;
{ Dua cac so nguyen to ve dau mang p }
j := 0;
for i := 2 to n do
if (p[i] = PRIME) then
begin
inc(j); p[j] := i;
end;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
i := p[j]+2;
while not IsPrime(i,2) do i := i + 2;
inc(j); p[j] := i;
Sieve := j;
end;
{ Bac cua uoc nguyen to q trong n
n thay doi sau khi goi ham }
function CDegree(var n: Li; q: Li): Li;
var d: Li;
begin
d := 0; CDegree := 0;
if (n < 2) OR (q < 2) then exit;
while (n mod q = 0) do
begin
inc(d);
n := n div q;
end;
CDegree := d;

end;
procedure Decompose2(a: Li);
var i,m,can: Li;
begin
if (a = 1) then exit;
can := round(sqrt(a));
i := 1;
while (a > 1) and (p[i] <= can) do
begin
if (a mod p[i] = 0) then
begin

15


m := CDegree(a,p[i]);
if (m > 1) then write(p[i],'^',m,'.')
else write(p[i],'.');
end;
inc(i);
end;
if (a > 1) then write(a,'.');
end;
procedure Run;
var f: text;
soTest, i, n: Li;
begin
Sieve(MN);
assign(f,'decomp2.inp');
reset(f);

readln(f,soTest);
writeln(' So test = ',soTest);
for i := 1 to soTest do
begin
readln(f,n);
write(' Test no ',i,'. n = ',n,' = ');
Decompose2(n); writeln;
end;
close(f);
end;
BEGIN
Run;
writeln(' F I N I ! ');
readln;
END.

Kết quả

3.4 Bâc cao
Cho số nguyên dương n. Hãy cho biết trong dạng phân tích ra thừa số nguyên tố thì giá trị lớn nhất của số
mũ m là bao nhiêu.
Nếu n = 805306368 thì m = 28 vì 805306368 = 3.228.
Thuật toán
16


Ta thực hiện lại thuật toán trong bài 3.3 với chỉnh sửa nhỏ sau đây: Thay vì phân tích ra thừa số ta chỉ lấy số
mũ (bậc) max.

Chương trình C++

// Decomp.cpp Phan tich cac so hang ty ra thua so
#include <iostream>
#include <fstream>
#include <math.h>
#include <string.h>
using namespace std;
const int MN = 150000;
int p[MN+1]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
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;
// Them 1 so nguyen to cuoi day
// lam linh canh
i = p[j]+2;
while(!Prime(i,2)) i += 2;
p[++j] = i;
return j;
}
// Bac cua thua so v trong n
int CDegree(int& n, int v){
int d = 0;
if (n < 2 || v < 2) return 0; // Qui uoc
while(n % v == 0){
++d; n /= v;
}
return d;
}
int Max(int a, int b){

17


}

return (a >= b) ? a : b;

// Bac cao nhat cua uoc nguyen to cua a

int MaxDegree(int a){
if (a == 1) return 0;
int can = int(sqrt(a));
int i, maxm = 0;
for (i = 1; a > 1 && p[i] <= can; ++i){
if (a % p[i] == 0)
maxm = Max(maxm, CDegree(a,p[i]));
} //for
return (a > 1) ? Max(maxm,1) : maxm;
}
void Run(){
int soTest, n, i;
Sieve(MN);
ifstream f("decomp2.inp");
f >> soTest;
cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
f >> n;
cout << "\n Test no " << i << ", n = "<< n
<< " : " << MaxDegree(n);
}
f.close();
}
main() {
Run();
// -------------------------------cout << endl << "\n\n F I N I ! \n";
return 0;
}

Chương trình Pascal

(* ------------------------------------------MaxDeg.pas: Bac cao nhat cua uoc nguyen to
------------------------------------------*)
const MN = 150000;
type Li = longInt;
TA = array[0..MN+2] of Li;
var p: TA; { Day so nguyen to do Sieve sinh }
(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
begin
IsPrime := false; can := round(sqrt(n));
i := d;
while (p[i] <= can) do
begin
if (n mod p[i] = 0) then exit;
inc(i);

18


end;
IsPrime := true;
end;
(* The Sieve of Eratosthenes
Them 1 so nguyen to > n vao cuoi day
lam linh canh. *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
var i, j: Li;

begin
fillchar(p,sizeof(p), PRIME);
for i := 2 to round(sqrt(n)) do
if (p[i] = PRIME) then
for j := i to (n div i) do
p[j*i] := DELETED;
{ Dua cac so nguyen to ve dau mang p }
j := 0;
for i := 2 to n do
if (p[i] = PRIME) then
begin
inc(j); p[j] := i;
end;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
i := p[j]+2;
while not IsPrime(i,2) do i := i + 2;
inc(j); p[j] := i;
Sieve := j;
end;
{ Bac cua uoc nguyen to q trong n
n thay doi sau khi goi ham }
function CDegree(var n: Li; q: Li): Li;
var d: Li;
begin
d := 0; CDegree := 0;
if (n < 2) OR (q < 2) then exit;
while (n mod q = 0) do
begin
inc(d);

n := n div q;
end;
CDegree := d;
end;
function Max(a,b: Li): Li;
begin
if a > b then Max := a else Max := b;
end;
function MaxDeg(a: Li): Li;
var i, can, maxm: Li;
begin
MaxDeg := 0;
if (a = 1) then exit;
maxm := 1; can := round(sqrt(a));
i := 1;
while (a > 1) and (p[i] <= can) do
begin
if (a mod p[i] = 0) then

19


maxm := Max(maxm, CDegree(a,p[i]));
inc(i);
end;
if (a > 1) then Maxdeg := Max(maxm,1)
else Maxdeg := maxm;
end;
procedure Run;
var f: text;

soTest, i, n: Li;
begin
Sieve(MN);
assign(f,'decomp2.inp'); reset(f);
readln(f,soTest);
writeln(' So test = ',soTest);
for i := 1 to soTest do
begin
readln(f,n);
writeln(' Test no ', i, '. n = ',n,' : ', MaxDeg(n));
end;
close(f);
end;
BEGIN
Run;
writeln(' F I N I ! ');
readln;
END.

Kết quả

3.5 Lũy thừa
Cho số nguyên dương a. Hãy tìm số nguyên dương nhỏ nhất n thỏa điều kiện nn chia hết cho a.
Dữ liệu vào gồm k test như sau:
Dòng đầu tiên: số test k.
20


Mỗi dòng tiếp theo là một số a, giá trị tối đa là 1 tỷ.
Dữ liệu ra gồm k dòng, mỗi dòng một số n là kết quả tìm được theo test tương ứng..

power.inp
power.out
15
1
4
8
12
100
1024
255255
17631601
461590641
999999999
828696141
387420489
625718308
872825381
805306368

1
2
4
6
10
8
255255
4199
153863547
37037037
276232047

9
312859154
872825381
24

Thuật toán
Phương pháp 1. Duyệt toàn bộ
for (n = 1; n <= a; ++i)
if (Pow(n) % a == 0) Ghi n ra output file;

Phương pháp này chỉ thích hợp với dữ liệu nhỏ.
Để tính z = am ta dùng phương pháp chia để trị như sau:
Giả sử m = 2k + r, 0 ≤ r ≤ 1/ Khi đó,
nếu r = 0, tức là m là số chẵn thì z = (a2)k, ngược lại, nếu r = 1, tức là m là số lẻ thì z = a(a2)k.
Dựa vào công thức trên ta viết hàm Pow(x) tính z = xx theo phương pháp chia để trị như sau:
// z = x^x
int Pow(int x){
int a = x, z = 1;
while (x != 0){
if (x&1 != 0) z *= a;
x >>= 1; // x = x / 2
a = a*a;
}
return z;
}
Tương tự hàm PowMod(int x, int a)

tính z = xx mod a và sẽ được sử dụng trong phương pháp 2 cũng
được cài đặt theo phương pháp chia để trị như sau:
// z = x^x % a

int PowMod(int x, int a){
int z = 1, m = x; // Tinh z = x^m mod a
x %= a;
while (m != 0){
if (m & 1 != 0) z = z*x % a;
m >>= 1; // x = x / 2
x = (x*x)%a;
}
return z;
}

21


Phương pháp 2. Giả sử a được phân tích ra thừa số nguyên tố tức là a là tích các số nguyên tố đôi một khác
nhau pi với số mũ tương ứng mi.
Đặt t = p1p2…pk , và gọi m là giá trị lớn nhất trong các số mũ mi , i = 1..k.
Nhận xét 1. Mọi số nguyên dương b chia hết cho a khi và chỉ khi dạng phân tích ra thừa số của b chứa mọi thừa
số nguyên tố pi với số mũ không nhỏ thua mi, i = 1,2,…,k.
Nhận xét 2. Nếu t ≥ m thì n = t chính là số nhỏ nhất chia hết a.
Thật vậy, vì t = p1p2…pk nên khi đó nn = tt đáp ứng điều kiện trong nhận xét 1.
Nhận xét 3. Nếu t < m thì tồn tại một số i nhỏ nhất để n = it chính là số nhỏ nhất để nn chia hết a.
Từ các nhận xét trên ta xây dựng được thuật toán sau:
• Bước 1. Phân tích a ra thừa số nguyên tố. Gọi p1, p2, …, pk là các thừa số nguyên tố trong dạng phân
tích của a. Tính t = p1p2…pk và tính m là giá trị lớn nhất trong các số mũ mi , i = 1, 2, ..., k.
• Bước 2. Nếu t ≥ m thì lấy kết quả n = t, nếu không thì tìm i đầu tiên thỏa điều kiện (it)it chia hết a, lấy
kết quả n = it.
for (i = 2; PowMod(i*t,a) != 0; ++i);
return i*t;


Chương trình C++
// Power.cpp
#include <iostream>
#include <fstream>
#include <math.h>
#include <string.h>
using namespace std;
const int MN = 200000;
int p[MN+1]; // Day 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){
int d = 0;
if (n == 0 || v < 2) return 0; // Qui uoc

while(n % v == 0){
++d; n /= v;
}

22


return d;
}
// Kiem tra n = 1 ? hoac n nguyen to?
// n chia het cho mot trong cac so ng to p[d..c]
//
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// x^x
int Pow(int x){
int z = 1, m = x; // z = x^m
while (m != 0){
if (m & 1 != 0) z *= x;
m >>= 1; // m = m / 2
x *= x;
}
return z;
}
// x^x % a

int PowMod(int x, int a){
int z = 1, m = x;
x %= a; // z = z^m mod a
while (m != 0){
if (m & 1 != 0) z = z*x % a;
m >>= 1;
x = (x*x)%a;
}
return z;
}
int Power(int a){
int maxm = 0, m, can = int(sqrt(a));
int inita = a;
int i; // tich cac p[i]
int t = 1;
if (a == 1) return 1;
for (i = 1; a > 1 && p[i] <= can; ++i){
if (a % p[i] == 0) {
t *= p[i];
m = CDegree(a,p[i]);
if (m > maxm) maxm = m;
}
if (Prime(a,i+1)) break;
}// for
t *= a;
if (t >= maxm) return t;
for (i = 2; PowMod(i*t,inita) != 0; ++i);
return i*t;
}
void Run(){

int soTest, a, i, j;
Sieve(MN);
ifstream f("power.inp");
f >> soTest;

23


cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
f >> a;
cout << "\n Test no " << i << ". a = " << a;
cout << "
Result = " << Power(a);
}
f.close();
}
main() {
Run();
// -------------------------------cout << endl << "\n\n F I N I ! \n";
}

return 0;

Chương trình Pascal
(* Power.pas *)
const MN = 200000; nl = #13#10;
type int = longint;
var p: array[0..MN+1] of int; { Day so nguyen to }
{ The Sieve of Eratosthenes }

function Sieve(n: int): int;
const PRIME = 0; DELETED = 1;
var i, j: int;
begin
fillchar(p,sizeof(p), PRIME);
for i := 2 to round(sqrt(n)) do
if (p[i] = PRIME) then
for j := i to (n div i) do
p[i*j] := DELETED;
{ Dua cac so nguyen to ve dau mang p }
j := 0;
for i := 2 to n do
if (p[i] = PRIME) then
begin
inc(j) ; p[j] := i;
end;
Sieve := j;
end;
{ Bac cua thua so v trong n }
function CDegree(var n: int; v: int): int;
var d: int;
begin
d := 0; CDegree := 0;
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;


24


{----------------------------------------------Kiem tra n = 1 ? hoac n nguyen to?
n chia het cho mot trong cac so ng to p[d..c]
-------------------------------------------------}
function Prime(n, d: int): Boolean;
var i, can: int;
begin
Prime := true;
if (n = 1) then exit;
Prime := false; can := round(sqrt(n));
i := d;
while (p[i] <= can) do
begin
if (n mod p[i] = 0) then exit;
inc(i);
end;
Prime := true;
end;
{ z = x^x }
function Pow(x: int): int;
var z, m: int;
begin
z := 1; m := x;
while (m <> 0) do
begin
if Odd(m) then z := z*x;
m := m shr 1;

x := x * x;
end;
Pow := z;
end;
{ z = x^x mod a }
function PowMod(x, a: int): int;
var m: int;
z, xx,aa: int64;
begin
z := 1; m := x;
xx := x mod a;
while (m <> 0) do
begin
if Odd(m) then z := (xx*z) mod a;
m := m shr 1;
xx := (xx*xx) mod a;
end;
PowMod := z;
end;
function Power(a: int): int;
var maxm, m, inita, can, t, i: int;
begin
maxm := 0; can := round(sqrt(a));
inita := a;
if (a = 1) then
begin
Power := 1;
exit;
end;
i := 1; t := 1; { t : tich cac p[i] }

while (p[i] <= can) do

25


×