You are on page 1of 26

Algebarski algoritmi

Uvek kada izvršavamo neku algebarsku operaciju, kao što je recimo množenje
dva broja ili njihovo stepenovanje, mi u stvari izvršavamo neki algoritam. Mi
te operacije koristimo kao gradivne elemente u razvijanju složenijih algoritama
i često ne zalazimo dublje u analizu njihove složenosti. Međutim, i sami al-
goritmi sabiranja, oduzimanja, množenja i deljenja brojeva (posebno ako su
brojevi dati nizovima svojih cifara) predstavljaju važne algebarske algoritme. U
algebarske algoritme spadaju i mnogi algoritmi sa kojima smo se već susreli kao
što su izračunavanje vrednosti broja na osnovu datih cifara ili, nasuprot tome,
određivanje cifara broja na osnovu njegove vrednosti, zatim razni algoritmi nad
polinomima kao što su izračunavanje vrednosti polinoma i množenje polinoma.
U ovom poglavlju bavićemo se algebarskim algoritmima sa kojima se do sada
nismo susreli. Mnogi od njih igraju važnu ulogu u oblasti kriptografije, ali i u
drugim oblastima.

Modularna aritmetika

Kažemo da je broj r ostatak pri deljenju broja x brojem y i pišemo x mod y = r


ako i samo ako postoji broj q takav da je x = q · y + r i 0 ≤ r < y. Pored toga što
sa mod označavamo binarnu operaciju, mod se može koristiti i kao oznaka
binarne relacije u skupu celih brojeva. Naime, pisaćemo a ≡ b (mod m) ako
m|(a − b). Ovo je ekvivalentno tome da a i b daju isti ostatak pri deljenju sa m.
Na primer, pišemo 12 ≡ 2 (mod 5) jer 5|(12 − 2).
Relaciju mod koristimo u nekim svakodnevnim situacijama, a da toga često
nismo ni svesni. Jedan od takvih primera je rad sa vremenom. Naime, za
15 sata nakon 11 časova reći ćemo da je 2 sata (što odgovara tome da je
11 + 15 ≡ 2 (mod 24)). Slično važi i za dane u nedelji, koje računamo po
modulu 7: danas je recimo četvrtak i interesuje nas koji će dan biti za 100 dana.
Analogno se računa i mesec ili redni broj nedelje u godini. Slično je i prilikom
rada sa uglovima.
Modularna aritmetika ima puno praktičnih primena: koristi se za izračunavanje
kontrolnih suma za međunarodne standardne identifikatore knjiga (ISBN brojeve)
i identifikatore banki (IBAN). Modularna aritmetika je i u osnovi savremenih
kriptografskih sistema.
Brojevi se u računarima predstavljaju po modulu 2k . Na primer, u jeziku
C++ brojevi tipa unsigned int predstavljaju se po modulu 232 . Na primer, u
narednom kodu rezultat kvadriranja broja 123456789 biće vrednost 1234567892
mod 32 = 2537071545.

1
unsigned int x = 123456789;
cout << x*x << endl;
Ukoliko je potrebno odrediti vrednost zbira ili proizvoda brojeva po modulu m
od pomoći mogu biti naredne relacije:

(a + b) mod m = (a mod m + b mod m) mod m


(a · b) mod m = (a mod m · b mod m) mod m

Dokažimo drugu relaciju (prva je jednostavnija za dokazivanje). Pretpostavimo


da je a = qa · m + ra i b = qb · m + rb za 0 ≤ ra , rb < m. Tada važi da je
a · b = (qa · m + ra ) · (qb · m + rb ) = (qa · qb · m + qa · rb + ra · qb )m + ra · rb . Ako važi
da je ra ·rb = q·m+r za 0 ≤ r < m, tada je a·b = (qa ·qb ·m+qa ·rb +ra ·qb +q)m+r,
pa je (a · b) mod m = r. Važi da je (a mod m · b mod m) mod m = (ra · rb )
mod m = r, čime je tvrđenje dokazano.
Razmotrimo problem određivanja razlike brojeva b i a po modulu m. Na primer,
potrebno je odrediti vrednost (b − a) mod m. U slučaju kada je b < a mi bismo
želeli da kao vrednost ovog izraza dobijemo nenegativnu vrednost. Međutim, ne
postoji saglasnost između različitih programskih jezika u računanju vrednosti
a%m kada je a negativno. Naime, u jeziku C++ bismo za vrednost ostatka dobili
negativan broj: na primer vrednost izraza (2-7)%3 bila bi -2, dok bi u jeziku
Python kao rezultat dobili pozitivan broj 1. Umesto da vršimo analizu slučajeva,
rešenje je moguće dobiti izračunavanjem vrednosti izraza (b − a + m) mod m
tj. (b mod m − a mod m + m) mod m. Zaista, ako je b ≥ a, vrednost izraza
b − a + m biće veća ili jednaka m i njen ostatak pri deljenju sa m biće jednak
vrednosti b − a (traženje ostatka će praktično poništiti prvobitno dodavanje
vrednosti m). Sa druge strane, ako je b < a i 0 ≤ a, b < m tada će b − a biti
negativan broj, pa će se dodavanjem vrednosti m dobiti broj iz intervala između
0 i m − 1. Zato pronalaženje ostatka neće na kraju promeniti rezultat i dobiće
se vrednost b − a + m za koju smo rekli da je tražena vrednost u ovom slučaju.
U prethodnom smo se oslonili na činjenicu da su i a i b brojevi koji su veći ili
jednaki od 0 i strogo manji od m. Pronalaženje vrednosti razlike po modulu m
moguće je i u opštem slučaju i važi

(B − A) mod m = (B mod m − A mod m + m) mod m

Dokažimo ovo tvrđenje. Podsetimo se da je x mod y = r ako i samo ako postoji


q takav da je x = q · y + r i ako je 0 ≤ r < y. Neka je A = qa · m + a i
B = qb · m + b, za 0 ≤ a, b < m. Zato je A mod m = a i B mod m = b.
Neka je B − A + m = p · m + r za neko 0 ≤ r < m. Zato je (B mod m − A
mod m + m) mod m = (b − a + m) mod m = r. Takođe, važi i da je B − A =
(qb − qa ) · m + (b − a) = (qb − qa − 1) · m + (b − a + m) = (qb − qa − 1 + p) · m + r,
pa je i (B − A) mod m = r, čime je tvrđenje dokazano.

2
Važi i naredno tvrđenje:

an mod m = (a mod m)n mod m

