Documente Academic
Documente Profesional
Documente Cultură
Invers modular
Exponențiere rapidă.
Doresc să pun în evidenţă o modalitate simplă de calcul a valorii expresiei xn, unde x este un
număr real, iar n este număr natural. O primă variantă, cea care se învaţă odată cu
instrucţiunile repetitive, este bazată pe relaţia:
Există deja o funcție pow definită în biblioteca cmath, dar dacă definiți funcția de mai sus
înainte de main este OK. Compilatorul va ști că nu vă referiți la cea din biblioteca respectivă.
Că tot vorbeam de funcția pow din cmath. Este periculoasă, deoarece lucrează cu tipul double,
așa că uneori un apel de genul pow (10, 2) poate returna 99. Prefer să-mi definesc propria
funcție pentru ridicarea la putere decât să o folosesc pe asta.
În acest fel, numărul total de operaţii de înmulţire va scădea semnificativ. De exemplu pentru
calculul lui x13 se vor efectua numai 5 înmulţiri, iar x100 doar 8 înmulţiri. Funcţia este
următoarea:
Știm deja că acest algoritm are complexitatea O(log2n), dar hai să o și demonstrăm. De
exemplu, pentru apelul pow(3, 618), lanțul de apeluri recursive arată cam așa:
pow(3, 618)
pow(3^2, 309)
3^ 2 * pow(3^4, 154)
3^ 2 * pow(3^8, 77)
3^ 10 * pow(3^16, 38)
3^ 10 * pow(3^32, 19)
3^ 42 * pow(3^64, 9)
3^106 * pow(3^128, 4)
3^106 * pow(3^256, 2)
3^106 * pow(3^512, 1)
3^618 * pow(3^1024, 0)
Se observă că, la fiecare pas al doilea parametru se înjumătățește, exact cum la căutarea binară, la
fiecare pas se înjumătățește intervalul de căutare. Numărul de pași făcuți de exponențierea
logaritmică este [log2n]+1. Acel +1 vine de la apelul final, când se returnează 1. Iată deci, cum
pentru 618 se efectuează doar 11 apeluri, pe când prin ridicarea la putere obișnuită, s-ar fi executat
618 pași.
Ei bine, de obicei, pentru exponenți suficienți de mari ca să se simtă diferența dintre cele două
variante de ridicare la putere, rezultatul final ar depăși long long int-ul. De aceea, de cele
mai multe ori, se cere doar restul lui modulo un anumit număr prim. Aplicând regulile clasice
de aritmetică modulară, o funcție ce calculează acest rest arată așa:
Cele mai importante două aplicații ale exponențierii rapide sunt calcularea inversului modular
și generarea celui de-al n-lea termen dintr-un șir ce se bazează pe o recurență liniară, ambele,
desigur, în timp logaritmic.
Lectia 9. Exponențiere rapidă. Algoritmul lui Euclid extins. Invers modular
Voi lua ca exemplu șirul lui Fibonacci, care este definit astfel:
Pentru a genera al n-lea termen din șirul lui Fibonacci (tot modulo un număr dat, pentru că
numerele lui Fibonacci cresc foarte repede), ar trebui, în mod obișnuit, să generăm mai întâi
toți termenii mai mici decât n. Complexitatea este O(n). Există totuși o soluție mult mai bună,
care necesită niște matematică de a 11-a, ce se bazează pe înmulțirea matricelor. Voi da mai
întâi soluția și o explic după. Am notat al n-lea termen din șirul lui Fibonacci cu f(n).
Folosim inducție matematică. Presupunând că ceea ce am scris mai sus este adevărat, demonstrăm
că:
Care, se vede clar că sunt adevărate. Ei bine, știind că înmulțirea matricelor este asociativă, putem
folosi exponențierea logaritmică pentru ridicarea matricei de mai sus la puterea n. Iată o sursă
elegantă, de 100 de puncte, pentru problema Kfib de pe InfoArena (care cere generarea celui de-al
k-lea termen Fibonacci):
#include <fstream>
#define MOD 666013
using namespace std;
ifstream fin("kfib.in");
ofstream fout("kfib.out");
// Struct ce reține o matrice 2x2
struct Mat {
long long int mat[2][2];
};
};
int k;
int main() {
fin >> k;
fout << pow(initMat, k).mat[0][1] << '\n';
fout.close();
return 0;
}
Constanta din spatele complexității logaritmice din această problemă este dată de numărul de
înmulțiri pe scalari ce se efectuează la înmulțirea a două matrice. Care, pentru matricele pătratice
este dimensiunea lor la puterea a treia; în cazul nostru 8.
Generalizare
Mai rămâne să găsim generalizarea acestei metode pentru generarea celui de-al n-lea termen al
șirului recurent:
Lectia 9. Exponențiere rapidă. Algoritmul lui Euclid extins. Invers modular
Luați-vă un exemplu mic și veți vedea că este chiar o relație intuitivă; un antrenament bun este
problema Iepuri de pe InfoArena. Recurența asta se poate aplica la fel de bine și la șirul lui Fibonacci.
Însă acolo, întâmplător:
Probleme recomandate
• Kfib
• Iepuri
• fibgcd
• 2sah
• Sumdiv
Sper că am explicat suficient de clar cum stă treaba cu exponențierea logaritmică și cu șirurile
recurente.
matematica pot sari direct la codul sursa, dar vor avea probleme in a tine minte algoritmul pe
viitor.
• b * x0 + (a % b) * y0 = d
• a*x+b*y=d
Trebuie sa aflam o solutie pentru x si y. Vom nota ca mai sus parte intreaga din a / b cu c.
• b * x0 + (a - b * c) * y0 = a * x + b * y
• b * (x0 - c * y0 - y) = a * (x - y0)
• x0 - c * y0 - y = 0, De unde rezulta y = x0 - c * y0
• x - y0 = 0, De unde rezulta x = y0
Sursa modificata pentru a calcula si x si y nu este mult mai complexa. Acum intelegeti de ce
am trimis d ca pointer mai sus, si de ce am folosit varianta recursiva a algoritmului lui euclid.
Implementat iterativ, este nevoie de un vector care sa tina toate valorile c (a / b) obtinute pe
parcurs.
Si iterativ:
while (b) {
int q = a / b;
tmp = b;
b = a - q * b;
a = tmp;
tmp = *x;
*x = x1 - q * *x;
x1 = tmp;
tmp = *y;
*y = y1 - q * *y;
y1 = tmp;
//cout<< a<<" "<< b<<" "<< *x<<" "<< *y<<" " << x1<<" " y1<<"\n";
}
*d = a;
*x = y1;
*y = x1;
}
Invers modular
Aplicatie:
Se dau doua numere A si N, cu 1 ≤ A ≤ N-1, prime intre ele (cel mai mare divizor
comun al lor este 1). Sa se determine X intre 1 si N-1 astfel incat A * X sa fie congruent cu 1,
modulo N (restul impartirii lui A * X la N sa fie 1). Numarul X se va numi inversul modular al
lui A.
Solutii:
Varianta bruta
Un algoritm evident ar fi incercarea tuturor numerelor X intre 1 si N-1 si verificarea proprietatii din
enunt pentru X.
Lectia 9. Exponențiere rapidă. Algoritmul lui Euclid extins. Invers modular
Algoritmi optimi
Varianta 1
O complexitate mai buna se obtine folosind teorema lui Euler, din care avem ca
, unde reprezinta numarul numerelor mai mici decat N si prime cu N.
Cum rezulta ca este inversul modular al lui A.
long long Putere (long long a, long long n, long long mod ) { /// a ^ n
long long prod ;
prod = 1 ;
while (n > 0 ) {
if (n % 2 == 1 ) {
prod = ( (prod % mod) * (a % mod) ) % mod ;
n--;
} else {
a = (a % mod) * (a % mod) % mod ;
n /= 2 ;
}
}
return prod ;
Lectia 9. Exponențiere rapidă. Algoritmul lui Euclid extins. Invers modular
int main() {
ifstream fin ("inversmodular.in") ;
ofstream fout("inversmodular.out") ;
long long n, a ;
fin>>a>>n;
fout<<Putere(a, Phi(n)-1, n) ;
return 0;
}
Varianta 2
Complexitatea optima pentru determinarea inversului modular este O(log2N). Putem folosi
principiul extins al lui Euclid: oricare ar fi A si N numere intregi exista doua numere X si Y de
asemenea intregi astfel incat A * X + N * Y = cmmdc(A, N). Cum in problema determinarii inversului
modular avem cmmdc(A, N) = 1, exista X si Y astfel incat A * X + N * Y = 1.
Obs! X poate sa fie si negativ, deci trebuie sa adaugam N la X pana cand devine pozitiv.
#include <fstream>
using namespace std;
ifstream fin("inversmodular.in");
ofstream fout("inversmodular.out");
int n,a,i;
long long y,s=3;
void inv(long long &x,long long &y,int a,int b ) {
if (!b) {
x=1;
y=0;
return;
}
inv(x,y,b,a%b);
i=x;
x=y;
y=i-(a/b)*y;
}
int main() {
fin>>a>>n;
inv(s,y,a,n);
if (s<0) fout<<s%n+n; /// In loc de while(x<0) x+=n;
else fout<<s;
return 0;
}
Lectia 9. Exponențiere rapidă. Algoritmul lui Euclid extins. Invers modular
Alte aplicatii ce folosesc notiunile prezentate mai sus se regasesc in urmatoarele probleme
infoarena :
• Functii
• Jap2