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

TÀI LIỆU bồi DƯỠNG học SINH GIỎI cấp THPT

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 (210.82 KB, 29 trang )

LÝ THUYẾT SỐ
I. Tóm tắt lý thuyết
Lý thuyết số, hay số học là lĩnh vực nghiên cứu về các số nguyên. Trong tài liệu
này, chúng ta sẽ đề cập đến một số kiến thức và các thuật toán về số học thường gặp,
bao gồm từ những vấn đề cơ bản.
1. Số nguyên tố
1.1. Nếu p là ước nguyên tố bé nhất của n, thì p2 ≤ n
(n=pq, mà p ≤ q, do đó p2 ≤ pq = n)
1.2. Thuật tốn kiểm tra tính ngun tố
Từ 1.1. ta có thuật tốn kiểm tra tính nguyên tố của một số n, chạy trong thời gian
O(n1/2):
function isprime(n): boolean;
begin
isprime:=false;
if (n<=2) then exit;
i:=2;
while (i*i<=n) do
begin
if n mod i = 0 then
exit;
inc(i);
end;
isprime:=true;
end;
1.3. Phân tích ra thừa số ngun tố
Cũng từ 1.1., ta có thuật tốn phân tích một số n ra thừa số nguyên tố:
procedure factor(n);
begin
i:=2;
while (i*i<=n) do
begin


if (n mod i = 0) then
begin
a:=0;
while (n mod i = 0) do
begin
n:=n div i;
inc(a);
end;
inc(m);
prime[m]:=i;
power[m]:=a;
end;
end;
if (n>1) then
begin
inc(m);
prime[m]:=n;
23


power[m]:=1;
end;
end;
Thông tin được trả về trong mảng prime và power: prime[i], power[i] tương ứng
cho biết thừa số và số mũ thứ i trong phép phân tích n ra thừa số nguyên tố.
Đây là phương pháp phân tích đơn giản nhất, được gọi là phép thử chia. Trong
trường hợp xấu nhất, n là số nguyên tố, thuật toán chạy trong thời gian O(n1/2)
1.4. Sàng số nguyên tố
Khi cần biết các số ngun tố đến một phạm vi nào đó, ví dụ từ 2 đến 10 8, sử dụng
sàng số nguyên tố Eratosthenes sẽ hiệu qủa hơn về thời gian.

Thủ tục sau tạo sàng số nguyên tố từ 2 đến N:
procedure sieve(n);
begin
fillchar(p, sizeof(p), true);
for i:=2 to n do
if (p[i]) then
begin
j:=i+i;
while (j<=n) do
begin
p[j]:=false;
j:=j+i;
end;
end;
end;
Thông tin trả về trong mảng p: p[i] = true nếu và chỉ nếu i là số nguyên tố.
Bạn có thể ước lượng thời gian chạy của thuật toán sàng Eratosthenes là O(nlogn), ta
sẽ đề cập đến một số ước lượng cần thiết ở những phần sau.
1.5. Ước lượng số số nguyên tố
Kí hiệu π(n) là số số nguyên tố bé hơn n. Đây là một ước lượng tương đối tốt và ngắn
gọn cho π(n):
π(n) ≈ n / ln(n)
Ví dụ, π(106) ≈ 106 / ln(106) ≈ 72382
Con số này sẽ giúp cho việc ước lượng thời gian tính tốn cho các bài toán liên quan
đến số nguyên tố
1.6. Bài tập
Cho một dãy số nguyên dương n phần tử: a1, a2, ..., an
Dãy con của dãy số là dãy nhận được sau khi xóa đi một số phần tử nào đó
Yêu cầu: Tìm dãy con dài nhất, sao cho tổng của hai số liên tiếp là số nguyên tố
Input:

NT1.IN
Dòng 1: n
Dòng 2: n số nguyên dương, a1, a2, ..., an
Output:
NT1.OUT
Dòng 1: độ dài của dãy con tìm được
Giới hạn:
4
• n <= 10
4
• ai <= 10
24