Problem: Napiši program koji određuje poslednje tri cifre zbira i poslednje tri
cifre proizvoda četiri uneta cela broja manja od 999.
Ideja koja prirodno prva padne na pamet je da se izračunaju zbir i proizvod
unetih brojeva, a da se onda poslednje tri cifre odrede izračunavanjem ostatka
pri celobrojnom deljenju sa 1000. Kada se u obzir uzme raspon brojeva koji
se unose, takvo rešenje bi davalo korektne rezultate u slučaju zbira, međutim,
proizvod brojeva može biti mnogo veći od samih brojeva i zato je za izračunavanje
poslednje tri cifre proizvoda potrebno primeniti malo napredniji algoritam koji
koristi činjenicu da su za poslednje tri cifre proizvoda relevantne samo poslednje
tri cifre svakog od činilaca. Zato je pre množenja moguće odrediti ostatak pri
deljenju sa 1000 svakog od činilaca, izračunati proizvod, a onda odrediti njegove
tri poslednje cifre izračunavanjem njegovog ostatka pri deljenju sa 1000.
Funkcije za množenje i sabiranje po modulu zasnovaćemo direktno na prikazanim
relacijama, a onda ćemo ih primenjivati tako što ćemo proizvod (tj. zbir) po
modulu m svih prethodnih brojeva primenom funkcija kombinovati sa novim
brojem. Dakle, ako funkciju za sabiranje po modulu m označimo sa +m , a
množenje po modulu m sa ·m , izračunavaćemo ((a +m b) +m c) +m d tj. ((a ·m
b) ·m c) ·m d.
Ipak, naglasimo da je izračunavanje celobrojnog količnika i ostatka vremenski
zahtevna operacija (najčešće se izvršava dosta sporije nego osnovne aritmetičke
operacije), tako da je u praksi poželjno izbeći ih kada je to moguće. Na primer,
ako će zbir a + b biti moguće predstaviti odabranim tipom podataka, tada je
umesto (a mod m + b mod m) mod m ipak bolje koristiti (a + b) mod m.
int plus_mod(int a, int b, int m){
return ((a % m) + (b % m)) % m;
}

int puta_mod(int a, int b, int m){


return ((a % m) * (b % m)) % m;
}

int main(){

int a, b, c, d;
cout << "Unesi cetiri broja" << endl;
cin >> a >> b >> c >> d;
cout << "Poslednje tri cifre njihovog zbira su "
<< plus_mod(plus_mod(plus_mod(a,b,1000),c,1000),d,1000) << endl;
cout << "Poslednje tri cifre njihovog proizvoda su "

3
<< puta_mod(puta_mod(puta_mod(a,b,1000),c,1000),d,1000) << endl;
return 0;
}
Moguća je i naredna optimizacija. Naime, na osnovu invarijante tekuća vrednost
je uvek u rasponu [0, 1000): ovo važi za ulazne vrednosti, a važi i za rezultat
funkcija plus_mod i puta_mod. Stoga nije neophodno računati ostatak tekuće
vrednosti pri deljenju sa 1000. Dakle, od tri pojave operacije % u funkcijama
plus_mod i puta_mod, dovoljno je ostaviti samo poslednju.
int plus_mod(int a, int b, int m){
return (a + b) % m;
}

int puta_mod(int a, int b, int m){


return (a * b) % m;
}

Faktorizacija na proste činioce

Problem: Dat je broj n. Rastaviti ga na proste činioce.


Na primer, ako je n = 315, onda izlaz treba da bude oblika 3 3 5 7.
Problem možemo rešavati induktivno-rekurzivnim pristupom na sledeći način.
Pretpostavimo da umemo da odredimo proste činioce za sve brojeve manje od n.
Ako n nije prost broj, onda se on može predstaviti√kao proizvod n = d · n1 , gde
je d najmanji od svih delilaca broja n i važi d ≤ n. Činilac d se√onda može
pronaći prolaskom kroz skup prirodnih brojeva u redosledu od 1 do n (redosled
nam garantuje pronalaženje najmanjeg činioca), a ostale činioce broja n1 po
induktivnoj hipotezi znamo da odredimo. Iterativna varijanta ovog rekurzivnog
algoritma bi se sastojala iz narednih koraka: sve dok je n deljivo sa 2, odštampati
2 i vrednost n postaviti na n/2. Kada n√više nije deljivo sa 2, onda su svi činioci
neparni te idemo redom od i = 3 do n po skupu neparnih brojeva i ako je
n deljivo sa i, sve dok je n deljivo sa i štampamo i i postavljamo vrednost n
na n/i. Specijalan slučaj čini situacija kada je n prost broj, u kom slučaju je
potrebno samo odštampati sam taj broj.
// funkcija koja ispisuje sve proste cinioce
// datog broja n
void ispisiProsteCinioce(int n){

// ako je n parno, stampamo 2 onoliki broj puta koliko puta deli n


while (n % 2 == 0){
cout << 2 << " ";
// azuriramo n
n /= 2;

4
}

// n je neparno
// prolazimo kroz neparne brojeve
for (int i = 3; i <= sqrt(n); i = i + 2){
// sve dok i deli n, stampamo i
while (n % i == 0){
cout << i << " ";
// azuriramo n
n /= i;
}
}

// ako je n prost broj veci od 2


// onda samo stampamo n
if (n > 2)
cout << n << " ";

cout << endl;


}

int main(){

int n;
cout << "Unesite broj n" << endl;
cin >> n;
ispisiProsteCinioce(n);
return 0;
}
Istaknimo jednu važnu stvar: naime, nigde u programu se posebno ne određuju
prosti brojevi. Na prvi pogled ovo može delovati zbunjujuće, međutim redosled
kojim tražimo proste faktore nam garantuje da nikada nećemo odštampati neki
složeni broj s = i · j, i, j < s jer ćemo pre ispitivanja deljivosti sa s proveravati
deljivost sa i i j, s obzirom da je njihova vrednost manja od s.

Složenost prikazanog algoritma je u najgorem slučaju O( n) – on se dešava kada
je n = p2 , gde je p neki prost broj ili kada je sam broj n prost. Primetimo da će
se za složene brojeve granica for petlje smanjivati sa svakim novim pronađenim
činiocem (jer će nova vrednost za n biti √ strogo manja od stare), te će broj
izvršavanja petlje uglavnom biti manji od n.
Ukoliko je potrebno veliki broj puta izvršiti faktorizaciju brojeva, npr. O(n)
puta, onda je potrebno O(n) √
puta izvršiti ispočetka ovaj algoritam, te će ukupna
složenost algoritma biti O(n n).
Faktorizaciju brojeva možemo izvršiti i korišćenjem Eratostenovog sita. Naime,
umesto da kao u originalnom algoritmu prilikom prolaska kroz umnoške nekog

