Sunteți pe pagina 1din 19

CIURUL LUI

ERATOSTENE
INTRODUCERE

Din dorința de a lucra și a aprofunda mai multe probleme pe această temă, considerându-
le o provocare pentru elevii de liceu care doresc să susțină Concursul de Admitere anul acesta,
dar și să participe la diferite olimpiade și concursuri școlare, am ales tema „Ciurul lui
Eratostene”. Mi-am ales această temă, deoarece am fost pasionată de ea încă din momentul când
a fost predată la oră, iar faptul că aceasta constituie un caz particular de probleme devine din ce
în ce mai interesantă.
De asemenea, forma compactă și bagajul de informații deja cunoscut pe acest domeniu
din timpul orelor de curs determină ca până și cele mai grele sarcine să pară ușor de rezolvat.
Prin această metodă, lucrarea de față își propune prezentarea diferitelor probleme cu ciur
date în ultimii ani la concursuri, olimpiade și nu numai, dar și a unui algoritm general de
rezolvare aplicabil tuturor tipurilor de probleme din această categorie. Ciurul lui Eratostene are o
importanță semnificativă, deoarece generează toate numerele prime mai mici sau egale cu o
valoare dată în mod eficient fără a ne mai gândi la alte metode cu o complexitate mult mai mare.

