Permutări, Aranjamente, Combinări: “Algebra Combinatorică” a Programării

Bun venit în lumea fascinantă a combinatoricii! Dacă matematica discretă și programarea s-ar întâlni la o cafea, acesta ar fi subiectul lor de discuție favorit. Să explorăm împreună aceste concepte fundamentale care stau la baza multor probleme de programare!


1. Introducere: “Magia Aranjării”

Imaginează-ți această analogie:

Ai 3 cărți pe masă: A (Albăstră), R (Roșie), V (Verde). În câte moduri diferite le poți aranja?

Răspuns:

  1. A R V
  2. A V R
  3. R A V
  4. R V A
  5. V A R
  6. V R A

Aceasta este esența combinatoricii! Studiază modurile în care putem aranja, selecta și combina obiecte.


2. Ce sunt Permutările? “Orchestrația Perfectă”

Definiție:

O permutare este un aranjament al TUTUROR elementelor unei mulțimi într-o anumită ordine.

Formula matematică:

P(n) = n! = n × (n-1) × (n-2) × ... × 2 × 1

Unde n! se citește “n factorial”.

Exemple:

  • 3 cărți: 3! = 3 × 2 × 1 = 6 permutări
  • 4 studenți într-o bancă: 4! = 24 de așezări diferite
  • 5 melodii într-un playlist: 5! = 120 de ordini diferite

Cum gândește calculatorul:

Pentru {A, B, C}:
Alege primul: A, B sau C
│
├── Dacă aleg A: rămân {B, C}
│   ├── Alege B: rămâne {C} → ABC
│   └── Alege C: rămâne {B} → ACB
│
├── Dacă aleg B: rămân {A, C}
│   ├── Alege A: rămâne {C} → BAC
│   └── Alege C: rămâne {A} → BCA
│
└── Dacă aleg C: rămân {A, B}
    ├── Alege A: rămâne {B} → CAB
    └── Alege B: rămâne {A} → CBA

Implementare în C++:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void afisarePermutare(vector<int>& permutare) {
    for(int num : permutare) {
        cout << num << " ";
    }
    cout << endl;
}

// Metoda 1: Folosind backtracking clasic
void permutariBacktracking(vector<int>& elemente, 
                          vector<bool>& folosit,
                          vector<int>& permutareCurenta) {

    if(permutareCurenta.size() == elemente.size()) {
        afisarePermutare(permutareCurenta);
        return;
    }

    for(int i = 0; i < elemente.size(); i++) {
        if(!folosit[i]) {
            // ADAUGĂ elementul
            folosit[i] = true;
            permutareCurenta.push_back(elemente[i]);

            // CONTINUĂ recursiv
            permutariBacktracking(elemente, folosit, permutareCurenta);

            // BACKTRACK
            folosit[i] = false;
            permutareCurenta.pop_back();
        }
    }
}

// Metoda 2: Folosind funcția next_permutation din STL
void permutariSTL(vector<int> elemente) {
    // IMPORTANT: vectorul trebuie sortat inițial!
    sort(elemente.begin(), elemente.end());

    do {
        afisarePermutare(elemente);
    } while(next_permutation(elemente.begin(), elemente.end()));
}

int main() {
    cout << "=== PERMUTĂRI - 3 ELEMENTE ===\n\n";

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

    cout << "Metoda Backtracking:\n";
    vector<bool> folosit(3, false);
    vector<int> permutareCurenta;
    permutariBacktracking(elemente, folosit, permutareCurenta);

    cout << "\nMetoda STL (next_permutation):\n";
    permutariSTL(elemente);

    cout << "\nNumar total de permutari: 3! = " 
         << 3*2*1 << " = 6 permutari\n";

    return 0;
}

3. Ce sunt Aranjamentele? “Selecția cu Ordine”

Definiție:

Un aranjament este o selecție de k elemente dintr-o mulțime de n elemente, unde ordinea contează.

Formula matematică:

A(n, k) = n! / (n-k)! = n × (n-1) × (n-2) × ... × (n-k+1)

Exemplu concret:
Ai 5 alergători (Ana, Bogdan, Cătălin, Dana, Eduard). Vrei să alegi:

  • Primul loc (medalia de aur)
  • Al doilea loc (medalia de argint)

Câte posibilități ai?