5
prostog broja označavamo da su ti brojevi složeni, čuvaćemo vrednost najmanjeg
prostog činioca tog broja. Nakon toga izračunaćemo činioce broja n uzastopnim
deljenjem broja n njegovim najmanjim prostim činiocem.
const int MAX = 1000;

// niz u kome cemo cuvati najmanji cinilac za svaki broj


vector<int> najmanjiCinilac(MAX);

void eratosten(){

// postavljamo da je najmanji cinilac svakog broja sam taj broj


for (int i = 1; i < MAX; i++)
najmanjiCinilac[i] = i;

for (int d = 2; d*d <= MAX; d++)


// ako je broj prost, tj njegov najmanji delilac je on sam
if (najmanjiCinilac[d] == d)
for (int i = d+d; i <= MAX; i += d)
// ako nije ranije azurirana vrednost, odnosno
// postavljena neka manja vrednost za cinilac
if (najmanjiCinilac[i] == i)
// postavljamo da je najmanji cinilac broj d
najmanjiCinilac[i] = d;
}

// funkcija koja ispisuje sve proste cinioce


// datog broja n
void ispisiProsteCinioce(int n){

while (n != 1){
// stampamo najmanji cinilac broja n
cout << najmanjiCinilac[n] << " ";
// azuriramo vrednost n
n /= najmanjiCinilac[n];
}
cout << endl;
}

int main(){

int n;
cout << "Unesite broj n" << endl;
cin >> n;
eratosten();
ispisiProsteCinioce(n);

6
return 0;
}
Složenost prvog koraka algoritma – određivanja najmanjeg prostog činioca svih
brojeva do n jednaka je složenosti Eratostenovog sita O(n log log n), a ispisivanja
činioca datog broja je O(log n) jer je maksimalni broj činilaca broja n jednak
log2 n. Dakle, ukoliko je potrebno izvršiti faktorizaciju na proste činioce O(n)
puta onda će složenost ovog algoritma biti O(n log log n)+O(n log n) = O(n log n)
što je efikasnije od prethodnog pristupa.
Za veliko n prikazani algoritam nije dovoljno efikasan. Važi i sledeće: nije
jednako teško faktorisati sve brojeve jedne iste dužine. Najteže instance ovih
problema čine brojevi koji su proizvodi dva prosta broja. Kada su oba ova
prosta činioca velika i slične veličine, ovaj problem postaje jako teško rešiti. 2009.
godine okončao se dvogodišnji napor grupe istraživača da faktoriše broj od 232
cifre korišćenjem više stotina računa. Međutim, do danas nije ni dokazano da ne
postoji neki efikasan algoritam koji bi rešavao ovaj problem. Pretpostavljena
težina ovog problema čini srž velikog broja kriptografskih algoritama, kao što je
RSA.

Ojlerova funkcija

Ojlerova funkcija φ broja n označava broj prirodnih brojeva manjih ili jednakih
n koji su uzajamno prosti sa n. Na primer, φ(9) = 6 jer postoji šest brojeva
1, 2, 4, 5, 7, 8 manjih od 9 koji su uzajamno prosti sa 9.
Problem: Za dati prirodan broj n izračunati vrednost Ojlerove funkcije φ(n).
Direktan način da se reši problem bio bi da se redom prođe kroz sve brojeve od
1 do n − 1 i da se izbroji koliko je brojeva od njih uzajamno prosto sa n.
// funkcija koja racuna najveci zajednicki delilac dva broja
int nzd(int a, int b){
if (a == 0)
return b;
return nzd(b % a, a);
}

// funkcija koja racuna vrednost Ojlerove funkcije


int ojlerovaFunkcija(int n){
// 1 je uvek uzajamno prosto sa n
int br = 1;
for (int i = 2; i < n; i++)
if (nzd(i, n) == 1)
br++;
return br;
}

7
int main(){

int n;
cout << "Unesite n" << endl;
cin >> n;
cout << "fi(" << n << ")=" << ojlerovaFunkcija(n) << endl;
return 0;
}
Prilikom računanja vrednosti Ojlerove funkcije, funkcija za određivanje najvećeg
zajedničkog delioca se poziva O(n) puta. Kako znamo da je složenost određivanja
najvećeg zajedničkog delioca brojeva i i n jednaka O(log(i + n)) i kako je ovde
i < n, važi da je složenost računanja vrednosti nzd(i, n) jednaka O(log n), te je
ukupna složenost algoritma O(n log n).
Ojlerova funkcija ima primenu u teoriji brojeva, u algebri, a takođe igra ključnu
ulogu u definiciji RSA sistema za šifrovanje.
Razmotrimo sada efikasnije rešenje ovog problema. Iz same definicije Ojlerove
funkcije važi da je φ(1) = 1. Takođe, ako je n = pk , gde je p neki prost broj
onda sa njim nisu uzajamno prosti samo umnošci broja p, odnosno brojevi
p, 2p, 3p, pk−1 p kojih ima ukupno pk−1 . Dakle, važi
1
φ(n) = pk − pk−1 = pk−1 (p − 1) = pk (1 − )
p

Važi i naredno tvrđenje koje navodimo bez dokaza: φ(ab) = φ(a)φ(b) ako
su brojevi a i b uzajamno prosti. Funkcije za koje važi ovo svojstvo nazivamo
multiplikativne funkcije. Različite funkcije u teoriji brojeva su multiplikativne, što
omogućava da se na osnovu njihovih vrednosti za proste brojeve lako izračunaju
njihove proizvoljne vrednosti, što u kombinaciji sa efikasnim razlaganjem na
proste činioce daje efikasne algoritme.
Na osnovu prethodna dva tvrđenja, ako n u opštem slučaju predstavimo kao
n = pk11 · · · pkr r gde su p1 , p2 , . . . , pr prosti činioci broja n važi:

1 k2 1 1
φ(n) = φ(pk11 )φ(pk22 ) · · · φ(pkr r ) = pk11 (1 − )p2 (1 − ) · · · pkr r (1 − )
p1 p2 pr
1 1 1 Y 1
= pk11 pk22 · · · pkr r (1 − )(1 − ) · · · (1 − ) = n (1 − )
p1 p2 pr p
p|n

Dakle, na primer φ(36) = φ(22 · 32 ) = 36(1 − 21 )(1 − 13 ) = 12