2
CONSIDERAȚII TEORETICE

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 această lucrare voi prezenta Ciurul lui Eratostene, împreună cu niște optimizări
obișnuite.

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 ciur de tip bool, cu semnificația ciur[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: ciur[0] = ciur[1] = true.Apoi, alegem primul număr cu valoarea false din vectorul ciur,
și marcăm multiplii săi cu true. Repetăm acest proces până când toate numerele neprime
din ciurvor fi marcate cu true. Iată algoritmul scris în C++:

3
int i, j;
ciur[0] = ciur[1] = true;
for (i = 2; i <= n; i++) // Parcurgem vectorul ciur
if(!ciur[i]) // Dacă numărul curent este prim
for (j = 2 * i; j <= n; j += i) // parcurgem multiplii săi
ciur[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 √nare 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.

int i, j;
ciur[0] = ciur[1] = true;
for(i = 2; i * i <= n; i++)
if(!ciur[i])
for(j = i * i; j <= n; j += i)
ciur[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.

int i,j;
ciur[0] = ciur[1] = true;
for (j = 4; j <= n; j += 2) ciur[j] = true;
for(i = 3; i * i <= n; i += 2)
if(!ciur[i])
for(j = i * i; j <= n; j += 2 * i) ciur[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. 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.

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ă:

𝑛 𝑛 𝑛 𝑛 𝑛
+ + + +⋯+
2 3 5 7 𝑘

4
Unde k este cel mai mare număr prim mai mic sau egal cu n. Factorizându-l
pe n obținem:

1 1 1 1 1
𝑛( + + + + ⋯ + )
2 3 5 7 𝑘

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. Așadar, complexitatea algoritmului este O(nln(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
influențează complexitatea. La nivelul clasei a IX – a, este de ajuns să știm că ciurul lui
Eratostene face mai puțin decât O(n2) operații. De fapt, algoritmul lucrează aproape în timp
liniar, căci în practică O(ln(ln(n))) poate fi aproximat cu O(1).

5
APLICAȚII
I. Grad de dificultate : MEDIE

1) ERATOSTENE

Cerință:
Se daun numere naturale mai mici decât1000000. Determinaţi câte dintre ele sunt prime.

Date de intrare:
Fişierul de intrare eratostene.inconţine pe prima linie număruln; urmează cele n numere,
dispuse pe mai multe linii şi separate prin spaţii.

Date de iesire:
Fişierul de ieşire eratostene.out va conţine pe prima linie numărul C, reprezentând
numărul valorilor citite care erau numere prime.

Restricții și precizări: 1 ≤ n ≤ 1000000

Soluție:
Vom folosi vector characteristic v[] cu n = 1000000 elemente:
 v[p]=0, dacă p este prim
 v[p]=1, dacă p este neprim
Inițial toate elementele sunt 0. Marcăm numerele 0 1 ca fiind neprime, cu 1.Parcurgem șirul
(până la sqrt(n)). Dacă v[i]=0, i este prim și marcăm cu 1 toți multiplii lui i.

Implementare:

#include <fstream>
#define N 1000001
using namespace std;
ifstream fin("eratostene.in"); ofstream fout("eratostene.out");
int n;
bool c[N];
void ciur()
{ int i,j;
c[0]=c[1]=1;
for(i=2; i*i<N; i++)
if(!c[i])
for(j=2; i*j<N; j++) c[i*j]=1;
}
int main()
{ int i,x,ct=0;
ciur();
fin>>n;
for(i=1; i<=n; i++)
{ fin>>x;
if(!c[x]) ct++;
}
fout<<ct; return 0; }

6
II. Grad de dificultate : DIFICILĂ

2) DEVT

Enunț :
Într-o zi, Gigel a găsit pe masa tatălui său o foaie A4 pe care era trecut șirul denumit
“devt” sub forma 1, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, …, n. Dedesubtul acestui
șir găsește un text alcătuit din k întrebări de forma a, b cu semnificația “Câte numere din acest
șir se află în intervalul [a,b]?”.

Cerință:
Ajutați-l pe Gigel să răspundă corect la toate cele k întrebări.

Date de intrare:
Fișierul de intrare devt.in conține pe prima linie numerele natural n și k, iar pe
următoarele k linii numerele a, b cu semnificația din enunț.

Date de ieșire:
Fișierul de ieșiredevt.out va conținek linii, pe fiecare liniei aflându-se un număr natural,
reprezentând răspunsul întrebăriii.

Restricții și precizări:
 0 ≤ a ≤ b ≤ n ≤ 100000
 1 ≤ k ≤ 5000
 n aparține șirului devt

Soluție:
Se observă ca șirul este alcătuit din toate numerele naturale neprime. Astfel se generează
în vectorul c ciurul lui Eratostene până la n marcând cu 0 numerele prime, iar cu 1 numerele
neprime. Se mai generează un vector f în care pe poziția i se memorează câte numere neprime au
fost intâlnite până la numărul i.

Implementare:

#include <fstream>
#define N 100005
using namespace std;
ifstream fin("devt.in");
ofstream fout("devt.out");
bool c[N];
int f[N],n;
void ciur()
{ int i,j;
c[0]=c[2]=1;
for(i=3; i<=N; i+=2) c[i]=1;
for(i=3; i<=N; i+=2)
if(c[i])
for(j=3*i; j<=N; j+=2*i) c[j]=0;}

7
void neprime()
{ int i,ct=0;
for(i=0; i<=n; i++)
{ if(!c[i])ct++;
f[i]=ct;
}
}
void solve(int a,int b)
{ int ct=0;
ct=f[b]-f[a];
if(!c[a]) ct++;
fout<<ct<<"\n";
}
int main()
{ int k,a,b,i;
ciur();
fin>>n>>k;
neprime();
for(i=1; i<=k; ++i)
{ fin>>a>>b;
solve(a,b);
}
return 0;
}

III. Grad de dificultate : CONCURS

3) EXTRAPRIME

Enunț:
Gigel, mare amator de probleme de matematică şi informatică, a observat că unele
numere prime au o proprietate interesantă: orice cifră ar elimina dintr-un astfel de număr,
numărul obţinut este tot număr prim. A numit astfel de numere numere extraprime. De
exemplu, numărul 317 este un număr extraprim: el este număr prim şi, în plus, dacă eliminăm
cifra 3, obţinem 17, care este prim; dacă eliminăm 1, obţinem 37, care este prim; dacă eliminăm
7, obţinem 31, care este şi el număr prim.
Cerință:
Spunem că x este între a şi b dacă x≥a şi x≤b. Fiind date două valori natural a şi b, să se
determine câte numere extraprime există între a şi b, precum şi cel mai mic şi cel mai mare
număr extraprim dintre a şi b.
Date de intrare:
Fișierul de intrare extraprime.in conține pe prima linie cele două valori natural a şi b,
separate printr-un spaţiu.
Date de ieșire:
Fișierul de ieșire extraprime.out va conține 3 linii. Pe prima linie se va scrie un număr
natural nr reprezentând numărul de numere extraprime dintre a şi b. Pe linia a doua a fişierului
de ieşire se va scrie cel mai mic număr extraprim dintre a şi b iar pe linia a treia a fişierului de
ieşire se va scrie cel mai mare număr extraprim dintre a şi b.

8
Restricții și precizări:
 10 < a ≤ b < 10000000
 numărul 1 nu este prim
 pentru datele de test există întotdeauna soluție
Soluție:
Se citesc datele, se stabilește eficient numărul de cifre ale lui b și se contruiește eficient
ciurul lui Eratostene până la b. Pentru valorile de la a la b se verifică dacă sunt prime și dacă
verifică proprietatea de extraprim, se reține primul și ultimul găsit și se numără cele care
îndeplinesc ambele condiții. La final se afișează cele trei rezultate obținute.
Implementare:
#include <fstream>
#define N 10000001
using namespace std;
ifstream fin("extraprime.in");
ofstream fout("extraprime.out");
bool c[N];
void Ciur(int n) ///functia va genera toate numerele prime <= cu n
{ int i,j; c[2]=1;
for(i=3; i<=n; i+=2) c[i]=1;
for(i=3; i*i<=n; i+=2)
if(c[i])
for(j=3*i; j<=n; j+=2*i) c[j]=0;
}
bool Extraprime(int x) ///functia va returna 1 daca un nr este extraprim, 0 in caz contrar
{ int nou,n=x,p=10;
///indiferent de cifra eliminata vom obtine tot un numar prim
while(x) ///cat timp numarul mai are cifre
{ nou=(n/p)*(p/10)+n%(p/10);
if(c[nou]==0) return 0;
p*=10; x/=10;
}
return 1;
}
void Cerinta()
{ int i,a,b,ct=0,vmax=0,vmin=N;
fin>>a>>b; ///citim capetele intervalului
Ciur(b);
if(a%2==0) a++; ///daca primul nr este par il marim, deoarece niciun nr par nu e prim
for(i=a; i<=b; i+=2) ///parcurgem intervalul
{ if(c[i]==1) ///daca nr e prim
if(Extraprime(i)) ///si extraprim
{ct++; ///le contorizam
if(i>vmax) vmax=i; ///aflam maximul
if(i<vmin) vmin=i; ///aflam minumul
}
}
fout<<ct<<"\n"<<vmin<<"\n"<<vmax; ///le afisam
}
int main()
{ Cerinta();
return 0;}

9
4) FANTASTICE

Enunț:
Definim un număr ca fiind fantastic dacă numărul de numere la care acesta se împarte
exact este un număr prim.

Cerință:
Dându-se un șir cu n numere întregi strict pozitive, să se afișeze numărul de numere
fantastice din șir.

Date de intrare:
Fișierul de intrare fantastice.in conține pe prima linie numărul n de numere, iar pe cea
de-a doua linie, separate prin câte un spaţiu, cele n numere.

Date de ieșire:
Fișierul de ieșire fantastice.out va conține pe prima linie numărul de numere fantastice
din șir.

Restricții și precizări:
 1 ≤ n ≤ 106
 Numerele de pe a doua linie a fișierului de intrare vor fi mai mici sau egale cu 106

Soluție:
Rezolvarea constă în determinarea numărului de divizori și verificarea primalității.
Aceste rezultate pot fi determinate în mai multe moduri:

verificarea primalității se poate face direct, prin căutarea unui divizor, sau cu
ciurul lui Eratostene
determinarea numărului de divizori ai unui număr dat se poate face prin
determinarea acestora sau cu indicatorul lui Euler.

Implementare:

#include <fstream>
#define N 1000001
using namespace std;
ifstream fin("fantastice.in");
ofstream fout("fantastice.out");
int n,d,ct,c[N],x;
void Ciur() ///functia va genera toate numerele prime <= cu n
{ int i,j;
c[2]=1;
for(i=3; i<=N; i=i+2) c[i]=1;
for(i=3; i*i<=N; i=i+2)
if(c[i])
for(j=3*i; j<=N; j=j+2*i) c[j]=0;
}

10
int Divizori(int x) ///functia va determina numarul de divizori ai unui nr
{ int ct=0,i;
for(i=1; i*i<=x; i++)
if(x%i==0)
{ ct++;
if(x/i!=i) ct++;
}
return ct;
}
int main()
{ int i;
Ciur();
fin>>n; ///citim nr. de valori
for(i=1; i<=n; i++) ///parcurgem numerele
{ fin>>x; ///le citim
d=Divizori(x); ///in d punem nr. de divizori
if(c[d]) ct++; ///contorizam val. prime
}
fout<<ct;
return 0;
}

5) FACTORI

Enunț:
Gigel a aflat la matematică definiţia factorialului unui număr natural nenul n. Acesta este
produsul tuturor numerelor naturale începând cu 1 şi terminând cu numărul respectiv şi se
notează cu n!. Astfel, factorialul numărului natural 6 este 6!=1*2*3*4*5*6 şi este egal cu 720.
Factorialele numerelor naturale cresc însă extrem de repede. De exemplu, 7!=5040 în timp ce
10!=3628800.Fiind un bun matematician, Gigel a imaginat o altă metodă de a indica factorialul
unui număr. Astfel, el ştie că un număr natural nenul se poate descompune în factori primi. De
exemplu 720 poate fi scris ca 24*32*51. Gigel codifică descompunerea în factori primi astfel: 4 2
1 însemnând faptul că în descompunerea lui 720 în factori primi apare factorul 2 de 4 ori,
factorul 3 apare de 2 ori şi factorul 5 apare o dată. Cu alte cuvinte, Gigel indică pentru fiecare
număr prim ≤ n puterea la care acesta apare în descompunerea în factori primi a lui n!.
Cerință:
Scrieţi un program care să citească o secvenţă de numere naturale nenule şi care să
afişeze în modul descris în enunţ factorialele numerelor citite.
Date de intrare:
Fişierul de intrare factori.in conţine mai multe numere naturale nenule, câte un număr pe
linie. Ultima linie a fişierului de intrare conţine valoarea 0 indicând faptul că setul de numere s-a
terminat.
Date de ieșire:
Fişierul de ieşire factori.out va conţine câte o linie pentru fiecare număr nenul din
fişierul de intrare. Pe liniai din fişierul de ieşire va fi descrisă descompunerea în factori primi a
factorialului numărului de pe liniai din fişierul de intrare, în modul descris în enunţ. Numerele
scrise pe aceeaşi linie vor fi separate prin câte un spaţiu.

11
Restricții și precizări:
 Numerele naturale din fişierul de intrare (exceptând ultimul) sunt din intervalul
[2,60000].
 Fişierul de intrare conţine maxim 10 numere naturale nenule.

Soluție:
Voi genera numerele prime mai mici decât 6000 cu ajutorul ciurului și pentru fiecare
număr citit voi calcula numărul de apariții a numerelor prime în factorial.

Implementare:

#include <fstream>
#define N 60001
using namespace std;
ifstream fin("factori.in");
ofstream fout("factori.out");
bool c[N];
void ciur()
{ int i,j; c[2]=1;
for(i=3; i<=N; i=i+2) c[i]=1;
for(i=3; i*i<=N; i=i+2)
if(c[i])
for(j=3*i; j<=N; j=j+i*2) c[j]=0;
}
void desc(int n,int f[])
{ int ct,d=2;
while(n!=1)
{ ct=0;
while(n%d==0) { n=n/d; ct++;}
if(ct>0) f[d]+=ct;
if(d*d<n)d++;
else d=n;
}
}
int main()
{int x,i;
fin>>x;
ciur();
while(x)
{ int f[N]={0};
for(i=2; i<=x; i++)
if(c[i]) f[i]++;
else desc(i,f);
for(i=2; i<=x; i++)
if(f[i]>0) fout<<f[i]<<" ";
fout<<"\n";
fin>>x;
}
return 0;
}

12
6) CĂTĂLIN ȘI GREȘELILE