2. USCLN, thuật toán Euclid
2.1. (a, b) = (b, a mod b)
Thật vậy, từ đẳng thức
a = bq + r.
với r = a mod b
Ta thấy mọi ước chung d của a, b cũng là ước chung của b, r. Do đó (a, b) = (b, r).
2.2. Thuật tốn tìm USCLN
Từ 2.1, ta có thuật tốn Euclid tìm USCLN của a và b, viết dưới dạng đệ quy:
function gcd(a, b);
begin
if (agcd:=gcd(b, a)
else if (b=0) then
gcd:=a
else
gcd:=gcd(b, a mod b);

end;
Với a, b ≤ n, bạn có thể ước lượng thời gian thực hiện thuật toán vào khoảng O(log 10n),
tức là tỉ lệ với số chữ số của n.
2.3.
Nếu (a, b)=d, thì tồn tại hai số nguyên x, y sao cho
ax + by = d
2.4. Thuật toán Euclid mở rộng
Thuật toán Euclid mở rộng sẽ tìm USCLN d của a và b, đồng thời tìm được cả hai số
nguyên x, y trong phần 2.3
Thuật tốn Euclid mở rộng có thể diễn đạt bằng đệ quy như sau:
procedure ee(a, b, var x, var y);
var
x2,y2;
begin
if (aee(b, a, x, y)
else if (b=0) then
begin
x:=1;
y:=0;
end else
begin
ee(b, a mod b, x2, y2);
x:=y2;
y:=x2-(a div b)*y2;
end;
end;
Giải thích:
Từ 2.1, ta đã biết (a, b) = (b, r) = d
ee(a, b, var x, var y) trả về giá trị x, y sao cho ax + by = d

Dòng lệnh màu đỏ chạy thủ tục đệ quy: tìm x2, y2 sao cho:
bx2 + ry2 = d
25


Mặt khác:
r = a – bq
Với
r=a mod b
q=a div b
Do đó
bx2 + (a-bq)y2 = d
ay2 + b(x2 – qy2) = d
Vậy
x = y2
y = x2 – qy2
Cấu trúc đệ quy của thuật toán Euclid mở rộng cũng tương tự như thuật tốn Euclid.
2.5. Một số tính chất
Giả sử
a = p1a1p2a2...pkak
b = p1b1p2b2...pkbk
Định nghĩa:
USCLN: (a, b) = p1min(a1, b1)p2min(a2, b2)...pkmin(ak, bk)
BSCNN: [a, b] = p1max(a1, b1)p2max(a2, b2)...pkmax(ak, bk)
Tính chất:
• (a, b) x [a, b] = ab

(a, b, c) = ((a, b), c) = (a, (b, c))
• [a, b, c] = [[a, b], c] = [a, [b, c]]
Chú ý:

• Khơng có đẳng thức (a, b, c) [a, b, c] = abc
2.6. Bài tập
Cho dãy số nguyên dương n phần tử a1, a2, ..., an.
u cầu:
Tìm dãy con liên tiếp dài nhất có USCLN > 1
Input:
NT2.INP
Dòng 1: n
Dòng 2: n số nguyên dương, a1, a2, ..., an
Output:
NT2.OUT
Dòng 1: độ dài của dãy con tìm được
Giới hạn:
• n<=30000
0 < ai <= 32767

II/. Bài tập
2.1. Số nguyên tố.

2.1.1. Về hai hàm tìm số nguyên tố nêu ở trang 19 và 20 Tài liệu GKCT, Q1:
a. Hàm 1 (chọn k=2) có chạy chậm hơn hàm 2 (chọn k=6) hay không?
Hàm 1.
function prime1(a: longint): boolean;
var i, sqrt_a: longint;
begin
26


if (a<2) then exit(false);
sqrt_a := trunc(sqrt(a));

i:=2;
while (i<=sqrt_a) do begin
if (a mod i =0) then exit(false) else
inc(i);
end;
exit(true);
end;
Hàm 2.
function prime2(a: longint): boolean;
var i, sqrt_a: longint;
begin
if ((a=2) or (a=3)) then exit(true);
if ((a<2) or (a mod 2=0) or (a mod 3=0)) then
exit(false);
sqrt_a := trunc(sqrt(a));
i:=5;
while (i<=sqrt_a) do begin
if ((a mod i = 0) or (a mod (i+2)=0)) then exit(false)
else
inc(i,6);
end;
exit(true);
end;
Khi chọn k=2 thì xác suất số phải xét khoảng là 1/2 (chỉ xét các số lẻ), chọn k=6
xác suất số phải xét khoảng là 1/3 (chỉ xét các số 6k-1 và 6k+1). Khi dùng chúng để
tìm tất cả các số nguyên tố không vượt quá N với N nhỏ thì thời gian chạy khơng
chênh lệch nhau đáng kể, nhưng khi N lớn, thời gian đã khác biệt nhiều. Tham khảo
các số liệu sau về thời gian chạy chương trình ứng với máy có bộ nhớ 2G, bộ xử lí
ADM Dual-Core (1,65GHz):
1≤ N≤

1000
104
105
106
107
Thời gian chạy chtrình dùng hàm 1 (đơn vị giây) 0,005 0,015 0,203 4,695 117,75
Thời gian chạy ch trình dùng hàm 2 (đơn vị giây) 0,005 0,015 0,078 1,623 39,07
Số lượng số nguyên tố không vượt quá N
168 1229 9592 78498 664579

b. Trao đổi.
• Giải thích vì sao thời gian chạy hai chương trình tìm mọi số nguyên tố không
výợt quá N dung hai hàm nêu trên? Tìm hiểu ý nghĩa các dịng lệnh sau đây để đánh
giá ðộ phức tạp trung bình thời gian của mỗi thuật toán dùng trong hai hàm này :

Trong hàm prime2:
if ((a<2) or (a mod 2=0) or (a mod 3=0)) then exit(false);
27


và:
if ((a mod i = 0) or (a mod (i+2)=0)) then exit(false)
else
inc(i,6);
Trong hàm prime1:
if (a mod i =0) then exit(false) else
inc(i);
• Trong các vịng lặp của hai chương trình tại sao chỉ số i chỉ cần chạy tới

sqrt_a?

• Cả hai hàm được đánh giá có độ phức tạp trung bình là O( N ) có đúng
khơng?
2.1.2. Kiểm tra nhanh tính nguyên tố theo xác suất như thế nào?
Ý tưởng của các thuật tốn kiểm tra nhanh tính ngun tố là:
Giả sử có mệnh đề Q(p,a) đúng với các số nguyên tố p và mọi số tự nhiên
. Cần kiểm tra số n có là số ngun tố hay khơng, thực hiện phép thử xem Q(n, a) đúng
hay sai:
a) Nếu Q(n,a) khơng đúng, thì tất yếu n là hợp số.
b) Nếu Q(n,a) đúng, thì số n có thể là số ngun tố với một xác suất nào đó.
Khi tăng số lần thử (số lần chọn giá trị ngẫu nhiên của a), xác suất để từ b) suy ra n là
số nguyên tố sẽ tăng lên.
Do đó kiểm tra theo cách trên được gọi là kiểm tra xác suất tính nguyên tố.
a. Kiểm tra tính nguyên tố theo định lý Fecmat nhỏ
Mệnh đề Q(p,a) được dùng trong trường hợp này chính là định lý Fecmat nhỏ: “Nếu p
là số nguyên tố và a là số tự nhiên thì ap ≡ a (mod p).” Phát biểu cách khác: “Nếu p là
số nguyên tố thì với mỗi số nguyên a thỏa mãn 1 ≤ a