// funkcija koja racuna vrednost Ojlerove funkcije
int ojlerovaFunkcija(int n){

8
// rezultat inicijalizujemo na n
int br = n;

// za svaki prost cinilac p broja n


// rezultat mnozimo sa 1-1/p = (p-1)/p
// usput azuriramo vrednost broja n
for (int p = 2; p*p <= n; p++){

if (n % p == 0) {
while (n % p == 0)
n /= p;
br = br*(p-1)/p;
}
}

// ako je n prost broj


if (n > 1)
br = br*(n-1)/n;

return br;
}
2
√ slučaja (kada je n = p , za neko prosto p ili ako je sam broj
Složenost najgoreg
n prost) je O( n).
p
S obzirom na to da važi br · p−1 = br · (1 − p1 ) = br − br
p , možemo izbeći množenje
u prethodnoj implementaciji tako što ćemo od n oduzeti broj umnožaka svakog
prostog činioca.
int ojlerovaFunkcija(int n){

// rezultat inicijalizujemo na n
int br = n;

// za svaki prost cinilac p oduzimamo od br broj njegovih umnozaka


for (int p = 2; p*p <= n; p++){

if (n % p == 0){
while (n % p == 0)
n /= p;
// od br oduzimamo broj umnozaka cinioca p
br -= br / p;
}
}

// ako je n prost

9
if (n > 1)
br -= br / n;

return br;
}
Ukoliko bi zadatak bio da se izračuna vrednost Ojlerove funkcije
√ za sve vrednosti
manje ili jednake n, ova implementacija bi bila složenosti O(n n). Kao i ranije,
razmotrimo varijantu algoritma zasnovanu na Eratostenovom situ. Inicijalizo-
vaćemo sve vrednosti φ(i), 1 ≤ i ≤ n na i. Razmatraćemo redom vrednosti za
p počev od 2 do n i ukoliko je φ(p) = p, to znači da je broj p prost i vrednosti
Ojlerove funkcije svih umnožaka broja p pomnožićemo sa 1 − p1 .
void ispisiVrednostiOjleroveFunkcijeDoN(int n){

vector<int> ojlerovaFunkcija(n+1);

// inicijalizujemo sve vrednosti


for (int i = 1; i <= n; i++)
ojlerovaFunkcija[i] = i;

// za sve vrednosti od 2 do n
for (int p = 2; p <= n; p++){

// ako vrednost nije menjana, to znaci da je p prosto


if (ojlerovaFunkcija[p] == p){

// postavljamo vrednost Ojlerove funkcije za prost broj p


ojlerovaFunkcija[p] = p-1;

// azuriramo vrednosti Ojlerove funkcije


// za sve umnoske broja p
for (int i = 2*p; i <= n; i += p){
ojlerovaFunkcija[i] = (ojlerovaFunkcija[i]/p) * (p-1);
}
}
}

// stampamo dobijene vrednosti Ojlerove funkcije


for (int i=1; i<=n; i++)
cout << "fi(" << i << ")=" << ojlerovaFunkcija[i] << endl;
}

int main(){

int n;
cout << "Unesite n" << endl;

10
cin >> n;
ispisiVrednostiOjleroveFunkcijeDoN(n);
return 0;
}
Složenost ovog algoritma odgovara složenosti Eratostenovog sita, odnosno jednaka
je O(n log log n).
Jedno važno svojstvo Ojlerove funkcije jeste Ojlerova teorema.
Ojlerova teorema: Ako je n ≥ 1 i a je ceo broj uzajamno prost sa n, onda važi

aφ(n) ≡ 1 (mod n)

Ova teorema predstavlja uopštenje Male Fermaove teoreme.


Mala Fermaova teorema: Ako je p prost broj, onda za svaki ceo broj a važi

ap ≡ a (mod p)

Pretpostavimo da je n jednako proizvodu dva različita prosta broja p i q. Problem


faktorizacije broja n je težak, međutim ako je pored n poznata i vrednost φ(n),
onda se p i q mogu jednostavno odrediti. Naime važi φ(n) = (p − 1) · (q − 1).
Odavde dobijamo da je n − φ(n) + 1 = p + q. Na osnovu vrednosti p + q i p · q
lako se mogu odrediti brojevi p i q. Ovo je važna stvar u razmatranju sigurnosti
RSA enkripcije. Naime, jačina metoda enkripcije se zasniva na tajnosti vrednosti
φ(n), a kao što smo prethodno videli računanje vrednosti Ojlerove funkcije je
ekvivalentno faktorizaciji broja, što se smatra teškim problemom.

Broj i zbir delilaca

Problem: Za dati broj n izračunati ukupan broj njegovih delilaca.


Na primer, ako je n = 24, njegovi delioci su 1, 2, 3, 4, 6, 8, 12, 24 te je tražena
vrednost 8.
Naivni pristup bi prolazio skupom svih brojeva od 1 do n i za svaku vrednost
koja deli n inkrementirao bi ukupni broj delilaca.
// funkcija koja racuna broj delilaca broja n
int brojDelilaca(int n){
// broj delilaca je bar 1 -- sam taj broj
int br = 1;
for (int i = 1; i < n; i++)
if (n % i == 0)
br++;
return br;
}

11
Primetimo da se delioci uglavnom javljaju u paru: u prethodnom primeru imamo
da je 24 = 1 · 24 = 2 · 12 = 3 · 8 = 4 · 6. Ovo ne važi samo u slučaju kvadrata nekog
broja, npr. delioci broja 16 su 1, 2, 4, 8, 16 gde srednji delilac nema svog para.
Nešto
√ efikasniji algoritam bi prolazio skupom svih brojeva manjih ili jednakih
n i za svaki broj d koji deli broj n inkrementirao bi ukupan broj delilaca za 2,
ako je d 6= nd , a inače za 1.
// funkcija koja racuna broj delilaca broja n
int brojDelilaca(int n){
int br = 0;
for (int i = 1; i <= sqrt(n); i++)
if (n % i == 0)
if (i != sqrt(n))
br += 2;
else
br++;
return br;
}

Složenost ovakvog algoritma bila bi O( n). Ukoliko bi bilo potrebno da√ovaj
postupak sprovedemo O(n) puta, ukupna složenost algoritma bila bi O(n n).
Neka je n = pk11 · · · pkr r , gde su p1 , p2 , . . . , pr svi prosti činioci broja n. Prosti
činioci svakog delioca broja n moraju biti iz skupa p1 , p2 , . . . , pr , te je opšti oblik
svakog delioca broja n jednak d = pl11 · · · plrr , gde važi 0 ≤ li ≤ ki , odnosno
postoji ki + 1 mogućnosti za eksponent činioca pi . Dakle, važi da je ukupan broj
njegovih delilaca jednak (k1 + 1)(k2 + 1) · · · (kr + 1).
Dakle ako bismo Eratostenovim sitom označili sve proste brojeve do neke vred-
nosti, onda bismo mogli da prolaskom kroz skup tih prostih brojeva proveravamo
da li dele n i ako ga dele k puta da ukupan broj pomnožimo sa k + 1.
void eratosten(vector<int> & prost){

int n = prost.size();
prost[0] = prost[1] = false;
// odredjujemo proste brojeve Eratostenovim sitom
for (int d = 2; d*d < n; d++)
if (prost[d])
for (int j = d+d; j<n; j+=d)
prost[j] = false;
}