Enunț:
Îl cunoașteți, cred, pe Cătălin, fan-ul numărul 1 al greșelilor. Ei bine, în teza la mate,
Cătălin a făcut N greșeli. Presupunând, prin reducere la absurd, că el corectează o greșeală i,
poate alege să corecteze o singură greșeală j, cu proprietatea 1<j<i şi i%j=0. El știe că, dacă face
această alegere poate să continue din greșeala j, după aceeași regulă și nu mai poate reveni la o
greșeala anterioară.
Cerință:
Cătălin alege T greșeli G[1] G[2] … G[T] și dorește să știe, pentru fiecare G[i], numărul
maxim de greșeli pe care le poate corecta dacă începe rezolvând-o pe aceasta.
Date de intrare:
Fișierul de intrare greșelile.in conține pe prima linie două numere N și T unde N este
numărul de greșeli și T numărul de întrebări. Următoarele T linii conțin câte un număr natural
nenul reprezentând greșeala de la care Cătălin vrea să pornească corectarea.
Date de ieșire:
Fișierul de ieșire greșelile.out va conține fiecare linie i un singur număr natural
reprezentând numărul maxim de greșeli care pot fi corectate dacă ar începe Cătălin cu greșeala
G[i].
Restricții și precizări:
 1 ≤ N, T ≤ 1000000
