Sunteți pe pagina 1din 5

Ciurul lui Eratostene în C++

Problema Fractii de pe InfoArena

Ciurul lui Eratostene este un algoritm clasic folosit pentru determinarea tuturor numerelor prime
mai mici sau egale cu un număr natural dat n.
Acesta a fost creat de matematicianul grec Eratostene, și este predecesorul multor algoritmi mai
eficienți, precum Ciurul lui Atkin și Ciurul lui Euler. În acest articol voi prezenta Ciurul lui Eratostene,
împreună cu niște optimizări obișnuite și o aplicație interesantă.
Strike the twos and strike the threes
The Sieve of Eratosthenes!
When the multiples sublime,
The numbers that remain are prime.

Algoritm
1. Se scrie pe o foaie o listă cu toate numerele naturale de la 2 la n.
2. Se caută în listă primul număr care nu a fost tăiat (și pe care încă nu l-am folosit).
3. Se taie de pe foaie toți multiplii numărului ales mai înainte, cu excepția lui.
4. Dacă încă nu am ajuns la finalul listei căutând numere netăiate, se revine la pasul 2.
La final, toate numerele netăiate din listă sunt cele prime. De fiecare dată, numărul găsit la pasul 2 chiar
este prim, pentru că, dacă ar fi fost divizibil cu vreun număr prim mai mic decât el, ar fi fost tăiat deja.
Apoi, evident că numerele tăiate la pasul 3, fiind divizibile cu un număr prim mai mic decât ele, sunt
numere compuse.

Implementare
Reținem un vector sieve (ciur în engleză), de tip bool, cu semnificația sieve[i] == true dacă numărul i nu
este prim, iar false dacă i este prim.
Am ales codificarea pe dos pentru că în C++ vectorii globali sunt automat inițializați cu 0, și n-are rost să
mai facem o parcurgere pentru a-l schimba pe 0 în 1. Cum numerele 0 și 1 nu sunt nici prime, nici
compuse, le vom marca de la început cu true: sieve[0] = sieve[1] = true;.
Apoi, alegem primul număr cu valoarea false din vectorul sieve, și marcăm multiplii săi cu true. Repetăm
acest proces până când toate numerele neprime din sieve vor fi marcate cu true.

Implementare C++:
1/5
1 int i, j;
2 sieve[0] = sieve[1] = true;
3  
4 for (i = 2; i <= n; i++) // Parcurgem vectorul sieve.
5     if (!sieve[i]) // Dacă numărul curent este prim,
6         for (j = 2 * i; j <= n; j += i) // parcurgem multiplii săi
7             sieve[j] = true; // și îi marcăm drept numere compuse.

Optimizări
Pe linia 6, putem porni for-ul de la i * i, pentru că toți multiplii lui i de forma k * i, cu k < i, au fost deja
tăiați. Din același motiv, pe linia 4, putem itera i-ul până la √n: Orice număr compus mai mare
decât √n are printre divizori cel puțin un număr prim mai mic decât √n, pe care l-am folosit deja. Putem
scrie condiția i <= sqrt(n), dar e ineficientă, pentru că apelul funcției sqrt va calcula radicalul lui n la
fiecare pas. O metodă mai elegantă este i * i <= n.

1 int i, j;
2 sieve[0] = sieve[1] = true;
3  
4 for (i = 2; i * i <= n; i++)
5     if (!sieve[i])
6         for (j = i * i; j <= n; j += i)
7             sieve[j] = true;

O altă idee de optimizare este să căutăm numerele prime (pe linia 4) din 2 în 2, deoarece știm că toate
numerele prime mai mari decât 2 sunt impare. Deci, îl vom trata pe 2 separat, iar primul for îl vom începe
de la 3.

1 int i, j;
2 sieve[0] = sieve[1] = true;
3  
4 for (j = 4; j <= n; j += 2)
5     sieve[j] = true;
6  
7 for (i = 3; i * i <= n; i += 2)
8     if (!sieve[i])
9         for (j = i * i; j <= n; j += 2 * i)
10             sieve[j] = true;

Pe linia 9 am incrementat j-ul cu 2 * i, pentru a nu itera și printre multiplii pari.

O ultimă optimizare pe care i-o putem aduce programului ține de memorie. Cum vectorul conține
elemente de tipul bool, putem implementa un vector pe biți (despre care am vorbit la finalul articolului
despre operații pe biți). Astfel, vom folosi de 8 ori mai puțină memorie, dar rareori se întâmplă să nu
avem suficientă memorie pentru un ciur obișnuit.

Iată o sursă C++ completă ce folosește Ciurul lui Eratostene pentru a determina toate numerele
prime mai mici sau egale cu nn, precum și numărul lor.

1 #include <iostream>
2 #define VMAX 618