Primul loc: 5 posibilități
Al doilea loc: 4 posibilități (după ce am ales primul)
Total: 5 × 4 = 20 de aranjamente

Listă parțială a aranjamentelor:

  1. (Ana, Bogdan) ≠ (Bogdan, Ana) → ORDINEA CONTEAZĂ!

Implementare în C++:

#include <iostream>
#include <vector>
using namespace std;

void afisareAranjament(vector<int>& aranjament) {
    for(int num : aranjament) {
        cout << num << " ";
    }
    cout << endl;
}

void aranjamenteBacktracking(vector<int>& elemente,
                           vector<bool>& folosit,
                           vector<int>& aranjamentCurent,
                           int k) {

    if(aranjamentCurent.size() == k) {
        afisareAranjament(aranjamentCurent);
        return;
    }

    for(int i = 0; i < elemente.size(); i++) {
        if(!folosit[i]) {
            // ADAUGĂ elementul
            folosit[i] = true;
            aranjamentCurent.push_back(elemente[i]);

            // CONTINUĂ recursiv
            aranjamenteBacktracking(elemente, folosit, aranjamentCurent, k);

            // BACKTRACK
            folosit[i] = false;
            aranjamentCurent.pop_back();
        }
    }
}

int main() {
    cout << "=== ARANJAMENTE ===\n\n";
    cout << "Multime: {1, 2, 3, 4}\n";
    cout << "Aranjamente de cate 2 elemente (A(4,2)):\n\n";

    vector<int> elemente = {1, 2, 3, 4};
    vector<bool> folosit(4, false);
    vector<int> aranjamentCurent;

    aranjamenteBacktracking(elemente, folosit, aranjamentCurent, 2);

    cout << "\nFormula: A(4,2) = 4! / (4-2)! = 4×3 = 12 aranjamente\n";
    cout << "Observatie: (1,2) ≠ (2,1) - ordinea conteaza!\n";

    return 0;
}

4. Ce sunt Combinările? “Selecția fără Ordine”

Definiție:

O combinare este o selecție de k elemente dintr-o mulțime de n elemente, unde ordinea NU contează.

Formula matematică:

C(n, k) = n! / [k! × (n-k)!]

Notație: C(n, k) se citește “combinări de n luate câte k” sau “n alege k”

Exemplu concret:
Ai 5 prieteni și vrei să iei 2 cu tine la cinema.

  • Alegi pe Maria și Ion = același lucru ca Ion și Maria
  • ORDINEA NU CONTEAZĂ! Sunt aceeași combinație.

Diferența față de aranjamente:

  • Aranjamente: (Maria, Ion) ≠ (Ion, Maria) → 2 aranjamente diferite
  • Combinări: {Maria, Ion} = {Ion, Maria} → 1 singură combinație

Tabel comparativ Permutări vs Aranjamente vs Combinări:

ConceptOrdinea contează?Selectezi toate?FormulaExemplu cu {A,B,C}
PermutăriDADA (toate n)P(n)=n!ABC, ACB, BAC, BCA, CAB, CBA
Aranjamente de kDANU (doar k)A(n,k)=n!/(n-k)!AB, AC, BA, BC, CA, CB
Combinări de kNUNU (doar k)C(n,k)=n!/[k!(n-k)!]{A,B}, {A,C}, {B,C}

Implementare în C++:

#include <iostream>
#include <vector>
using namespace std;

void afisareCombinare(vector<int>& combinare) {
    cout << "{ ";
    for(int num : combinare) {
        cout << num << " ";
    }
    cout << "}" << endl;
}

void combinariBacktracking(vector<int>& elemente,
                          vector<int>& combinareCurenta,
                          int start, int k) {

    if(combinareCurenta.size() == k) {
        afisareCombinare(combinareCurenta);
        return;
    }

    // Start de la 'start' pentru a evita duplicatele
    for(int i = start; i < elemente.size(); i++) {
        // ADAUGĂ elementul
        combinareCurenta.push_back(elemente[i]);

        // CONTINUĂ cu următoarele elemente (i+1 pentru a evita repetiția)
        combinariBacktracking(elemente, combinareCurenta, i + 1, k);

        // BACKTRACK
        combinareCurenta.pop_back();
    }
}