// funkcija koja racuna broj delilaca broja n


int brojDelilaca(vector<int> prost, int n){

// 1 je uvek delilac broja n


int brDelilaca = 1;

12
// za sve vrednosti od 2 do n
for (int p = 2; p <= n; p++){
if (prost[p]){
int br = 0;
if (n % p == 0){
while (n % p == 0){
n = n/p;
br++;
}
brDelilaca = brDelilaca * (br + 1);
}
}
}
return brDelilaca;
}

int main(){

int n;
cout << "Unesi n" << endl;
cin >> n;

vector<int> prost(n+1,true);
eratosten(prost);

for (int i=0; i<n; i++)


cout << "Broj delilaca broja " << i << " je " << brojDelilaca(prost,i) << endl;
return 0;
}
Problem: Za dati broj n izračunati sumu svih njegovih delilaca.
Na primer, ako je n = 24, njegovi delioci su 1, 2, 3, 4, 6, 8, 12, 24 te je tražena
vrednost 60.
Naivni pristup bi prolazio skupom svih brojeva manjih od n i za svaki broj koji
je delilac broja n uvećavala bi se suma delilaca za taj broj.
// funkcija koja racuna sumu delilaca broja n
int sumaDelilaca(int n){
// n je uvek sam sebi delilac
int suma = n;
for (int i = 1; i < n; i++)
if (n % i == 0)
suma += i;
return suma;
}

13
Malo efikasniji pristup bi delioce√otkrivao u paru, tako što bi prolazio skupom
svih brojeva manjih ili jednakih n i za svaki broj i koji deli broj n vrednost i i
vrednost njemu odgovarajućeg činioca √ n/i bi dodavao na ukupnu sumu, osim
kada je i = n/i, odnosno kada je i = n.
// funkcija koja racuna sumu delilaca broja n
int sumaDelilaca(int n){
int suma = 0;
for (int i = 1; i <= sqrt(n); i++)
if (n % i == 0)
if (i != sqrt(n))
suma += (i+n/i);
else
suma += i;
return suma;
}
Drugo rešenje istog problema bi se zasnivalo na sledećem razmatranju: neka
je n = pk11 · · · pkr r , gde su p1 , p2 , . . . , pr svi prosti činioci broja n. Opšti oblik
delioca broja n jednak je d = pl11 · · · plrr , gde važi 0 ≤ li ≤ ki , odnosno postoji
ki + 1 mogućnosti za eksponent činioca pi . Dakle važi da je suma svih delioca
jednaka:

S = (1 + p1 + p21 + . . . + pk11 ) · (1 + p2 + p22 + . . . + pk22 ) · · · (1 + pr + p2r + . . . + pkr r )

Možemo primetiti da se gornji izraz može srediti računanjem zbirova odgovara-


jućih geometrijskih progresija:

pk11 +1 − 1 p2k2 +1 − 1 pkr +1 − 1


S= · ··· r
p1 − 1 p2 − 1 pr − 1

Dakle, problem se svodi na identifikovanje prostih činalaca broja, korišćenjem


Eratostenovog sita.
int sumaDelilaca(vector<int> prost, int n){

int sumaDelilaca = 1;
// za sve vrednosti od 2 do n
for (int p=2; p<=n; p++){
if (prost[p]){
int br = 0;
if (n % p == 0){
while (n % p == 0){
n = n/p;
br++;

14
}
sumaDelilaca = sumaDelilaca * (pow(p,br+1) - 1)/(p-1);
}
}
}
return sumaDelilaca;
}
Iza ovih efikasnijih rešenja krije se to da su ove dve funkcije: broj delilaca i suma
delilaca multiplikativne, odnosno važi da je f (a · b) = f (a) · f (b) za uzajamno
proste brojeve a i b. Preciznije, σk (n) je funkcija delilaca i označava sumu
k-tih stepena svih pozitivnih delilaca broja n i za nju važi da je multiplikativna.
Specijalno, σ0 (n) označava broj delilaca, a σ1 (n) sumu delilaca broja n. Dakle,
ako sa B(n) označimo broj delilaca broja n, a sa S(n) sumu njegovih delilaca, onda
za uzajamno proste brojeve x i y važi B(x·y) = B(x)·B(y) i S(x·y) = S(x)·S(y)
(svaki činilac broja x može se iskombinovati sa svakim činiocem broja y i na taj
način dobijamo sve moguće činioce broja xy).

Prošireni Euklidov algoritam

Podsetimo se ideje osnovnog Euklidovog algoritma za određivanje najvećeg


zajedničkog delioca brojeva a i b: polazeći od brojeva r0 = a i r1 = b, izračunava
se ostatak r2 = r0 mod r1 , zatim ostatak r3 = r1 mod r2 , itd. Tako se dobija
opadajući niz ostataka

ri−1 = qi ri + ri+1 , 0 ≤ ri+1 < ri , za i = 1, 2, . . . , k (1)

(pri deljenju ri−1 sa ri količnik je qi , a ostatak ri+1 ). Dobijeni niz je konačan jer
je opadajući i sastoji se od prirodnih brojeva. Neka je npr. rk+1 = 0 i neka je
rk 6= 0 poslednji član ovog niza različit od nule. Kako je
nzd(r0 , r1 ) = nzd(r1 , r2 ) = . . . = nzd(rk−1 , rk ) = nzd(rk , 0) = rk ,
vidimo da je d = nzd(a, b) upravo jednako rk , poslednjem ostatku različitom od
nule.
Uz malu dopunu, Euklidov algoritam se može iskoristiti i za rešavanje sledećeg
problema.
Problem: Najveći zajednički delilac d dva prirodna broja a i b izraziti kao
njihovu celobrojnu linearnu kombinaciju. Drugim rečima, odrediti cele brojeve x
i y, tako da važi d = nzd(a, b) = x · a + y · b.
Cilj je d izraziti u obliku celobrojne linearne kombinacije d = x · r0 + y · r1
ostataka r0 = a i r1 = b. Problem se može rešiti indukcijom. Polazi se od baze,
izraza za d u obliku linearne kombinacije ostataka rk−1 i rk−2 :
d = rk = rk−2 − qk−1 rk−1