Soluție:
Se folosește programarea dinamica, în dp[i] fiind răspunsul pentru nodul i.Se parcurge cu
i de la 2 la n, și pentru fiecare multiplu a lui i (j), dp[j] = max (dp[j], dp[i]) + 1.

Implementare:

#include <fstream>
#define N 1000001
using namespace std;
ifstream fin("greselile.in");
ofstream fout("greselile.out");
int n,t,dp[N];
int main()
{ fin>>n>>t;
int i,j,x;
for(i=2; i<=n; i++)
{ if(!dp[i]) dp[i]=1;
for(j=2*i; j<=n; j=j+i) dp[j]=max(dp[j],dp[i]+1);
}
for(i=1; i<=t; i++)
{ fin>>x;
fout<<dp[x]<<"\n";
}
return 0;
}

13
7) CMMDCSECV

Enunț:
Fie un şir a1, a2, …, an de numere naturale. O secvenţă a şirului este o succesiune de
elemente alăturate din şir, deci de forma ai, ai+1, …, aj-1, aj. Lungimea acestei secvenţe este dată
de numărul de elemente ale secvenţei, adică j-i+1.

Cerință:
Să se determine lungimea maximă a unei secvenţe din şir cu proprietatea că cel mai mare
divizor comun al numerelor din secvenţă este strict mai mare decât 1.

Date de intrare:
Fişierul de intrare cmmdcsecv.in conţine pe prima linie un număr natural n reprezentând
lungimea şirului, iar pe linia a doua se află n numere naturale separate prin câte un spaţiu
reprezentând elementele şirului.

Date de ieșire:
Fişierul de ieşire cmmdcsecv.out va conţine un singur număr natural reprezentând
lungimea maximă a unei secvenţe care are cel mai mare divizor comun strict mai mare decât 1.

Restricții și precizări:
 3 ≤ n ≤ 100000
 1 ≤ ai ≤ 1000

Soluție:
Se generează ciurul după care pentru fiecare număr prim se calculează cea mai lungă
secvență care se găsește în șir pe poziții consecutive pentru care numărul prim este divizor. Se
selectează astfel cel mai mare număr obținut.

Implementare:
#include <fstream>
using namespace std ;
int prime[200], a[100001], n, k, maxL;
void Ciur()
{int i, j ;
for(i=4; i<=1000 ; i+=2) a[i] = 1 ;
k=0;
prime[k++] = 2;
for (i = 3 ; i <= 1000 ; i++)
if (a[i] == 0)
{ prime[k++] = i;
for (j=i*i ; j <= 1000 ; j = j + 2*i)a[j] = 1 ;
}
}

14
void Citire()
{ ifstream fin("cmmdcsecv.in");
fin>>n;
for (int i = 0 ; i < n ; i++) fin>>a[i];
}
int Calcul(int x)
{ int i=0, lung, lungmax=0 ;
while (i < n)
if (a[i] % x != 0)i++ ;
else
{ i++ ;
lung = 1 ;
while (a[i] % x == 0 && i < n)
{ i++ ;
lung++ ;
}
if (lung > lungmax)lungmax = lung ;
}
return lungmax ;
}

void DetSol()
{ int i, p; maxL = 0 ;
for (i = 0 ; i < k ; i++)
{ p = Calcul(prime[i]) ;
if (maxL < p) maxL = p ;
}
ofstream fout("cmmdcsecv.out") ;
fout << maxL << "\n" ;
}