int main() {
    cout << "=== COMBINĂRI ===\n\n";
    cout << "Multime: {1, 2, 3, 4}\n";
    cout << "Combinari de cate 3 elemente (C(4,3)):\n\n";

    vector<int> elemente = {1, 2, 3, 4};
    vector<int> combinareCurenta;

    combinariBacktracking(elemente, combinareCurenta, 0, 3);

    cout << "\nFormula: C(4,3) = 4! / [3! × (4-3)!] = 4 combinari\n";
    cout << "Observatie: {1,2,3} = {3,2,1} - ordinea NU conteaza!\n";

    // Calculăm C(4,3) matematic
    cout << "\nCalcul matematic:\n";
    cout << "C(4,3) = 4! / (3! × 1!) = (4×3×2×1) / [(3×2×1) × 1] = 24 / 6 = 4\n";

    return 0;
}

5. Triunghiul lui Pascal: “Harta Combinărilor”

Triunghiul lui Pascal este o modalitate ușoară de a calcula combinările:

        C(0,0)
      C(1,0) C(1,1)
    C(2,0) C(2,1) C(2,2)
  C(3,0) C(3,1) C(3,2) C(3,3)
C(4,0) C(4,1) C(4,2) C(4,3) C(4,4)

Care se traduce în:

       1
      1 1
     1 2 1
    1 3 3 1
   1 4 6 4 1

Regula: Fiecare număr este suma celor două numere de deasupra lui.

Implementare triunghi Pascal:

#include <iostream>
#include <vector>
using namespace std;

void afisareTriunghiPascal(int n) {
    vector<vector<int>> triunghi(n);

    for(int i = 0; i < n; i++) {
        triunghi[i].resize(i + 1);
        triunghi[i][0] = triunghi[i][i] = 1;

        for(int j = 1; j < i; j++) {
            triunghi[i][j] = triunghi[i-1][j-1] + triunghi[i-1][j];
        }
    }

    cout << "Triunghiul lui Pascal pentru n=" << n << ":\n\n";

    for(int i = 0; i < n; i++) {
        // Spații pentru centrare
        for(int s = 0; s < n - i - 1; s++) {
            cout << "  ";
        }

        for(int j = 0; j <= i; j++) {
            cout << triunghi[i][j] << "   ";
        }
        cout << endl;
    }

    cout << "\nRelatia cu combinari: triunghi[i][j] = C(" 
         << "i" << ", " << "j" << ")\n";
    cout << "Exemplu: C(4,2) = " << triunghi[4][2] << " (vezi linia 4)\n";
}

int combinariDinTriunghi(int n, int k) {
    if(k > n) return 0;
    if(k == 0 || k == n) return 1;

    vector<int> randCurent(n + 1, 0);
    vector<int> randAnterior(n + 1, 0);
    randAnterior[0] = 1;

    for(int i = 1; i <= n; i++) {
        randCurent[0] = randCurent[i] = 1;
        for(int j = 1; j < i; j++) {
            randCurent[j] = randAnterior[j-1] + randAnterior[j];
        }
        randAnterior = randCurent;
    }

    return randCurent[k];
}

int main() {
    cout << "=== TRIUNGHIUL LUI PASCAL ===\n\n";

    afisareTriunghiPascal(6);

    cout << "\nCalcul C(5,2) folosind triunghiul:\n";
    cout << "C(5,2) = " << combinariDinTriunghi(5, 2) << endl;

    return 0;
}

6. Formula Combinatorică: “Calculul Direct”

Funcție pentru factorial:

#include <iostream>
using namespace std;