15
Ovaj izraz je ekvivalentan pretposlednjem deljenju u Euklidovom algoritmu
(izraz (1) za i = k − 1).
Korak indukcije bio bi da se polazeći od izraza d = x0 ri + y 0 ri+1 kao celobrojne
linearne kombinacije ostataka ri i ri+1 , broj d izrazi u obliku celobrojne linearne
kombinacije ostataka ri−1 i ri , odnosno kao d = x00 ri−1 + y 00 ri . Zaista, zamenom
ri+1 u prethodnom izrazu sa ri−1 − qi ri , dobija se

d = x0 ri + y 0 ri+1 = x0 ri + y 0 (ri−1 − qi ri ) = y 0 ri−1 + (x0 − qi y 0 )ri , (2)

odnosno:

x00 = y0
y 00 = x0 − qi y 0 (3)

tj. dobija se izraz za d u obliku celobrojne linearne kombinacije ostataka ri−1


i ri . Dakle, indukcijom po i, i = k − 2, k − 1, . . . , 1 dokazano je da se d može
izraziti kao celobrojna linearna kombinacija bilo koja dva uzastopna člana ri ,
ri+1 niza ostataka. Specijalno, za i = 0, dobija se traženi izraz. Ova verzija
Euklidovog algoritma naziva se prošireni Euklidov algoritam.
Primer: Odrediti cele brojeve x i y tako da važi 3 = 33x + 24y. S obzirom na
to da je nzd(33, 24) = 3 možemo iskoristiti prethodni postupak za određivanje
brojeva x i y. Prilikom izračunavanja najvećeg zajedničkog delioca imamo naredni
niz jednakosti: nzd(33, 24) = nzd(24, 9) = nzd(9, 6) = nzd(6, 3) = nzd(3, 0).
Prateći ovaj niz jednakosti dobijamo: d = 3 = 9 − 6 = 9 − (24 − 2 · 9) = 3 · 9 − 24 =
3 · (33 − 24) − 24 = 3 · 33 − 4 · 24, odnosno x = 3, y = −4.
Rekurzivna verzija ovog algoritma bila bi sledeća:
int nzdProsireni(int a, int b, int &x, int &y){

// ako je b jednako 0, onda je nzd(a,b) = a


if (b == 0){
x = 1;
y = 0;
return a;
}

int x1, y1;


// rekurzivno resavamo problem za brojeve b i a%b
int nzd = nzdProsireni(b, a%b, x1, y1);
// na osnovu (3) vazi naredna veza
x = y1;
y = x1 - (a/b) * y1;

16
return nzd;
}

int main(){

int a, b;
int x, y;
cout << "Unesite brojeve a i b" << endl;
cin >> a >> b;
int d = nzdProsireni(a, b, x, y);
cout << x << "*" << a << " + " << y << "*" << b
<< " = " << d << endl;
return 0;
}
Možemo napisati i odgovarajuću iterativnu verziju algoritma. Za r0 = n važi
da je x = 1 i y = 0, a za r1 = m važi x = 0 i y = 1. Želimo da ri+1
predstavimo kao celobrojnu linearnu kombinaciju brojeva a i b, ako znamo da
važi ri−1 = xi−1 ·a+yi−1 ·b i ri = xi ·a+yi ·b. Na osnovu veze ri+1 = ri−1 −qi ·ri
dobijamo:

ri+1 = xi−1 · a + yi−1 · b − qi (xi · a + yi · b) = (xi−1 − qi xi )a + (yi−1 − qi yi )b

Dakle vrednosti xi+1 i yi+1 možemo izračunati na osnovu prethodnih vrednosti


xi−1 i xi , odnosno yi−1 i yi .
int nzdProsireni(int a, int b, int &x, int &y){

// vrednost x i y za r_0 = n
int x_preth = 1;
int y_preth = 0;
// vrednost x i y za r_1 = m
int x_tek = 0;
int y_tek = 1;

while (b != 0){

// azuriramo vrednosti za a i b
// kao u standardnom Euklidovom algoritmu
int q = a/b;
int r = a - q*b;
a = b;
b = r;

int xpom = x_preth - q*x_tek;


x_preth = x_tek;

17
x_tek = xpom;

int ypom = y_preth - q*y_tek;


y_preth = y_tek;
y_tek = ypom;
}

// postavljamo konacne vrednosti za x i y


// kao vrednosti x i y za r_k
x = x_preth;
y = y_preth;

// vracamo a kao nzd brojeva


return a;
}

int main(){

int a, b;
int x, y;
cout << "Unesite brojeve a i b" << endl;
cin >> a >> b;
int d = nzdProsireni(a, b, x, y);
cout << x << "*" << a << " + " << y << "*" << b
<< " = " << d << endl;
return 0;
}
Broj operacija je proporcionalan sa brojem operacija u Euklidovom algoritmu,
tj. jednak je O(log(a + b)).
Značaj proširenog Euklidovog algoritma je u tome što omogućuje rešavanja
jedne klase Diofantovih jednačina, tzv. linearnih Diofantovih jednačina koje
su oblika a · x + b · y = c, gde su a, b, c dati celi brojevi, a x i y su celi
brojevi koje treba odrediti. Bez smanjenja opštosti može se pretpostaviti da
je a, b > 0. Da bi navedena jednačina imala bar jedno rešenje, potrebno je
da d = nzd(a, b) deli c (pošto d deli levu stranu jednačine, mora da deli i
desnu). Ako je ovaj uslov ispunjen, jedno od rešenja lako se dobija opisanim
postupkom. Pošto se d izrazi u obliku d = ax0 + by 0 , množenjem sa celim brojem
c/d dobija se c = a(x0 c/d) + b(y 0 c/d), tj. vidi se da je jedno rešenje jednačine
par (x, y) = (x0 c/d, y 0 c/d).

Modularni multiplikativni inverz

Multiplikativni inverz broja a po modulu m je broj x za koji važi a·x ≡ 1 (mod m).
Na primer, multiplikativni inverz broja 3 po modulu 11 je 4 jer važi 3 · 4 ≡

18
1 (mod 11). Multiplikativni inverz po modulu m mora biti iz skupa vrednosti
{0, 1, 2 . . . , m − 1}.
Postavlja se pitanje da li modularni inverz uvek postoji. Pokazuje se da on
postoji ako i samo ako su brojevi a i m uzajamno prosti i tada je jedinstven.
Problem: Za data dva cela broja a i m odrediti multiplikativni inverz broja a
po modulu m.
Naivni pristup rešavanju ovog problema bi za x razmatrao redom brojeve od 1
do m i proveravao da li je ostatak pri deljenju broja a · x sa m jednak 1.
// multiplikativni inverz broja a po modulu m
int modInverz(int a, int m){

a = a % m;
// proveravamo redom kandidate od 1 do m-1
for (int x = 1; x < m; x++)
if ((a*x) % m == 1)
return x;
}