Trường hợp này thuật toán được gọi là: Kiểm tra Fecmat
Trong thuật tốn kiểm tra Fecmat cịn dựa vào một thuật tốn tính lũy thừa theo mơđun (có độ phức tạp thuật tốn là O(log 3n) cùng các tính chất của phép tốn đồng dư
theo cơng thức sau:
  2y   2
   ÷
y = 2k
 x ÷
÷



x y (mod N ) ≡ 
(mod (N) (*)
   2y  2


 
    ÷
y = 2k + 1
x × x ÷
÷
 

 

Chương trình kiểm tra Fecmat
var z, p, a : int64;
function exponent(x,y,n: int64):int64;// Xây dựng hàm xy (mod
N) theo công thức (*)
var z1 : longint;
28


begin
if y=0 then exponent := 1
else begin
z := exponent(x, y div 2, n);
if (y mod 2 = 0) then z1 := (z*z mod n)
else z1:= x*(z*z mod n);
if z1>n then z1:=z1 mod n;
exponent := z1;
end;
end;
function prime(p : int64): boolean;
var i: int64;
begin

a:= 2;
while a<=p-1 do begin
if exponent(a,p-1,p)<>1 then exit (false);
if p<2 then exit(false) else
exit(true);
inc(i);
end;
end;
BEGIN
write('nhap so p : '); readln(p);
if prime(p) then write('Nguyen to ')
else write('Khong nguyen to');
readln;
END.
Độ phức tạp trung bình của thuật tốn kiểm tra Fecmat là O(n.log3n) vì thủ tục
exponent có độ phức tạp O(log3n).
Trong trường hợp số p lớn muốn đạt tốc độ chạy nhanh hơn, người ta thực hiện
chỉ kiểm tra gần đúng số p lớn (gọi là kiểm tra xác suất) bằng cách không test mọi giá
trị của a từ 2 đến p-1 mà chỉ cho a nhận một vài giá trị ngẫu nhiên random(p-2)+2 để
test.
Gọi số lượng test là ntest, gọi xác suất tối thiểu để có thể thừa nhận p nguyên tố
là xacsuat. Gọi số lần đếm chưa khẳng định được p là hợp số là dem. Khi đó p được
thừa nhận là số nguyên tố nếu dem/ntest > xacsuat. Số lượng test là ntest càng lớn thì
khả năng thừa nhận p là nguyên tố càng tiếp cận đúng hơn . Xác suất tối thiểu xacsuat
có thể điều chỉnh tăng để tăng độ chính xác của thừa nhận. Kiểm tra tính nguyên tố
theo kiểu này gọi là kiểm tra xác suất Fecmat
b. Hàm kiểm tra xác suất Fecmat
function prime_F(p : int64): boolean;
var test,dem : longint;
29



begin
if (p=2) or (p=3) then exit(true);
if (p<2) or (p mod 2=0) or (p mod 3=0) then exit(false);
randomize; dem := 0;
for test:=1 to ntest do begin
a := random(p-2)+2;
if exponent(a,p-1,p)<>1 then exit(false)
else inc(dem);
if (dem/ntest>0.6) then exit(true);
end;
exit(false);
end;
Trong trường hợp này, độ phức tạp thời gian trung bình của thuật tốn là
O(k.log3N) trong đó k là số lần test (ntest).
2.1.3. Thuật toán Miller-Rabin (cũng dựa trên định lí Fecma nhỏ)
Thuật tốn Miller-Rabin cũng là một thuật tốn kiểm tra xác suất tính ngun tố. Để
xây dựng mệnh đề Q(p,a) chúng ta xét quá trình phân tích sau:
Giả sử số nguyên tố p lẻ phân tích thành: p-1=2s.d (d lẻ, s>0 ), a ∈ [1,2,.. p − 1] .
k

Xét dãy số: xk = a 2 .d (k=0, 1, ,s), các lưu ý:
+ khi k=0 có x0=ad (mod p)
+ xk=(xk-1)2
+ xs=ap-1
nên khi xây dựng dãy này bắt đầu từ x0 dần về xs ta sử dụng điều suy luận sau:
 xs −1 ≡ −1(mod p )
 xs −1 ≡ 1(mod p )


Nếu xs ≡ 1(mod p ) ⇔ xs −12 ≡ 1(mod p ) ⇔ 

Nếu xs −1 ≡ −1(mod p ) thì dừng, cịn nếu xs −1 ≡ 1(mod p ) thì tiếp tục với xs-2
Vậy trong quá trình xây dựng dãy {xk} từ x0 sẽ xảy ra những tình huống sau:
+ hoặc gặp xk ≡ −1(mod p) (0 ≤ k ≤ s-1)
+ hoặc tới k=s mà vẫn chỉ toàn gặp xk ≡ 1(mod p )
Vậy có mệnh đề Q(p,a) như sau:

Nếu p là số nguyên tố lẻ và p-1=2 s.d (d lẻ, s>0 ) thì: với mọi a: 0∀k ∈ [0; s] xk = a 2 .d ≡ 1(mod p)
thể xảy ra một trong hai trường hợp sau: 
k
∃k ∈ [0; s − 1] xk = a 2 .d ≡ −1(mod p )
k

∃k ∈ [0, s − 1] xk ≡1(mod p )

(1)

∀k ∈ [0, s ] xk ≡ − 1(mod p)

(2)

Do đó: Nếu 

thì n là hợp số.

Thuật tốn kiểm tra xác suất Miller-Rabin (gọi là kiểm tra Miller-Rabin):
+ Nếu n=2 thì n là nguyên tố. Kết thúc
+ Nếu n<2 hoặc n chẵn thì n là hợp số. Kết thúc.

+ Phân tích n-1thành d.2s (d là số lẻ)
30


+ Thực hiện vịng lặp (ngồi) với số lần chọn thích hợp {
+ Khởi tạo một số ngẫu nhiên a ∈ [1,n-1]
+ Tính x= ad (mod n)
+ Nếu x=1 hoặc x=n-1 quay về đầu vòng lặp
+ k=1
+ Trong khi k- Thay x=x2 (mod n)
- Nếu x=1 thì n là hợp số. Kết thúc.
- Nếu x = n-1 thì thốt vịng lặp trong
- Tăng k một đơn vị
}
+ Nếu x ≠ n-1 thì n là hợp số. Kết thúc
}
+ n là nguyên tố. Kết thúc.
Độ phức tạp thời gian trung bình của thuật tốn là O(s.log3n).
Một số hàm trong chương trình kiểm tra số nguyên tố theo Miller-Rabin
procedure analysis(x: int64; var d, s : int64);// phân tích
n-1=2s.d
begin
s := 0; d:=1;
while (x mod 2 =0) do begin
x := x div 2;
inc(s);
end;
d := x;
end;

function prime_RM(n : int64): boolean;
var dem, k, a, x : int64; test: longint;
begin
if n=2 then exit(true);
if (n<2) or (n mod 2=0) then exit(false);
analysis(n-1,d,s);
randomize;
for test:=1 to ntest do begin // ntest là số lần kiểm
thử
a := random(n-2)+2;
x := exponent (a,d,n);
if (x=1) or (x=n-1) then continue;
k := 1;
while kx:= exponent (x,2,n);
if x=1 then exit(false);
if x=n-1 then break;
31


inc(k);
end;
if x<>n-1 then exit(false);
end;
exit(true);
end;
2.1.4. Sàng Eratosthenes?
Thuật tốn sàng Eratosthenes. Tìm tồn bộ các số ngun tố khơng vượt q
số ngun dương N.
Thuật toán như sau: Biểu diễn các số nguyên trên trục số bắt đầu từ 2 đến N, sau

đó xoá dần các điểm là bội của 2 (khác 2), rồi các số là bội của 3 (khác 3), …, quá
trình tiếp tục như thế với các bội của i (khác i) khi mà i 2 ≤ N. Các số ngun cịn lại
chưa bị xố sẽ là số ngun tố.
Chương trình thực hiện thuật tốn sàng Eratosthenes (Thuận lợi khi cần tìm mọi
số ngun tố khơng vượt q N, N ≤ 109)
uses sysutils;
const fo ='';
maxnum = 10000 ;
var n, count : int64;
test : longint;
g : text;
x : array [2..maxnum] of boolean;
start, stop : tdatetime;
procedure creat_x;
var i, j, canN : int64;
begin
i:=2;
while (i<=maxnum) do begin // Khởi trị mảng đánh dấu x: các phần tử
chưa xét (nhận giá trị false)
x[i] := false;
i := i+1;
end;
n := maxnum;
canN := trunc(sqrt(n));
i:= 2;
while i<=canN do begin
j := i+i;
while j<=n do begin
x[j] := true; // j là hợp số vì là bội của i
j := j+i;

end;
i := i+1;
32


while x[i] do i := i+1; // Bỏ qua các số i liên tiếp là hợp số để gặp
số nguyên tố mới
end;
end;
BEGIN
start := now;
creat_x;
assign(g, fo); rewrite(g);
n := 2; count := 0;
while n<=maxnum do begin
if not x[n] then begin inc(count); write(g,n,' ');
end;
n:= n+1;
end;
writeln(g); write(g,count,' ');
stop := now;
write((stop-start)*3600*24:0:5);
close(g);
END.
Trao đổi. Trong thủ tục tao_x , vòng lặp “while i<=canN do begin” chỉ cho
giá trị biến i chạy đến canN (căn bậc hai của N)?
2.1.5. Chương trình phân tích số ngun thành tích các thừa số nguyên tố
var n,i : longint;
begin
write('Nhap so nguyen duong n : ');readln(n);

i := 2;
while (n>1) do begin
while (n mod i=0) do begin
write(i,' ');
n := n div i;
end;
inc(i);
end;
readln;
end.
2.1.6. Chương trình biểu diễn số nguyên theo cơ số tùy chọn
Chuyển biểu diễn số nguyên dương từ hệ cơ số c1 sang hệ cơ số mới c2.
Hạn chế: (2 ≤ c1, c2 ≤ 34).
uses
crt;
Const max
= 34;
H
: String = '0123456789ABCDEFGHIKLMNOPQRSTUVXYZ';
Var
c1,c2,so : Longint;
33


n
: String;
function Cosomin(S:String):Byte;
var i : Byte; ch : Char;
begin
ch := '0';

for i:=1 to length(s) do
if s[i]>ch then ch := s[i];
cosomin:= pos(ch,H);
end;
procedure nhap;
var i : byte;
begin
repeat
write('Cho biet co so thu nhat ');
{$i-} readln(c1); {$i+}
until (Ioresult=0) and (c1>=1) and (c1<=Max);
repeat
write('Cho biet so N (duoi dang co so thu nhat ): ');
{$i-} readln(n);
{$i+}
for i:=1 to length(n) do n[i]:=Upcase(n[i]);
until (ioresult=0) and (c1>=cosomin(n)) and (c1<=max);;
repeat
write(#10#13'Cho biet co so moi : ');
{$i-} Readln(c2);
{$i+}
until (ioresult=0) and (c2>1) and (c2<=max);
end;
function doiC1_10(s:String):Longint;
var i
: Byte;
p
: Longint;
begin
p:=0;

for i:=1 to length(s) do
p:= p*c1+ Pos(s[i],h)-1;
doiC1_10:=p;
end;
function doi10_C2(n:Longint):String;
var p : String;
i : Byte;
begin
p:='';
while (n>0) do begin
p
:= H[n mod c2+1]+p;
n
:= n div c2;
34


end;
doi10_C2:=p;
end;
BEGIN
repeat
clrscr;
nhap;
so := DoiC1_10(n);
writeln(N,'[cs ',c1,']=',Doi10_C2(so),'[cs ',c2,']');
write(#10#13,'ESC : Thoat . Phim khac : tiep tuc ');
until Readkey=#27;
END.
2.1.7. Bài tập 1. Kiểm tra số nguyên tố với tốc độ cao

Tìm số nguyên tố gần nhất nhỏ hơn N (3 ≤ N ≤ 232). Dữ liệu vào: Dòng đầu tiên chứa
số nguyên T là số lượng test. (T ≤ 10000). T dòng tiếp theo, mỗi dòng chứa một số
nguyên N. Kết quả: Với mỗi test, in kết quả trên một dòng. Hạn chế thời gian: 5 giây.
Ví dụ:
Dữ liệu:
3
5
10
17
Kết quả:
3
7
13
Để dự kiến cách giải bài tập trên, chúng ta có thể tham khảo kết quả chạy các
chương trình tìm các số ngun tố khơng vượt quá N. Hãy phác thảo các dự kiến giải
bài tập này sao cho bảo đảm hạn chế thời gian nêu trong đề. Bạn chọn dự kiến nào
trong các dự kiến sau đây:
A. Tạo sàng Eratosthenes, lưu vào một mảng các số nguyên tố từ 2 đến 2 32 , sau
đó đọc tệp input, với mỗi số N tìm được, tìm trên mảng đã lưu tìm số nguyên tố nhỏ
hơn và gần N nhất
B. Đọc input, mỗi lần được một số N, dùng hàm prime1 hoặc prime2 kiểm tra
phát hiện số nguyên tố nhỏ hơn và gần N nhất.
C. Đọc input, mỗi lần được một số N, dùng hàm prime_F hoặc hàm prime_RM
kiểm tra phát hiện số nguyên tố nhỏ hơn và gần N nhất.
D. Dự kiến khác.
Hãy phân tích từng dự kiến có khả năng thực hiện được hay không?

35



2.2. Ước chung lớn nhất.
2.2.1. Thuật toán Euclid
Bổ đề. Nếu a=bq+r (a, b ≠ 0, q và r là các số ngun) thì: UCLN(a, b)=UCLN(b,
r).
Thuật tốn Euclid tìm ýớc chung lớn nhất: Lặp thực hiện bổ đề trên cho đến
khi b=0.
Chương trình tìm UCLN theo định nghĩa
uses crt;
var x, y : longint;
function gcd(a,b : longint): longint;
var r : longint;
begin
if a<0 then a:= -a;
if b<0 then b:= -b;
while b<>0 do begin
r := a mod b;
a := b;
b := r;
end;
exit (a);
end;
begin
write('nhap hai so x va y : '); readln(x,y);
write('UCLN (',x,',',y,') = ',gcd(x,y));
readln;
end.
2.2.2. Thuật tốn Euclid mở rộng
Định lý. Nếu d=UCLN(a, b) thì tồn tại cặp số nguyên x và y sao cho d=ax+by.
Thuật toán Euclit mở rộng thể hiện qua thủ tục sau (giả mã) trả về d, x và y:
Extended_Euclid(a,b) {

Nếu b=0 thì trả về (d=a, x=1, y=0). Dừng
Gọi đệ qui Extended_Euclid(b, a mod b) trả về (d’, x’, y’);
a
Gán (d, x, y) ¬ (d’, y’, x’-   y’)
b

Trả về (d, x, y);
}
Thủ tục (thuật toán Euclid mở rộng)
procedure
ext_Euclid(a:
longint;b:
longint;
var x,y: longint);
var d1,x1,y1 : longint;
begin

longint;

var

d:

36


if b=0 then begin
d := a; x := 1; y := 0;
end
else begin

ext_Euclid(b, a mod b, d1, x1, y1);
d := d1;
x := y1;
y:= x1-(a div b)*y1;
end;
end;
2.2.3. Hàm Ơ-le
Hàm Ơ-le
cho số lượng các số nguyên từ 1 đến n nguyên tố cùng nhau với n. Nói
cách khác là số lượng các số nguyên trong đoạn [1;n] có UCLN với n bằng 1.
Một vài giá trị ban đầu: φ (1) =1, φ (2) =1, φ (3) =2, φ (4) =2, φ (5) =4,…
Ba tính chất đơn giản của hàm Ơ-le sau đây cho phép tính được giá trị của hàm.
• φ ( p) =p-1 với p là số nguyên tố
• φ ( p a ) = p a − p a −1 với p là số nguyên tố và a là số ngun.
• φ (a.b) = φ (a).φ (b)
Từ đó xây dựng được cách tính hàm Ơ-le φ (n) như sau:
a
a
a
• Phân tích n thành dạng tích các thừa số nguyên tố. n = p1 . p2 ... pk . (pi nguyên tố,
i=1, 2,..k)
a
a −1
a
a −1
a
a −1
a
• φ (n) = φ ( p1a ).φ ( p2a )...φ ( pk ) = ( p1 − p1 ) . ( p2 − p2 ) ... ( pk − pk ) .
1


1

2



Do ðó φ (n) = n 1 −


k

1

1

2

2

2

k

k

k

1
1  

1 
÷. 1 − ÷... 1 − ÷
p1  
p2  
pk 

Chương trình tính Hàm Ơle
uses crt;
var n, i : longint;
function ole(n : longint): longint;
var result, i : longint;
begin
result := n;
i := 2;
while (i*iif (n mod i=0) then begin
while (n mod i=0) do n := n div i;
result := result-(result div i);
end;
inc(i);
end;
if n>1 then result := result-(result div i);
ole := result;
37


end;
BEGIN
write('Nhap n: '); readln(n);
write(ole(n));

readln;
END.
Tính chất quan trọng nhất của hàm Ơ-le thể hiện qua định lý Ơ-le:
Định lý Ơ-le. aφ ( m ) ≡ 1 (mod m) với a và m nguyên tố cùng nhau.
Và một trường hợp đặc biệt của định lý này là định lý Fecma nhỏ (nêu ở trên): a p −1 ≡ 1
(mod p).
2.2.4. Một số khái niệm về đồng dư
Phép toán đồng dư. Dư của phép chia số nguyên cho N có N giá trị là 0, 1, 2,
…, N-1. Hai số nguyên x và y có cùng một số dư khi chia cho N được gọi là hai số
nguyên đồng dư, kí hiệu: x ≡ y (mod N).
Hai số nguyên x và y được gọi là đồng dư theo môđun N nếu (x-y) chia hết cho
N.
Các số nguyên có cùng số dư khi chia cho N tạo thành một lớp đồng dư. Tập hợp
các lớp đồng dư theo N kí hiệu là Zn. Ví dụ có 3 lớp đồng dư theo mơđun N=3 là:
… -9, -6, -3, 0, 3, 6, 9, …
… -8, -5, -2, 1, 4, 7, 10, …
… -7, -4, -1, 2, 5, 8, 11, …
Một số tính chất
• x ≡ x ' (mod N) và y ≡ y ' (mod N) thì x + y ≡ x '+ y ' (mod N) và xy ≡ x ' y '
(mod N)
• x + y + z ≡ x + ( y + z ) ≡ ( x + y ) + z (mod N)
• xy ≡ yx (mod N)
• x( y + z ) ≡ xy + xz (mod N)
• Số đối của x theo mơđun N là N-x
• Số nghich đảo của x theo môđun N là số y sao cho x.y ≡ 1 (mod N). Kí hiệu
y=x-1
Định lý cơ bản của đồng dư: ∀x, y, n∈N , n≠ 0 và kí hiệu θ là một trong 3 phép
tốn: +, -, * thì: (xθy) mod n = ((x mod n) θ (y mod n)) mod n.
Hệ quả. xy mod n = (x mod n)y mod n (áp dụng khi x>n).
2.2.5. Giải phương trình đồng dư tuyến tính (Đi-ơ-phăng): ax ≡ b (mod n).

Cho phương trình ax ≡ b (mod n), với a, b, n là các số tự nhiên, n>0. Cách giải là:
a) Tìm d là UCLN (a,n) bằng thuật tốn Euclid mở rộng d=a.x+b.n
b) Nếu d không là ước của b thì phương trình vơ nghiệm.

38


c) Nếu b = kd thì phương trình có đúng d nghiệm trong tập Z n. Giả sử x0 là một
nghiệm tùy ý (có thể chọn bằng x 0= x
x0 + i ×

b
), thì d nghiệm của phương trình có dạng
d

n
(mod n). , i = 0, 1, 2, ..., (d-1).
d

Ví dụ. Giải phương trình đồng dư 14x ≡ 30 (mod 100).
Ta có a=14; b = 30; n = 100; d=UCLN(14,100)=2=14.(-7)+100.1 (x=-7, y=1)
d là UCLN của a và n nên d=(14, 100)=2, d là ước của b vì 30 chia hết cho 2, nên
phương trình có 2 nghiệm trong Z 100. Hai nghiệm này là x1=-7.30/2= -105 và
x2=x1+100/2=x1+50=-55.
Chương trình giải phương trình Đi-ơ-phăng
uses
crt;
var
a, b, n, d, x, y : longint;
procedure ext_euclit (a, b : longint; var d, x, y :

longint);
var d1, x1, y1 : longint;
begin
if (b=0) then begin
d := a; x := 1; y := 0;
end else begin
ext_euclit(b, a mod b, d1, x1, y1);
d := d1;
x := y1;
y := x1 - (a div b)*y1;
end;
end;
procedure solution(a,b,n : longint);
var i : longint;
begin
ext_euclit(a,n,d,x,y);
if (b mod d=0) then begin
x := x*(b div d);
if x<0 then
while (x<0) do x := x + n div d
else
if (x>0) then
while (x>0) do x := x - n div d;
if x<0 then x := x + n div d;
writeln('Cac nghiem la: ');
for i:=0 to d-1 do writeln('x',i,' = ',x + i * ( n div
d));
end else writeln('vo nghiem');
39



end;
BEGIN
Writeln('Giai phuong trinh Di o phang ax=b (mod n) :');
write('Nhap a, b, n: '); readln(a, b, n);
solution(a, b, n);
readln;
END.
Trao đổi. Thuật tốn giải phương trình đồng dư: Vì sao khi b chia hết cho d thì
phương trình có d nghiệm trong Zn và có thể chọn một nghiệm là x0= x

b
.
d

2.2.6. Bài tập 2. Hàn Tín điểm quân
Tục truyền rằng ngày xưa Hàn Tín danh tướng của Lưu Bang (Hán Cao Tổ) điểm
binh cho lính xếp hàng ba, hàng năm, hàng bẩy rồi ghi các số lẻ tương ứng sẽ suy ra số
lính bằng cách sau đây: Nhân số lẻ hàng ba với 70, số lẻ hàng năm với 21, nhân số lẻ
hàng bẩy với 15. Cộng các kết quả ấy lại rồi thêm một bội số thích hợp của 105 sẽ
được số lính. Nếu ký hiệu số lính là S, số lẻ xếp hàng 3, hàng 5, hàng 7 tương ứng là
a, b, c thì S = (70.a + 21.b + 15.c) + 105.k (k là số nguyên chọn thích hợp để S phù
hợp là số lính của một đại đội, một tiểu đoàn hay trung đoàn... ).
Đây cũng là bài toán đề xuất bởi nhà toán học Trung quốc Sun-Tsu.
Cơ sở toán học của cách điểm binh nêu trên là định lý sau:
Định lý. Cho m1, m2, …, mn là các số nguyên đôi một nguyên tố cùng nhau. Hệ
phương trình:
 x ≡ a1

 x ≡ a2

(I) 
...
x ≡ a
n


( mod m1 )
( mod m2 )
( mod mn )

Có một nghiệm duy nhất là x ≡
với m= m1.m2…mn và Mi=

n

∑ a M y (mod m)
i =1

i

i

i

m
, yi là nghịch đảo của Mi theo môđun mi: yi.Mi ≡ 1
mi

(mod mi)
Ví dụ. Tìm nghiệm của hệ phương trình đồng dư sau:

x ≡ 2

x ≡ 3

x ≡ 2

( mod 3)
( mod 5) với x∈ [300; 400]
( mod 7 )

m=3 × 5 × 7, M1=35, M2=21, M3=15,
y1=2 vì 35 × 2 ≡ 1(mod 3), y2=1 vì 21 × 1 ≡ 1(mod 5), y3=1 vì 15 × 1 ≡ 1(mod 7).
Vậy nghiệm của hệ theo mơđun m=105 là :
x= 2 × 35 × 2 + 3 × 21 × 1 + 2 × 15 × 1 = 233 ≡ 23 (mod 105).
40


x ∈ [300; 400] nên x=338
Yêu cầu. Hãy viết chương trình nhập vào các số a1, a2, ..., an, m1, m2, ..., mn, c , d
và giải hệ (I) trên tập [c; d].
Input. Dòng đầu là số N (số phương trình thuộc hệ)
N dịng tiếp theo lần lượt là N số ai
N dòng tiếp theo lần lượt là N số mi
Dòng cuối cùng là hai số c và d.
Output. Hiện trên màn hình nghiệm duy nhất của hệ (nếu có) hoặc thông báo
”vo nghiem” trong trường hợp vô nghiệm.
Trao đổi.
1. Cách tìm số nghịch đảo của số a theo mod n?
2. Vì sao hệ (I) có nghiệm duy nhất là: x ≡


n

∑ a M y (mod m)?
i =1

i

i

i

2.3. Số nguyên lớn
2.3.1. Xây dựng các phép toán cộng, trừ và nhân
Bài toán. Dùng mảng một chiều, xây dựng các hàm cộng, trừ, nhân hai số ngun
có hàng nghìn chữ số.
Input. Dịng đầu là hai số nguyên la, lb (<20000) là số chữ số của hai số a và b
Dòng tiếp theo là la chữ số của số a (nếu a<0 thì trước la chữ số này là dấu âm)
Tiếp theo nữa, từ đầu dòng mới (kế tiếp với dòng cuối cùng biểu diễn a) là lb chữ
số của số b (nếu b<0 thì trước lb chữ số này là dấu âm)
Output. Ghi a, b và kết quả phép tính (cộng hoặc trừ hoặc nhân) giống như qui
định của input.
Chương trình xây dựng các phép toán: Cộng, trừ, nhân hai số nguyên lớn
uses crt;
const maxsize = 20000;
fi
= 'big_nums.in';
fo
= 'big_nums.out';
type arr = array[1..maxsize] of integer;
bignum = record

sign : integer; //dấu
value : arr;
//các chữ số
size : integer; //số lượng các chữ số
end;
var
a, b, c : bignum;
f, g
: text;
la, lb, lc : longint;
procedure nhap;
var ch : char; i : integer;
begin
fillchar(a,sizeof(a),0);
41


fillchar(b,sizeof(b),0);
assign(f,fi); reset(f);
readln(f,la,lb);
a.size := la; b.size := lb;
read(f,ch);
if (ch in ['0'..'9']) then begin
a.sign := 1;
a.value[1] := ord(ch)-48;
for i:=2 to la do begin
read(f,ch);
a.value[i] := ord(ch)-48;
end;
end

else begin
a.sign := -1;
for i:=1 to la do begin
read(f,ch);
a.value[i] := ord(ch)-48;
end;
end;
readln(f);
read(f,ch);
if (ch in ['0'..'9']) then begin
b.sign := 1;
b.value[1] := ord(ch)-48;
for i:=2 to lb do begin
read(f,ch);
b.value[i] := ord(ch)-48;
end;
end
else begin
b.sign := -1;
for i:=1 to lb do begin
read(f,ch);
b.value[i] := ord(ch)-48;
end;
end;
readln(f);
close(f);
end;
procedure write_out(x: bignum);
var i : longint;
begin

42


if x.sign=-1 then write(g,'-');
i:=1; while (x.value[i]=0) and (iwhile i<=x.size do begin write(g,x.value[i]); inc(i);
end;
writeln(g);
end;
procedure daomang(var x: bignum);
var i,j, temp : longint;
begin
i := 1; j := x.size;
while itemp := x.value[i];
x.value[i] := x.value[j];
x.value[j] := temp;
inc(i); dec(j);
end;
end;
function biger(a,b: bignum): boolean;
var i: longint;
begin
if a.size>b.size then exit(true);
if a.sizeif a.size=b.size then begin
for i:=1 to a.size do
if a.value[i]if a.value[i]>b.value[i] then exit(true)
end;

exit(true);
end;
function add(a,b: bignum): bignum;
var i, l, temp, carry : longint; c: bignum;
begin
fillchar(c,sizeof(c),0);
if a.size>b.size then l:=a.size else l:=b.size;
carry := 0;
daomang(a); daomang(b);
for i:=1 to l do begin
temp := a.value[i]+b.value[i]+carry;
c.value[i] := temp mod 10;
carry := temp div 10;
end;
if carry>0 then begin c.value[l+1]:=1; c.size := l+1;
end
43


else c.size := l;
daomang(c);
exit(c);
end;
function sub(a,b: bignum): bignum;
var i, l, temp, carry : longint; c: bignum;
begin
l:=a.size;
carry := 0; fillchar(c,sizeof(c),0);
daomang(a); daomang(b);
for i:=1 to l do begin

temp := a.value[i] - b.value[i] - carry;
if temp<0 then begin
temp := temp+10;
carry := 1;
end
else carry:= 0;
c.value[i] := temp mod 10;
end;
c.size := l;
daomang(c);
exit(c);
end;
operator + (a,b: bignum) c: bignum;
begin
if (a.sign>0) and (b.sign>0) then begin
c :=add(a,b); c.sign := 1;
end else
if (a.sign<0) and (b.sign<0) then begin
c :=add(a,b); c.sign := -1;
end else
begin
if biger(a,b) then begin
c := sub(a,b);
c.sign := a.sign;
end else begin
c := sub(b,a);
c.sign := b.sign;
end;
end;
end;

operator - (a,b: bignum) c: bignum;
begin
44


b.sign := b.sign * (-1);
c:= a+b;
end;
function mult1(x: bignum; y : integer): bignum;
var sum, carry, l, i : integer; z : bignum;
begin
daomang(x);
fillchar(z,sizeof(z),0);
l := x.size;
carry :=0;
for i:=1 to l do begin
sum := x.value[i]*y + carry;
z.value[i] := sum mod 10;
carry := sum div 10;
end;
if carry>0 then begin
inc(l);
z.value[l] := carry;
z.size
:= l;
end
else z.size := l;
daomang(z);
exit(z);
end;

function mult(x, y: bignum): bignum;
var l, i, j, lech : integer; z, z1 : bignum;
begin
fillchar(z,sizeof(z),0);
fillchar(z1,sizeof(z1),0);
l := y.size;
z := mult1(x,y.value[l]);
lech := 0;
for i:=l-1 downto 1 do begin
z1 := mult1(x,y.value[i]);
inc(lech);
for j:= 1 to lech do z1 := mult1(z1,10);
z
:= add(z,z1);
end;
exit(z);
end;
operator * (x,y : bignum) z: bignum;
begin
z:=mult(x,y);
45


if x.sign*y.sign>0 then z.sign:=1
else z.sign:=-1;
end;
BEGIN
clrscr;
assign(g,fo); rewrite(g);
nhap;

write_out(a);
write_out(b);
write_out(a-b); // Hoặc write_out(a+b); hoặc
write_out(a+b);
close(g);
END.
2.3.2. Bài tập 3. Tính 20000!
Đọc số N từ tệp 20000GT.IN. Ghi kết quả 20000! ra tệp 20000GT.OUT.
Gợi ý. 20000! có 77338 chữ số. Vì vậy dùng mảng A một chiều kích thước
khoảng 20000 phần tử (mỗi phần tử chứa 4 chữ số) chứa kết quả. Để thuận tiện khi
nhân, chữ số hàng đơn vị của kết quả được chứa vào phần tử cuối cùng (vị trí last) của
mảng. Chữ số hàng cao nhất (bên trái nhất) của kết quả ở vị trí do biến first chỉ vào.
Mỗi lần nhân (k-1)! với k để có k! ta thực hiện như sau:
Đem k nhân lần lượt với các chữ số có trong mảng A, theo thứ tự từ ô cuối cùng
trở về ô firrt-5. Chữ số hàng đơn vị của tích được thay cho giá trị cũ của ơ, phần cịn
lại của tích thì lưu vào biến carry để mang sang gộp với tích của k nhân với ơ tiếp
theo.
Mỗi lần nhân thêm một thừa số, biến first lại lùi về trái nhiều nhất là 5 phần tử vì
giá trị lớn nhất của mỗi lần nhân với một thừa số là 20000x9999=199980000 có 9 chữ
số, nên sau mỗi lần nhân với một thừa số tiếp theo chỉ thêm nhiều lắm là 5 ô bên trái
kết quả đã có ở lần nhân trước. Tất nhiên, sau đó cần điều chỉnh lại vị trí biến first dịch
sang phải cho đúng vị trí trái nhất của kết quả sau mỗi lần nhân.

2.3.3. Bài tập 4. Số Catalan
Xét tích n số M1, M2, …, Mn là số M=M1.M2….Mn. Số cách chèn cặp ngoặc đơn
vào tích này để được các cách biểu diễn khác nhau của M gọi là số Catalan. Nếu coi số
Catalan thứ nhất ứng với n=1, thì số Catalan thứ n được tính theo công thức truy hồi là
46



n

T (n) = ∑ T (i ).T (n − i ) (*). Hãy viết chương trình tính số Catalan thứ n. Hạn chế n ≤ 200.
i =1

(Khi n=200, số Catalan có 117 chữ số).
Gợi ý.
Cách 1. Sử dụng quy hoạch động, xây dựng mảng một chiều T(N) để tính các số
Catalan từ số thứ nhất dần về số thứ N theo công thức truy hồi (*). Thực hiện phép
nhân và cộng số lớn.
Cách 2. T (n) = C2nn+1 − C2nn …
Cách 3. Nếu coi số Catalan thứ nhất ứng với n=0 thì T[0]=1, T[1]=1, T[2]=2,
T[3]=5, T[4]=14,… Trong trường hợp này có thể sử dụng cơng thức:
T (n) =

( 2n ) ! = ( n + 2 ) × ( n + 3) × ... × (2n)
1
C2nn =
, cần thực hiện phép chia số
n +1
1× 2... × n
( n + 1) !× n !

lớn
Chương trình T (n) =

( 2n ) ! = ( n + 2 ) × ( n + 3) × ... × (2n)
1
C2nn =
n +1

1× 2... × n
( n + 1) !× n !

uses crt, sysutils;
const
fi='catalan.in';
fo='catalnan.out';
maxsize = 20000;
base
= 10000;
type
bignum = record
size : integer;
value: array[0..maxsize] of longint;
end;
var
a : bignum; n,k : integer;
f, g : text;
start : tdate;
function mult(a : bignum; b : longint): bignum;
var i : integer; carry, s : longint;
c : bignum;
begin
fillchar(c,sizeof(c),0);
carry := 0;
for i:= maxsize downto maxsize-a.size+1 do begin
s := a.value[i]*b + carry;
carry := s div base;
c.value[i] := s mod base;
end;

c.size := a.size;
if carry>0 then begin
47