// Funcție pentru factorial
long long factorial(int n) {
    long long result = 1;
    for(int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// Permutări directe
long long permutari(int n) {
    return factorial(n);
}

// Aranjamente directe
long long aranjamente(int n, int k) {
    if(k > n) return 0;

    long long result = 1;
    for(int i = 0; i < k; i++) {
        result *= (n - i);
    }
    return result;
}

// Combinări directe
long long combinari(int n, int k) {
    if(k > n) return 0;
    if(k == 0 || k == n) return 1;

    // Folosim proprietatea C(n,k) = C(n,n-k)
    if(k > n - k) {
        k = n - k;
    }

    long long result = 1;
    for(int i = 0; i < k; i++) {
        result *= (n - i);
        result /= (i + 1);
    }
    return result;
}

int main() {
    cout << "=== CALCUL DIRECT COMBINATORIC ===\n\n";

    cout << "Factoriale:\n";
    cout << "5! = " << factorial(5) << endl;
    cout << "10! = " << factorial(10) << endl;

    cout << "\nPermutari:\n";
    cout << "P(4) = " << permutari(4) << endl;

    cout << "\nAranjamente:\n";
    cout << "A(6,2) = " << aranjamente(6, 2) << endl;
    cout << "A(5,3) = " << aranjamente(5, 3) << endl;

    cout << "\nCombinari:\n";
    cout << "C(6,2) = " << combinari(6, 2) << endl;
    cout << "C(10,3) = " << combinari(10, 3) << endl;
    cout << "C(52,5) = combinatii la poker = " << combinari(52, 5) << endl;

    return 0;
}

7. Aplicații Practice în Probleme Reale

Problema 1: Loto 6/49

Câte combinații există la Loto 6/49?

C(49,6) = 49! / (6! × 43!) = 13.983.816 combinații

Problema 2: Formarea Comisiilor

Avem 10 persoane. Câte comisii de 3 persoane putem forma?

C(10,3) = 120 de comisii

Problema 3: Handshake Problem

Într-o cameră cu n persoane, fiecare dă mâna cu fiecare. Câte strângeri de mână?

C(n,2) = n × (n-1) / 2
Pentru 10 persoane: C(10,2) = 45 de strângeri de mână

Problema 4: Coduri PIN

Un PIN are 4 cifre (0-9). Câte coduri posibile dacă:
a) Cifrele pot fi repetate? → 10^4 = 10.000
b) Cifrele sunt distincte? → A(10,4) = 5.040

Implementare probleme practice:

#include <iostream>
using namespace std;

long long combinari(int n, int k);

int main() {
    cout << "=== APLICAȚII PRACTICE ===\n\n";

    // 1. Loto 6/49
    cout << "1. LOTO 6/49:\n";
    cout << "   C(49,6) = " << combinari(49, 6) << " combinatii\n";
    cout << "   Sansele de castig: 1/" << combinari(49, 6) << endl;

    // 2. Comisii
    cout << "\n2. COMISII:\n";
    cout << "   Din 10 persoane, comisii de 3: C(10,3) = " 
         << combinari(10, 3) << endl;
    cout << "   Din 20 de studenti, comisii de 5: C(20,5) = " 
         << combinari(20, 5) << endl;

    // 3. Strângeri de mână
    cout << "\n3. STRÂNGERI DE MÂNĂ:\n";
    int persoane = 15;
    cout << "   La o petrecere cu " << persoane << " persoane:\n";
    cout << "   C(" << persoane << ",2) = " << combinari(persoane, 2) 
         << " strangeri de mana\n";

    // 4. Coduri PIN
    cout << "\n4. CODURI PIN:\n";
    cout << "   PIN de 4 cifre (0-9):\n";
    cout << "   a) Cifrele pot fi repetate: 10^4 = " 
         << 10*10*10*10 << " coduri\n";
    cout << "   b) Cifrele sunt distincte: A(10,4) = ";
    long long aranj = 1;
    for(int i = 0; i < 4; i++) {
        aranj *= (10 - i);
    }
    cout << aranj << " coduri\n";

    return 0;
}

8. Algoritmi Avansați pentru Generare

Generarea Combinărilor cu Gray Code:

Metodă care generează combinații astfel încât două combinații consecutive diferă prin exact un element.

#include <iostream>
#include <vector>
#include <bitset>
using namespace std;

void combinariGrayCode(int n, int k) {
    if(k == 0 || k > n) return;

    vector<int> combinare(k);

    // Inițializare: primele k elemente
    for(int i = 0; i < k; i++) {
        combinare[i] = i;
    }

    int pozitie = k - 1;

    while(pozitie >= 0) {
        // Afișează combinația curentă
        cout << "{ ";
        for(int i = 0; i < k; i++) {
            cout << combinare[i] << " ";
        }
        cout << "}" << endl;

        if(combinare[k-1] == n-1) {
            pozitie--;
        } else {
            pozitie = k - 1;
        }

        if(pozitie >= 0) {
            for(int i = k-1; i >= pozitie; i--) {
                combinare[i] = combinare[pozitie] + i - pozitie + 1;
            }
        }
    }
}