2/5
3  
4 using std::cin;
5 using std::cout;
6  
7 int n;
8 bool sieve[VMAX];
9  
10 int sol; // numărul de numere prime
11 int primes[VMAX]; // numerele prime
12  
13 int main() {
14     int i, j;
15  
16     cin >> n;
17     sieve[0] = sieve[1] = true;
18  
19     for (j = 4; j <= n; j += 2)
20         sieve[j] = true;
21  
22     for (i = 3; i * i <= n; i += 2)
23         if (!sieve[i])
24             for (j = i * i; j <= n; j += 2 * i)
25                 sieve[j] = true;
26  
27     primes[sol++] = 2;
28     for (i = 3; i <= n; i += 2)
29         if (!sieve[i])
30             primes[sol++] = i;
31  
32     cout << sol << '\n';
33     for (i = 0; i < sol; i++)
34         cout << sieve[i] << ' ';
35  
36     cout << '\n';
37     return 0;
38 }

Complexitate
De fiecare dată când se ajunge la pasul 2, algoritmul face n/i pași pentru a tăia numerele compuse multipli
de i. În total, asta înseamnă:
n/2+n/3+n/5+n/7+⋯+n/k ,

unde k este cel mai mare număr prim mai mic sau egal cu n.

Factorizându-l pe n obținem:

n(1/2+1/3+1/5+1/7+⋯+1/k)

Al doilea factor, adică suma inverselor numerelor prime până la k, crește la fel de repede ca
funcția ln(ln(n)), conform lui Euler.

3/5
Așadar, complexitatea algoritmului este O(n x ln(ln(n))) sau, dacă aplicăm optimizările de mai
sus, O(ln(ln(√n))). S-ar mai adăuga un O(√n)), care este numărul de iterații făcute de primul for, dar asta
nu prea influențează complexitatea. La nivelul clasei a 9-a, este de ajuns să știți că Ciurul lui Eratostene
face (mult) mai puțin decât O(n2) operații.

Aplicație la Ciurul lui Eratostene: Problema Fractii de pe InfoArena

De obicei, Ciurul lui Eratostene este folosit pentru marcarea rapidă a numerelor prime la începutul
programului, pentru ca mai apoi să poată fi testată primalitatea oricărui număr în O(1).
Însă, există probleme în care este mult mai greu să-ți dai seama că poți folosi tehnica ciurului. Una
dintre ele este problema Fractii de pe InfoArena.

În problema Fractii trebuie să aflăm numărul de fracții ireductibile de forma p/q, cu 1≤p,q≤n. Plecăm de
la două observații imediate:
 Dacă p/q este ireductibilă, atunci și q/p este ireductibilă. Deci, este de ajuns să numărăm fracțiile
ireductibile de forma p/q, cu 2≤p<q, iar apoi să înmulțim rezultatul cu 2. După aceea, adunăm 1 la
rezultat, pentru că pe 1/1 nu am numărat-o.
 Numărul de fracții ireductibile de forma p/q, cu p≤q este egal cu numărul de numere mai mici sau
egale cu q, prime cu el. Adică indicatorul lui Euler, φ(q).

Problema se rezumă la a calcula eficient indicatorul lui Euler pentru fiecare număr natural de
la 2 la n. Putem folosi formula
 φ(n)=(p1−1)p1k1−1 ………. (pr−1)prkr−1
pentru fiecare număr, dar nu se încadrează în timp pentru toate testele.
Avem nevoie de o soluție mai inteligentă. Aici intervine Ciurul lui Eratostene.
Reținem un vector euler, unde euler[i] reprezintă φ(i). Inițial, pentru fiecare index i de la 2 la n îi atribuim
lui euler[i] valoarea i - 1, pentru că i nu este prim cu el însuși. Apoi, aplicăm tehnica ciurului ca mai jos:

1 int i, j;
2 for (i = 2; i <= n; i++)
3     for (j = 2 * i; j <= n; j += i)
4         euler[j] -= euler[i];

Când cu primul for ajungem la euler[i], îi știm deja valoarea finală, așa că actualizăm indicatorul pentru
multiplii mai mari ai lui i. Toți aceștia sunt primi cu toate numerele cu care și i este prim. De aceea
efectuăm scăderea euler[j] -= euler[i].
Problema Fractii de pe InfoArena, C++

1 #include <fstream>
2 #define VMAX 1000010
3
4 std::ifstream fin("fractii.in");
5 std::ofstream fout("fractii.out");
6
7 int n;
8 long long int sol;
9 int euler[VMAX];
10

4/5
11 int main() {
12     int i, j;
13  
14     fin >> n;
15     for (i = 2; i <= n; i++)
16         euler[i] = i - 1;
17
18     for (i = 2; i <= n; i++) {
19         sol += euler[i];
20         for (j = 2 * i; j <= n; j += i)
21             euler[j] -= euler[i];
22     }
23     fout << 2 * sol + 1 << '\n';
24     
25     fout.close();
26     return 0;
27 }

Probleme recomandate
 nrdiv
 prime
 prime2
 extraprime
 Fractii
 Numere Prime
 Cmmdc2
 Movedel

5/5

S-ar putea să vă placă și