int main(){

int a, m;
cout << "Unesite broj a i broj m" << endl;
cin >> a >> m;
cout << "Modularni multiplikativni inverz broja " << a << " je "
<< modInverz(a, m) << endl;
return 0;
}
Složenost ove implementacije je O(m).
Da bismo dobili efikasniji algoritam koji radi u slučaju kada su brojevi a i
m uzajamno prosti, možemo iskoristiti prošireni Euklidov algoritam. Naime,
prošireni Euklidov algoritam određuje najveći zajednički delilac d i brojeve x i y
takve da za date brojeve a i b važi:

a·x+b·y =d

Da bismo pronašli multiplikativni inverz broja a po modulu m, u gornjoj jednačini


umesto b uvrstićemo m. Pretpostavka je da su brojevi a i m uzajamno prosti te
je d = 1. Dakle važi jednakost:

a·x+m·y =1

odnosno važi i:

19
a · x + m · y ≡ 1 (mod m)

Drugi sabirak na levoj strani jednačine je deljiv sa m pa ga možemo ukloniti, te


dobijamo:

a · x ≡ 1 (mod m)

odakle sledi da je multiplikativni inverz broja a po modulu m jednak (x mod m+


m) mod m.
Dakle, određivanje modularnog multiplikativnog inverza možemo svesti na
prošireni Euklidov algoritam.
// multiplikativni inverz broja a po modulu m
int modInverz(int a, int m){

int x, y;
// odredjujemo x i y tako da vazi a*x+m*y=d
int d = nzdProsireni(a,m,x,y);

// ako a i m nisu uzajamno prosti, onda ne postoji inverz


if (d != 1)
cout << "Inverz ne postoji" << endl;
else{
// m dodajemo da bismo obradili potencijalno negativno x
int inverz = (x % m + m) % m;
return inverz;
}
}
Možemo konstruisati algoritam koji ne poziva eksplicitno funkciju za rešavanje
proširenog Euklidovog algoritma, već, pošto nama koeficijent y nije od interesa,
možemo na isti način kao i kod proširenog Euklidovog algoritma iterativno
računati samo koeficijent x.
// x je multiplikativni inverz broja a po modulu m,
// tj. broj takav da je (a * x) mod m = 1
// funkcija vraca da li je uspela da izracuna broj x
bool modInverz(int a, int m, int& x) {

int r = m;
int r1 = a;
x = 0;
int x1 = 1;

while (r1 > 0) {

20
int q = r / r1;
int tmp;

tmp = r;
r = r1;
r1 = tmp - q*r1;

tmp = x;
x = x1;
x1 = tmp - q*x1;
}

// x postavljamo na pozitivnu vrednost


if (x < 0)
x += m;

// vracamo true ako je nzd(a,m)=1, inace false


return r == 1;
}
Modularni multiplikativni inverz ima primenu u oblasti kriptografije, npr. u
RSA algoritmu.
RSA algoritam je jedan od prvih sistema šifrovanja koji se zasniva na javnom
ključu. U ovakvim sistemima ključ koji se koristi za kodiranje je javni i on se
razlikuje od ključa koji se koristi za dekodiranje koji je tajni. Razmotrimo kako
izgleda kodiranje i dekodiranje u RSA algoritmu.
1. Izaberemo dva različita prosta broja, p i q.
2. Izračunamo n kao n = p · q
3. Izračunamo k = φ(n) = (p − 1) · (q − 1)
4. Biramo vrednost e tako da je uzajamno prosta sa k
5. Odredimo d kao multiplikativni inverz broja e po modulu k, odnosno tako
da važi e · d ≡ 1 (mod k)
6. Brojevi n i e su javni, a broj d je privatni
7. Poruka m koja se predstavlja celim brojem manjim od n se kodira raču-
nanjem S = me (mod n)
8. Poruka se dekodira računanjem t = S d (mod n)
9. Prema Ojlerovoj teoremi i kineskoj teoremi o ostacima važi da je t = m
(ne navodimo dokaz)
Sigurnost sistema RSA bi se narušila ako bi se broj n mogao efikasno faktorisati
ili ako bi se φ(n) moglo odrediti bez faktorizacije broja n

21
Kineska teorema o ostacima

Prema jednoj kineskoj legendi, general Han Xin je da bi izbegao da špijuni saznaju
sa kolikom je vojskom krenuo u boj, svom kuriru je davao samo informaciju
o ostatku broja vojnika u pravougaonikoj formaciji. Njegove poruke su bile u
narednoj formi: “Kada se vojnici organizuju u redove od po 9, preostaje njih
4; ako su u redovima od po 11, niko ne preostaje; ako su u redovima od po 13,
samo jedan preostaje, a ako su u redovima od po 19, preostaje njih trojica”.
General je takođe svom kuriru preneo da je broj vojnika drugo najmanje rešenje
ovog problema. Pitanje je kolikim brojem vojnika je general zapovedao? Ovaj
problem odgovara tzv. Kineskoj teoremi o ostacima, jednom od standardnih
problema iz teorije brojeva. Formulišimo ovaj problem u standardnim terminima.
Problem: Data su dva niza brojeva r: r0 , r1 , . . . , rn−1 i m: m0 , m1 , . . . , mn−1
pri čemu za svaki par brojeva niza m važi da su uzajamno prosti. Odrediti
najmanji pozitivan broj x za koji važi:

x mod m0 = r0
x mod m1 = r1
...
x mod mn−1 = rn−1 (4)

Pokazuje se da uvek postoji x koje zadovoljava dati skup jednačina i da je ono


jedinstveno po modulu m0 · m1 · . . . · mn−1 .
Naivni pristup bi krenuo redom od 1 po skupu prirodnih brojeva i proveravao
da li tekući broj zadovoljava date uslove.
int izracunajMinX(vector<int> m, vector<int> r){

int n = m.size();
// inicijalizujemo vrednost x
int x = 1;

// prema tvrdjenju Kineske teoreme o ostacima


// ova petlja ce se uvek zaustaviti
while (true){

int j;
// za svako i od 0 do n-1
// proveravamo da li je ostatak pri deljenju sa m_j jednak r_j
for (j = 0; j < n; j++)
if (x % m[j] != r[j])
break;

22
// ako su sve iteracije prosle uspesno
// nasli smo vrednost za x
if (j == n)
return x;

// inace nastavljamo sa pretragom


x++;
}
return x;
}

int main(void){

vector<int> m = {3, 4, 5};


vector<int> r = {2, 3, 1};

// m i r moraju biti istih dimenzija


if (m.size() != r.size()){
cout << "Broj modula mora biti jednak broju ostataka" << endl;
}
else
cout << "Najmanje x koje zadovoljava dati sistem kongruencija je "
<< izracunajMinX(m, r) << endl;
return 0;
}
Efikasniji algoritam je zasnovan na narednom razmatranju.
Neka je M = m0 · m1 · . . . · mn−1 . Ako umemo da odredimo cele brojeve
w0 , w1 , . . . , wn−1 takve da za svako i važi da wi pri deljenju sa mi daje ostatak
1, dok pri deljenju sa svim drugim brojevima mj , j 6= i daje ostatak 0, onda se
x može konstruisati kao x = r0 · w0 + . . . + rn−1 · wn−1 (mod M ).
| vrednost | vrednost | ... | vrednost
| po modulu m_0 | po modulu m_1 | ... | po modulu m_{n-1}
----------------------------------------------------------------
w_0 | 1 | 0 | ... | 0
----------------------------------------------------------------
w_1 | 0 | 1 | ... | 0
----------------------------------------------------------------
... | ...
----------------------------------------------------------------
w_{n-1} | 0 | 0 | ... | 1
----------------------------------------------------------------
Označimo sa:

23
M M M
z0 = , z1 = , . . . , zn−1 =
m0 m1 mn−1

Za svako 0 ≤ i < n važi sledeće:


1. zi ≡ 0 (mod mj ) za j 6= i
2. zi i mi su uzajamno prosti brojevi
Ako je broj wi deljiv svim brojevima mj osim eventualno sa mi , onda je on
deljiv i sa zi , odnosno mora da predstavlja neki njegov umnožak.
Uvedimo dalje naredne oznake:

y0 ≡ z0−1 (mod m0 )
y1 ≡ z1−1 (mod m1 )
...
−1
yn−1 ≡ zn−1 (mod mn−1 )

Ovi modularni multiplikativni inverzi postoje jer važi da su brojevi zi i mi uza-


jamno prosti, i možemo ih odrediti proširenim Euklidovim algoritmom. Prime-
timo da važi:
1. yi · zi ≡ 0 (mod mj ) za j 6= i
2. yi · zi ≡ 1 (mod mi )
Ova svojstva nam omogućavaju da definišemo brojeve w0 , w1 , . . . , wn−1 koje
zadovoljavaju zahteve iz prethodne tabele na sledeći način:

w0 = y0 · z0 mod M
w1 = y1 · z1 mod M
...
wn−1 = yn−1 · zn−1 mod M

Proverimo da li će zaista ovako definisano x = r0 · w0 + . . . + rn−1 · wn−1 (mod M )


davati ostatak ri pri deljenju sa mi .

x ≡ r0 y0 z0 + r1 y1 z1 + . . . + rn−1 yn−1 zn−1 (mod M ) (mod mi )


≡ r0 y0 z0 + r1 y1 z1 + . . . + rn−1 yn−1 zn−1 (mod mi )
≡ ri yi zi (mod mi )
≡ ri (mod mi ) (5)

24
Druga jednačina važi na osnovu toga što je M mod mi = 0, treća na osnovu
toga što je yi · zi ≡ 0 (mod mj ) za j 6= i, a četvrta na osnovu yi · zi ≡ 1 (mod mi ).
Pretpostavimo da postoje dva različita rešenja u i v. Tada važi

m0 |(u − v), m1 |(u − v), . . . , mn−1 |(u − v)

S obzirom da su svi m0 , m1 , . . . , mn−1 uzajamno prosti, važi i da m0 m1 · . . . ·


mn−1 |(u − v), odnosno važi da je u ≡ v (mod m0 m1 · . . . · mn−1 ), odnosno rešenje
je jedinstveno po modulu m0 m1 · . . . · mn−1 .
// proizvod brojeva x i y po modulu m
long long pm(long long x, long long y, long long m) {
return ((x % m) * (y % m)) % m;
}

// proizvod brojeva x, y i z po modulu m


long long pm(long long x, long long y, long long z, long long m) {
return pm(pm(x, y, m), z, m);
}

// zbir brojeva x i y po modulu m


long long zm(long long x, long long y, long long m) {
return ((x % m) + (y % m)) % m;
}

// na osnovu kineske teoreme o ostacima se izracunava rezultat tako da


// za sve elemente nizova m i a vazi da je rezultat mod m[i] = r[i]
// funkcija vraca da li je rezultat bilo moguce naci
bool kto(long long m[], long long r[], int n, long long& rezultat) {

long long M = 1;
// racunamo proizvod svih modula
for (int i = 0; i < n; i++)
M *= m[i];

rezultat = 0;
for (int i = 0; i < n; i++) {
long long zi = M / m[i];
long long zi_inv;
if (!modInverz(zi, m[i], zi_inv))
return false;
rezultat = zm(rezultat, pm(r[i], zi_inv, zi, M), M);
}
return true;
}

25
int main(){
// ucitavamo ulazne podatke
long long r[3], m[3];
for (int i = 0; i < 3; i++)
cin >> r[i] >> m[i];

// rezultat izracunavamo na osnovu kineske teoreme o ostacima


long long rezultat;
kto(m, r, 3, rezultat);

// ispisujemo rezultat
cout << rezultat << endl;

return 0;
}
Postoji još jedan postupak manje matematički zahtevan i manje efikasan nego
prethodni. Pristup je zasnovan na pametnoj pretrazi. Ako broj r sa brojem n1
daje ostatak a1 onda je on sigurno jedan od članova aritmetičkog niza a1 , a1 + n1 ,
a1 + 2n1 , . . . U petlji redom proveravamo ove brojeve sve dok ne naiđemo na
prvi element koji pri deljenju sa n2 daje ostatak a2 . Kada pronađemo takav
broj a12 (on će biti najmanji pozitivan broj sa osobinom da pri deljenju sa n1 i
n2 daje redom ostatke a1 i a2 ), znamo da će svaki naredni broj koji zadovoljava
to svojstvo biti član aritmetičkog niza a12 , a12 + n1 · n2 , a12 + 2 · n1 · n2 , . . .
Redom pretražujemo elemente ovog niza dok ne naiđemo na element a123 koji pri
deljenju sa n3 daje ostatak a3 , a zatim posmatramo brojeve a123 , a123 +n1 ·n2 ·n3 ,
a123 + 2 · n1 · n2 · n3 i tako dalje.

26

You might also like