int main() {
    cout << "=== COMBINĂRI GRAY CODE ===\n\n";
    cout << "Combinari de 3 luate cate 2 (ordine speciala):\n\n";

    combinariGrayCode(3, 2);

    return 0;
}

9. Probleme Clasice de Combinatorică

Problema 1: Numărul de Funcții

Câte funcții f: A → B există, unde |A|=m, |B|=n?

Răspuns: n^m
Exemplu: A={a,b}, B={1,2,3} → 3^2 = 9 funcții

Problema 2: Numărul de Funcții Injective

Câte funcții injective f: A → B există?

Răspuns: A(n,m) = n!/(n-m)!
Exemplu: A={a,b}, B={1,2,3,4} → A(4,2)=12 funcții injective

Problema 3: Numărul de Submulțimi

Câte submulțimi are o mulțime cu n elemente?

Răspuns: 2^n
Demonstrație: Fiecare element poate fi "în" sau "în afară"

Implementare probleme clasice:

#include <iostream>
#include <cmath>
using namespace std;

long long factorial(int n) {
    long long result = 1;
    for(int i = 2; i <= n; i++) result *= i;
    return result;
}

long long aranjamente(int n, int m) {
    if(m > n) return 0;
    long long result = 1;
    for(int i = 0; i < m; i++) result *= (n - i);
    return result;
}

int main() {
    cout << "=== PROBLEME CLASICE COMBINATORICE ===\n\n";

    // Problema 1: Numărul de funcții
    cout << "1. NUMARUL DE FUNCTII f: A→B\n";
    int m = 3, n = 4;
    cout << "   |A|=" << m << ", |B|=" << n << endl;
    cout << "   Numar functii: " << n << "^" << m << " = " 
         << (long long)pow(n, m) << endl;

    // Problema 2: Funcții injective
    cout << "\n2. FUNCTII INJECTIVE f: A→B\n";
    m = 3; n = 5;
    cout << "   |A|=" << m << ", |B|=" << n << endl;
    cout << "   Numar functii injective: A(" << n << "," << m 
         << ") = " << aranjamente(n, m) << endl;

    // Problema 3: Submulțimi
    cout << "\n3. SUBMULTIMILE UNEI MULTIMI\n";
    n = 5;
    cout << "   O multime cu " << n << " elemente are:\n";
    cout << "   2^" << n << " = " << (1LL << n) << " submultimi\n";
    cout << "   Inclusiv multimea vida si intreaga multime\n";

    // Problema 4: Permutări cu repetiție
    cout << "\n4. PERMUTARI CU REPETITIE\n";
    cout << "   Cuvinte cu literele 'M', 'A', 'T', 'E'\n";
    cout << "   M apare 2 ori, A apare 1, T apare 1, E apare 1\n";
    cout << "   Numar permutari: 5! / (2! × 1! × 1! × 1!) = "
         << factorial(5) / (factorial(2)*factorial(1)*factorial(1)*factorial(1))
         << " cuvinte\n";

    return 0;
}

10. Optimizări și Limitări

Limitări numerice:

#include <iostream>
#include <limits>
using namespace std;

int main() {
    cout << "=== LIMITE NUMERICE ÎN COMBINATORICĂ ===\n\n";

    cout << "Factorialul crește FOARTE repede:\n";
    cout << "10! = 3.628.800\n";
    cout << "15! = 1.307.674.368.000 (depășește int32)\n";
    cout << "20! = 2.432.902.008.176.640.000\n";
    cout << "21! depășește 64-bit signed! (2^63-1 ≈ 9.22×10^18)\n";

    cout << "\nConsecințe pentru programare:\n";
    cout << "1. Folosește 'long long' pentru factoriale mici\n";
    cout << "2. Pentru n>20, ai nevoie de aritmetică mare\n";
    cout << "3. Combinările pot fi mari chiar dacă n e mic\n";
    cout << "   Exemplu: C(100,50) ≈ 1.0×10^29\n";

    return 0;
}

Optimizări pentru combinări mari:

#include <iostream>
using namespace std;

// Combinări folosind formula recursivă C(n,k) = C(n-1,k-1) + C(n-1,k)
// Cu memoizare pentru eficiență

const int MAX_N = 100;
long long combMemo[MAX_N][MAX_N];