int main()
{ Ciur() ;
Citire() ;
DetSol() ;
return 0 ;
}

8) FRACȚII

Enunț:

Gigel, într-o zi când își făcea temele la matematică, s-a apucat să scrie pe o foaie de
hârtie, un șir de fracții ireductibile de forma P/Q cu 1 ≤ P,Q ≤ N, unde N este un număr natural
ales de el. De exemplu, pentru N = 4 el a obținut următorul șir:

1 1 1 1 2 2 3 3 3 4 4
, , , , , , , , , , .
1 2 3 4 1 3 1 2 4 1 3
Gigel s-a apucat apoi să numere câte fracții a obținut pentru N = 4 și a văzut că sunt 11.

15
Cerință:

Fiind dat un număr natural N, să se determine câte fracții sunt in șirul de fracții construit
după regulile de mai sus.

Date de intrare:

Fișierul de intrare fracții.in conține pe prima linie numărul natural N.

Date de ieșire:

Fișierul de ieșire fracții.out trebuie să conțină un număr natural pe prima linie care
reprezintă câte fracții sunt in șir.

Restricții și precizări:

 1 ≤ N ≤ 1000000

Soluție:

În această problemă 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≤qeste 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. Fiindcă nu se încadrează în timp pentru toate testele vom folosi 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:

int i, j;
for (i = 2; i <= n; i++)
for (j = 2 * i; j <= n; j += i)
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. Se observă că algoritmul se folosește de faptul că:

𝑛 = ∑ 𝜑(𝑑)
𝑑|𝑛

16
O demonstrație pentru această formulă, nu foarte riguroasă, dar destul de intuitivă este
următoarea: considerăm toate fracțiile de forma x/n, cu 1≤x≤n. Pentru n=10, acestea sunt:

1 2 3 4 5 6 7 8 9 10
, , , , , , , , ,
10 10 10 10 10 10 10 10 10 10
Acum, aducem fiecare fracție la forma sa ireductibilă, și le ordonăm crescător după
numitor:

1 1 1 2 3 4 1 3 7 9
, , , , , , , , ,
1 2 5 5 5 5 10 10 10 10
Se observă că fiecare numitor este un divizor al lui n și că numărătorii fracțiilor cu
numitorul d iau ca valori toate numerele mai mici sau egale cu d, prime cu d, care în total
sunt φ(d). De aici, formula dată se obține imediat.

Implementare:
#include <fstream>
#define N 1000001
ifstream fin("fractii.in");
ofstream fout("fractii.out");
int n,euler[N];
long long int sol;
int main()
{ int i, j;
fin >> n;
for (i = 2; i <= n; i++)
euler[i] = i - 1;
for (i = 2; i <= n; i++)
{
sol += euler[i];
for (j = 2 * i; j <= n; j += i)
euler[j] -= euler[i];
}
fout << 2 * sol + 1 << '\n';
fout.close();
return 0;
}

17
BIBLIOGRAFIE

1. Cerchez E.,Șerban M., Programarea in limbajul C/C++ pentru liceu, Ed. Polirom, 2005
2. https://www.pbinfo.ro/?pagina=articole&subpagina=afisare&&id=2540
3. https://infogenius.ro/ciurul-lui-eratostene-cpp/
4. https://infoarena.ro/ciurul-lui-eratostene
5. https://ro.wikipedia.org/wiki/Ciurul_lui_Eratostene
6. http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=1218

18
CUPRINS

1. Introducere ............................................................................................................ 3
2. Considerații teoretice
2.1. Algoritm ................................................................................................... 4
2.2. Implementare ........................................................................................... 4
2.3. Optimizări ................................................................................................ 5
2.4. Complexitate ............................................................................................ 5
3. Aplicații
3.1.Eratostene ........................................................................................................ 8
3.2. Devt .............................................................................................................. 10
3.3. Extraprime.................................................................................................... 12
3.4. Fantastice ..................................................................................................... 14
3.5. Factori .......................................................................................................... 16
3.6. Cătălin și greșelile ........................................................................................ 18
3.7. Cmmdcsecv .................................................................................................. 20
3.8. Fracții ........................................................................................................... 22
4. Bibliografie ......................................................................................................... 25

19

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