Ce vei învăța astăzi?
După această lecție, vei înțelege șirul factorial, șirul lui Lucas și alte șiruri definite prin relații recurente.
Șirul factorial - n!
Definiție: Șirul factorial este definit ca produsul numerelor naturale de la 1 la n. Notăm cu n! (citim "n factorial").
Formula recurentă:
cu condiția inițială: 0! = 1
Observații importante:
Analogii pentru factorial:
Farfurii în bufet
Dacă ai 5 farfurii diferite, în câte moduri le poți aranja? Răspuns: 5! = 120 moduri.
Persoane la coadă
În câte moduri pot sta 4 persoane la o coadă? Răspuns: 4! = 24 de moduri.
Cărți pe raft
În câte moduri poți aranja 3 cărți diferite? Răspuns: 3! = 6 moduri.
Piese de șah
Numărul de permutări ale n obiecte distincte este n! – apare des în combinatorică.
Extra (pe înțeles): Factorialul „crește foarte repede”. De aceea, în C++ folosim long long și limităm n (de obicei ≤ 20).
Implementarea factorialului în C++
Atenție la depășire! 20! = 2.432.902.008.176.640.000 care depășește limita lui int. Folosim long long.
Execuție pentru n=5:
Recursivitate: O funcție care se autoapelează. Trebuie să ai un caz de bază și un caz recursiv.
Observație: La factorial, recursivitatea e ok pentru n mic (ex: ≤ 20). La Fibonacci „naiv” nu e ok.
Procesul recursiv: apeluri succesive până la cazul de bază
| Situație | Soluție cu vector | Când este util | Exemplu |
|---|---|---|---|
| Mai multe factoriale | Calculează toate factorialele de la 0 la n într-un vector | Ai nevoie de mai multe valori factorial rapid | fact[0]=1, fact[1]=1, fact[2]=2... |
| Numere mari | Stochează factorial în vector de cifre | Pentru n>20 (depășește long long) | 100! are 158 de cifre |
| Memoizare | Stochează rezultatele calculate | Evită recalcularea în recursivitate | if(fact[n]!=0) return fact[n]; |
// CALCUL FACTORIALE CU VECTOR (precalcul)
#include <iostream>
using namespace std;
long long fact[21]; // pentru valori până la 20!
void calculeazaFactoriale() {
fact[0] = 1;
for (int i = 1; i <= 20; i++) {
fact[i] = fact[i-1] * i;
}
}
int main() {
calculeazaFactoriale();
cout << "5! = " << fact[5] << endl; // 120
cout << "10! = " << fact[10] << endl; // 3628800
return 0;
}
De ce e util precalculul? Dacă ai multe întrebări (ex: 100 de valori de n), nu mai recalculezi de fiecare dată.
Șirul lui Lucas
Definiție: Șirul lui Lucas este similar cu șirul Fibonacci, dar cu valori inițiale diferite:
L₁ = 1
Lₙ = Lₙ₋₁ + Lₙ₋₂ pentru n ≥ 2
Relația dintre Fibonacci și Lucas:
- Lₙ × Fₙ = F₂ₙ
- Lₙ - 5Fₙ = 4(-1)ⁿ
- Lₙ + Lₙ₊₃ = 2Lₙ₊₂
// GENERARE ȘIR LUCAS - similar cu Fibonacci
#include <iostream>
using namespace std;
int lucas(int n) {
if (n == 0) return 2;
if (n == 1) return 1;
int L0 = 2, L1 = 1, Ln = 0;
for (int i = 2; i <= n; i++) {
Ln = L0 + L1;
L0 = L1;
L1 = Ln;
}
return Ln;
}
int main() {
cout << "Primii 10 termeni Lucas: ";
for (int i = 0; i < 10; i++) {
cout << lucas(i) << " ";
}
// Afiseaza: 2 1 3 4 7 11 18 29 47 76
return 0;
}
De reținut: Lucas are aceeași recurență ca Fibonacci, doar pornește diferit (2,1 în loc de 0,1).
Exercițiu de completare - Factorial recursiv:
Completează funcția recursivă pentru calculul factorialului:
Bancă de cuvinte:
Hint: La factorial, cazul de bază clasic este n==0 (sau n<=1). Aici folosim n==0 ca să fie foarte clar.
Test de înțelegere: Care este valoarea corectă?
Test rapid - șiruri recurente:
1. Care este valoarea lui 6! (factorial)?
2. Care sunt primii doi termeni ai șirului Lucas?
3. Care este relația de recurență pentru factorial?
Alte șiruri recurente importante
Șirul Tribonacci: Generalizare a lui Fibonacci cu suma ultimelor 3 termeni.
Tₙ = Tₙ₋₁ + Tₙ₋₂ + Tₙ₋₃ pentru n ≥ 3
int tribonacci(int n) {
if (n == 0 || n == 1) return 0;
if (n == 2) return 1;
int a = 0, b = 0, c = 1, d;
for (int i = 3; i <= n; i++) {
d = a + b + c;
a = b;
b = c;
c = d;
}
return c;
}
Numerele Catalan: Folosite în combinatorică pentru numărarea structurilor.
Cₙ = (2(2n-1)/(n+1)) × Cₙ₋₁
Aplicații: numărul de parantezări corecte, arbori binari, triangulări ale poligonului etc.
Șirul Perrin: Similar cu Lucas, dar cu alte condiții inițiale.
Pₙ = Pₙ₋₂ + Pₙ₋₃ pentru n ≥ 3
Șirul Pell: O altă variantă de șir recurent.
Pₙ = 2Pₙ₋₁ + Pₙ₋₂ pentru n ≥ 2
Capcane comune și soluții pentru șiruri recurente
Greșeli frecvente la lucrul cu șiruri recurente:
// GREȘIT pentru n>12
int factorial(int n) {
int fact = 1;
for (int i = 1; i <= n; i++) {
fact = fact * i; // overflow pentru n>12
}
return fact;
}
// CORECT - folosește long long (până la n=20)
long long factorialLL(int n) {
long long fact = 1;
for (int i = 1; i <= n; i++) {
fact = fact * i;
}
return fact;
}
// GREȘIT - fără condiție de oprire
int factorialBad(int n) {
return n * factorialBad(n-1); // se apelează la infinit
}
// CORECT - cu caz de bază
int factorialOk(int n) {
if (n <= 1) return 1;
return n * factorialOk(n-1);
}
Calculul recursiv al lui Fibonacci fără memoizare are complexitate O(2ⁿ).
// GREȘIT - ineficient (exponențial)
int fibBad(int n) {
if (n <= 1) return n;
return fibBad(n-1) + fibBad(n-2);
}
// CORECT - cu memoizare (O(n))
int fibMemo(int n, vector<int>& memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fibMemo(n-1, memo) + fibMemo(n-2, memo);
return memo[n];
}
Best Practices pentru șiruri recurente:
long long pentru factorial până la 20Încălzire (2 minute)
Test rapid - șiruri recurente: Completează următoarele valori:
- 4! = verifică
- L₃ (Lucas) = verifică
- T₄ (Tribonacci) = verifică
Hint: 4! = 24, Șirul Lucas: 2,1,3,4..., Tribonacci: 0,0,1,1,2...
PbInfo #1159 - Factorial
Enunț:
Cerință: Să se calculeze n! (factorialul lui n).
Date de intrare:
Programul citește de la tastatură numărul n, 0 ≤ n ≤ 20.
Date de ieșire:
Programul va afișa pe ecran valoarea lui n!.
Exemplu:
Intrare:
5
Ieșire:
120
Explicație: 5! = 1 × 2 × 3 × 4 × 5 = 120
Cum gândim soluția:
- Citim numărul n
- Dacă n este 0 sau 1, factorialul este 1
- Altfel, calculăm produsul numerelor de la 1 la n
- Afișăm rezultatul
Execuție pentru n=5:
Ce trebuie să verifici mereu?
- n poate fi 0 (0! = 1)
- Folosește
long longpentru rezultat - Verifică că n este între 0 și 20
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long fact = 1;
for (int i = 2; i <= n; i++) {
fact *= i;
}
cout << fact;
return 0;
}
Varianta cu verificare:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n < 0 || n > 20) {
cout << "n trebuie sa fie intre 0 si 20";
return 0;
}
long long fact = 1;
for (int i = 1; i <= n; i++) fact *= i;
cout << fact;
return 0;
}
Ce învățăm din această problemă:
- Tip de date corect:
long long - Caz special: 0! = 1
- Iterativ: rapid și sigur
PbInfo #1160 - Șirul lui Lucas
Problemă care calculează al n-lea termen al șirului Lucas
Enunț:
Cerință: Se citește un număr n. Să se afișeze al n-lea termen al șirului Lucas.
Date de intrare:
Programul citește de la tastatură numărul n, 0 ≤ n ≤ 45.
Date de ieșire:
Programul va afișa pe ecran al n-lea termen al șirului Lucas.
Exemplu:
Intrare:
5
Ieșire:
11
Explicație: L₅ = 11 (șirul: 2, 1, 3, 4, 7, 11...)
Cum gândim soluția:
- Citim n
- Tratezi separat n=0 și n=1
- Pentru n ≥ 2, iterezi cu 2 variabile (L0, L1)
- Afișezi Ln
Cum funcționează pentru n=5:
De ce metoda iterativă și nu recursivă?
Pentru n mare (până la 45), recursivitatea ar fi ineficientă. Metoda iterativă este O(n).
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n == 0) { cout << 2; return 0; }
if (n == 1) { cout << 1; return 0; }
long long L0 = 2, L1 = 1, Ln = 0;
for (int i = 2; i <= n; i++) {
Ln = L0 + L1;
L0 = L1;
L1 = Ln;
}
cout << Ln;
return 0;
}
Varianta cu vector:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long L[50];
L[0] = 2;
L[1] = 1;
for (int i = 2; i <= n; i++) {
L[i] = L[i-1] + L[i-2];
}
cout << L[n];
return 0;
}
Ce învățăm:
- Lucas: L₀=2, L₁=1
- Iterativ: eficient
- Cazuri speciale: n=0, n=1
Exercițiul Practic: Diverse șiruri recurente
Sarcini de lucru
Completează următoarele sarcini pentru a-ți consolida cunoștințele despre șiruri recurente
Sarcina 1: Comparație factorial iterativ vs recursiv
Scrie un program care:
- Citește un număr n (0 ≤ n ≤ 10)
- Calculează n! folosind ambele metode (iterativ și recursiv)
- Afișează ambele rezultate și verifică dacă sunt egale
Soluție pentru Sarcina 1:
#include <iostream>
using namespace std;
// Factorial iterativ
long long factorialIterativ(int n) {
long long fact = 1;
for (int i = 2; i <= n; i++) fact *= i;
return fact;
}
// Factorial recursiv
long long factorialRecursiv(int n) {
if (n <= 1) return 1;
return n * factorialRecursiv(n - 1);
}
int main() {
int n;
cout << "Introduceți n (0-10): ";
cin >> n;
if (n < 0 || n > 10) {
cout << "n trebuie să fie între 0 și 10";
return 0;
}
long long factIterativ = factorialIterativ(n);
long long factRecursiv = factorialRecursiv(n);
cout << n << "! calculat iterativ: " << factIterativ << endl;
cout << n << "! calculat recursiv: " << factRecursiv << endl;
if (factIterativ == factRecursiv) cout << "✓ Cele două metode dau același rezultat!" << endl;
else cout << "✗ Există o eroare în implementare!" << endl;
cout << "\nPrimele " << n+1 << " factoriale: ";
for (int i = 0; i <= n; i++) cout << factorialIterativ(i) << " ";
return 0;
}
Observații:
- n=0 → 0!=1
- Iterativ = mai rapid, recursiv = mai „elegant”
- Test recomandat: n=5 → 120
Sarcina 2: Generare comparativă Fibonacci vs Lucas
Cerință: Scrie un program care:
- Citește un număr n (n ≤ 20)
- Generează și afișează primele n termeni pentru ambele șiruri
- Calculează și afișează diferența dintre termenii corespunzători
- Verifică relația: Lₙ = Fₙ₋₁ + Fₙ₊₁ pentru fiecare n ≥ 1
Soluție pentru Sarcina 2:
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Introduceți n (max 20): ";
cin >> n;
if (n <= 0 || n > 20) {
cout << "n trebuie să fie între 1 și 20!";
return 0;
}
long long F[25];
long long L[25];
F[0] = 0; F[1] = 1;
L[0] = 2; L[1] = 1;
// Generare până la n și încă un pic (pentru F[i+1])
int lim = (n + 2 < 25) ? (n + 2) : 24;
for (int i = 2; i < lim; i++) {
F[i] = F[i-1] + F[i-2];
L[i] = L[i-1] + L[i-2];
}
cout << "\nComparație Fibonacci vs Lucas:\n";
cout << "i\tF[i]\t\tL[i]\t\tL[i]-F[i]\tVerificare L_i = F_{i-1}+F_{i+1}\n";
cout << "--------------------------------------------------------------------------------\n";
for (int i = 0; i < n; i++) {
long long dif = L[i] - F[i];
cout << i << "\t" << F[i] << "\t\t" << L[i] << "\t\t" << dif << "\t\t";
if (i >= 1) {
long long suma = F[i-1] + F[i+1];
cout << ((L[i] == suma) ? "✓" : "✗");
} else {
cout << "-";
}
cout << endl;
}
// Exemplu explicit
if (n > 5) {
cout << "\nDemonstrație pentru i=4:\n";
cout << "L4 = " << L[4] << "\n";
cout << "F3 + F5 = " << F[3] << " + " << F[5] << " = " << (F[3] + F[5]) << "\n";
}
return 0;
}
Ce învățăm:
- Generare cu vectori
- Relația Lucas–Fibonacci
- Afișare tabelară clară