void initializareMemo() {
    for(int i = 0; i < MAX_N; i++) {
        combMemo[i][0] = combMemo[i][i] = 1;
        for(int j = 1; j < i; j++) {
            combMemo[i][j] = combMemo[i-1][j-1] + combMemo[i-1][j];
        }
    }
}

long long combinariMemo(int n, int k) {
    if(k < 0 || k > n) return 0;
    if(k == 0 || k == n) return 1;
    if(combMemo[n][k] != 0) return combMemo[n][k];

    combMemo[n][k] = combinariMemo(n-1, k-1) + combinariMemo(n-1, k);
    return combMemo[n][k];
}

int main() {
    initializareMemo();

    cout << "=== COMBINĂRI CU MEMOIZARE ===\n\n";

    cout << "C(10,5) = " << combinariMemo(10, 5) << endl;
    cout << "C(20,10) = " << combinariMemo(20, 10) << endl;
    cout << "C(30,15) = " << combinariMemo(30, 15) << endl;

    return 0;
}

11. Tabel Sinoptic Final

ConceptFormulaExempluAplicație
PermutăriP(n) = n!P(3)=6Așezare pe scaune, ordine în playlist
AranjamenteA(n,k)=n!/(n-k)!A(4,2)=12Podium (locul 1 și 2), parole cu cifre distincte
CombinăriC(n,k)=n!/[k!(n-k)!]C(4,2)=6Loterii, comisii, strângeri de mână
Permutări cu repetițien!/(n1!×n2!×…×nk!)“BANANA”=60Anagrame, aranjare obiecte identice
Combinații cu repetițieC(n+k-1,k)C(3+2-1,2)=6Alegere fructe când poți lua același de mai multe ori

12. Concluzie și Reguli Practice

Când folosești fiecare concept:

  1. Folosește PERMUTĂRI când:
  • Ai toate elementele
  • ORDINEA contează
  • Exemplu: aranjare cărți pe raft
  1. Folosește ARANJAMENTE când:
  • Selectezi doar unele elemente (k din n)
  • ORDINEA contează
  • Exemplu: alegere primul și al doilea premiu
  1. Folosește COMBINĂRI când:
  • Selectezi doar unele elemente (k din n)
  • ORDINEA NU contează
  • Exemplu: formare echipă, extragere la loto

Reguli de aur pentru programare:

// 1. Pentru n mic (<10): poti genera toate permutarile
// 2. Pentru n mediu (10-20): combinațiile încă sunt gestionabile
// 3. Pentru n mare (>20): folosește formula directă, nu genera!
// 4. Atenție la overflow! Folosește long long pentru n>12
// 5. Pentru combinări: C(n,k) = C(n,n-k) → alege k mai mic

Exercițiu final de sinteză:

#include <iostream>
using namespace std;

int main() {
    cout << "=== EXERCIȚIU FINAL - SINTEZĂ ===\n\n";

    cout << "Problema: La o petrecere sunt 8 persoane.\n\n";

    // 1. Strângeri de mână
    cout << "1. Cate strangeri de mana au loc?\n";
    cout << "   R: C(8,2) = " << (8*7/2) << endl;

    // 2. Fotografie de grup
    cout << "\n2. In cate moduri pot sta in rand pentru o fotografie?\n";
    cout << "   R: P(8) = 8! = 40320 moduri\n";

    // 3. Alegere comisie
    cout << "\n3. Cate comisii de 3 persoane pot fi formate?\n";
    cout << "   R: C(8,3) = " << (8*7*6/(3*2*1)) << " comisii\n";

    // 4. Alegere presedinte, vice, secretar
    cout << "\n4. Cate variante pentru presedinte, vice, secretar?\n";
    cout << "   R: A(8,3) = 8×7×6 = 336 variante\n";

    cout << "\n=== CONCLUZIE ===\n";
    cout << "Aceeasi multime de 8 persoane,\n";
    cout << "dar intrebari diferite → raspunsuri diferite!\n";
    cout << "Contextul si conditiile fac diferenta.\n";

    return 0;
}

Remember: Combinatorica este arta de a număra fără a număra efectiv fiecare caz! Învață să recunoști tipurile de probleme și formulele corespunzătoare, și vei rezolva cu ușurință o mulțime de probleme de programare! 🧮✨🔢

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *