Sari la conținut

Bac informatică 2020 Model, rezolvat

Modelul de la Bacalaureatul la Informatică (Mate-Info, C/C++): conține 3 subiecte (90 de puncte) și are un timp de lucru de 3 ore. Sub fiecare exercițiu găsești răspunsul corect și rezolvarea.

Subiecte oficiale publicate de Ministerul Educației. Rezolvări realizate de subac. Actualizat la .

📌 Despre Modelul din 2020

Modelul din 2020 trece prin expresii logice, recursivitate, backtracking, arbori, grafuri orientate, structuri, matrice, șiruri de caractere și fișiere. Este un test complet, bun ca să vezi cum arată un subiect de Bac de la cap la coadă.

Subiectul I (20p): 5 grile: o expresie logică echivalentă cu „x mai mic decât y și decât z”, trei subprograme (două recursive și unul iterativ) apelate pentru n=12, o problemă de backtracking cu platouri de câte trei prăjituri, fiii unui nod într-un arbore cu rădăcina dată și numărul minim de vârfuri ale unui graf orientat cu 3 componente tare conexe.

Subiectul al II-lea (40p): Un algoritm în pseudocod în care m crește și n scade, adunând multiplii lui x întâlniți pe drum, o structură parcare pentru cele 20 de zone de parcare ale unui oraș și o secvență care afișează indicii liniilor unei matrice al căror prim sau ultim element este 2020.

Subiectul al III-lea (30p): Un subprogram cu cifre (duplicare, care dublează fiecare cifră impară a unui număr), o problemă cu șiruri de caractere în care cuvintele cu cel puțin n litere se afișează înaintea celorlalte și o problemă de eficiență care numără valorile din [1,n] care lipsesc dintr-un șir ordonat descrescător, citit din fișierul numere.in.

⚠️ La ce să fii atent

  • La I.1: Fii atent la „dacă și numai dacă”! Varianta b) pare bună, dar ea cere z<y și x<z. Pentru x=1, y=2, z=5, x e mai mic decât amândouă, iar b) dă totuși 0.
  • La I.5: Componenta tare conexă în care nu intră niciun arc și cea din care nu iese niciun arc au nevoie de cel puțin 2 vârfuri fiecare, ca toate gradele să fie nenule. A treia poate avea un singur vârf, deci minimul este 2 + 2 + 1 = 5.
  • La II.1.a: Nu sări peste condiția m≠n! Când m și n ajung amândouă la 28, se adună doar m, nu și n, iar rezultatul este 120.
  • La III.3 (eficiență): Șirul poate avea până la un miliard de numere, iar n poate fi tot un miliard, deci un vector de apariții nu încape în memorie. Cum șirul e ordonat descrescător, valorile egale stau una lângă alta: numără valorile distincte comparând fiecare număr cu cel dinaintea lui, iar răspunsul este n minus acest număr.

Subiectul I

20 de puncte
  1. Exercițiul 1 4p Expresii și operatori

    Variabilele x, y și z sunt de tip întreg și memorează numere naturale din intervalul [1,10^3].

    Indicați o expresie C/C++ care are valoarea 1 dacă și numai dacă valoarea variabilei x este strict mai mică decât valoarea oricăreia dintre variabilele y și z.

    • a)z+x<x+y && x+z>z+y
    • b)z+x<x+y && z+y>y+x
    • c)x+z<z+y && z+y>y+x
    • d)x+y<y+z && x+z>z+y
    Vezi rezolvarea

    Răspuns corect: c) x+z<z+y && z+y>y+x

    Fiecare expresie se simplifică scăzând termenul care apare în amândouă părțile inegalității:

    • x+z<z+y înseamnă x<y
    • z+y>y+x înseamnă z>x, adică x<z

    Varianta c) cere exact „x mai mic decât y și x mai mic decât z”, adică ce se cere în enunț.

    Varianta b) pare să meargă, dar nu e echivalentă: ea cere z<y și x<z. E adevărat că de aici urmează x<y, dar invers nu: pentru x=1, y=2, z=5, x e mai mic decât amândouă, iar b) dă totuși 0. Enunțul cere „dacă și numai dacă”, deci nu e de ajuns ca expresia să fie adevărată uneori — trebuie să fie adevărată exact atunci.

  2. Exercițiul 2 4p Recursivitate

    Subprogramele f1, f2 și f3 sunt definite mai jos.

    Pentru n=12, se obține aceeași valoare la apelul subprogramelor:

    int f1(int n)
    { if(n==0) return 1;
      return n*f1(n-1);
    }
    int f2(int n)
    {  if(n>1)
         return n*(n-1)*f2(n-2);
       return 1;
    }
    
    int f3(int n)
    { int f=1;
      while(n!=0)
      { f=f*n;
        n=n-1;
      }
      return f;
    }
    • a)f1 și f2
    • b)f1 și f3
    • c)f2 și f3
    • d)f1, f2 și f3
    Vezi rezolvarea

    Răspuns corect: d) f1, f2 și f3

    Toate trei calculează 12!.

    f1 e factorialul recursiv obișnuit: 12*11*10*…*1.

    f2 coboară din doi în doi, dar înmulțește de fiecare dată două numere: 12*11*f2(10), apoi 10*9*f2(8), și tot așa până la 2*1*f2(0), iar f2(0) returnează 1. Înmulțite, dau tot 12*11*10*…*2*1.

    f3 face același produs cu un while, coborând de la 12 la 1.

    Deci f1(12)=f2(12)=f3(12)=12!, iar răspunsul e d).

  3. Exercițiul 3 4p Backtracking

    Având la dispoziție cinci tipuri de prăjituri, cu caise, cu căpșune, cu prune, cu piersici, respectiv cu cireșe, se utilizează metoda backtracking pentru a obține toate posibilitățile de a forma platouri cu câte trei tipuri de prăjituri diferite, știind că în cadrul unui platou nu contează ordinea de așezare a prăjiturilor și că prăjiturile cu căpșune nu vor fi plasate pe același platou cu prăjiturile cu piersici. Primele patru soluții obținute sunt, în această ordine: (caise, căpșune, prune), (caise, căpșune, cireșe), (caise, prune, piersici), (caise, prune, cireșe). A șasea soluție generate este:

    • a){caise, prune, căpșune}
    • b){caise, piersici, cireșe }
    • c){căpșune, prune, cireșe}
    • d){prune, piersici, cireșe}
    Vezi rezolvarea

    Răspuns corect: c) {căpșune, prune, cireșe}

    Numerotăm tipurile în ordinea din enunț: 1 caise, 2 căpșune, 3 prune, 4 piersici, 5 cireșe. Backtracking-ul generează combinările de câte trei în ordine crescătoare, sărind peste cele care au și 2, și 4:

    nr.combinare
    11,2,3caise, căpșune, prune
    —1,2,4respinsă: căpșune cu piersici
    21,2,5caise, căpșune, cireșe
    31,3,4caise, prune, piersici
    41,3,5caise, prune, cireșe
    51,4,5caise, piersici, cireșe
    —2,3,4respinsă
    62,3,5căpșune, prune, cireșe

    Primele patru din tabel sunt chiar cele date în enunț, ceea ce confirmă ordinea. A șasea soluție este c).

  4. Exercițiul 4 4p Arbori

    Un arbore cu 8 noduri, numerotate de la 1 la 8, are drept rădăcină nodul numerotat cu 5 și muchiile:

    1. [1,5]
    2. [2,7]
    3. [3,7]
    4. [3,6]
    5. [4,5]
    6. [5,7]
    7. [7,8]

    Indicați numărul de noduri care sunt descendenți direcți („fii”) ai nodului 7.

    • a)2
    • b)3
    • c)4
    • d)5
    Vezi rezolvarea

    Răspuns corect: b) 3

    Rădăcina este nodul 5, deci „fiii” unui nod sunt vecinii lui, mai puțin cel prin care s-a ajuns la el (părintele).

    Nodul 5 are muchii către 1, 4 și 7 — aceștia sunt fiii rădăcinii, iar 5 este părintele lui 7.

    Nodul 7 are muchii către 2, 3, 5 și 8. Dintre aceștia, 5 e părintele, deci fiii lui 7 sunt 2, 3 și 8 — trei noduri, varianta b).

    Nodul 6 e fiu al lui 3, deci nepot al lui 7: e descendent, dar nu direct.

  5. Exercițiul 5 4p Grafuri orientate

    Un graf orientat are 10 arce, 3 componente tare conexe, iar fiecare vârf al său are atât gradul interior, cât și gradul exterior nenule. Numărul minim de noduri pe care le poate avea graful este:

    • a)4
    • b)5
    • c)6
    • d)7
    Vezi rezolvarea

    Răspuns corect: b) 5

    Componentele tare conexe formează între ele un graf fără circuite, deci există cel puțin una în care nu intră niciun arc (sursă) și cel puțin una din care nu iese niciun arc (destinație).

    În componenta-destinație, arcele care pleacă din nodurile ei nu au unde merge în afară, fiindcă din ea nu iese nimic. Cum fiecare nod trebuie să aibă gradul exterior nenul, acele arce rămân înăuntru — deci componenta conține un circuit, iar un circuit fără bucle are nevoie de cel puțin 2 noduri. Același raționament, cu gradul interior, cere 2 noduri și pentru componenta-sursă.

    A treia componentă poate avea un singur nod: arcele lui intră dinspre sursă și ies spre destinație, deci amândouă gradele sunt nenule.

    Minim: 2 + 2 + 1 = 5 noduri, varianta b). Cele 10 arce sunt mai mult decât suficiente pentru a lega așa un graf.

Subiectul al II-lea

40 de puncte
  1. Exercițiul 1.a 6p Algoritmi în pseudocod

    Algoritmul alăturat este reprezentat în pseudocod.

    S-a notat cu a%b restul împărțirii numărului natural a la numărul natural nenul b.

    Scrieți valoarea afișată în urma executării algoritmului dacă se citesc, în această ordine, numerele 21, 38 și 4.

    citește m,n,x
      (numere naturale nenule, m≤n)
     s←0; pm←1; pn←1
    ┌repetă
    │┌dacă m%x=0 atunci
    ││ s←s+m; pm←x
    │└■
    │┌dacă n%x=0 și m≠n atunci
    ││ s←s+n; pn←x
    │└■
    │ m←m+pm
    │ n←n-pn
    └până când m>n
     scrie s
    Vezi rezolvarea

    Răspunsul din barem: 120

    pasmnce se adunăs
    12138—0
    22237—0
    32336n=3636
    42432m=24 și n=3292
    52828m=28120

    La pasul 3, n=36 se împarte exact la 4, deci se adună la s, iar pn devine 4 — de acum n scade din patru în patru. La pasul 4 amândouă sunt multipli de 4 și se adună amândouă, iar pm devine tot 4.

    La pasul 5, m și n sunt egale cu 28: m se adună, dar n nu, fiindcă a doua condiție cere m≠n. Aici e capcana exercițiului. După pas, m=32 și n=24, deci m>n și algoritmul se oprește, afișând 120.

  2. Exercițiul 1.b 6p Algoritmi în pseudocod

    Același algoritm ca la 1.a.

    S-a notat cu a%b restul împărțirii numărului natural a la numărul natural nenul b.

    Dacă pentru m și x se citesc numerele 20, respectiv 2020, scrieți cea mai mică și cea mai mare valoare care pot fi citite pentru variabila n, astfel încât, pentru fiecare dintre acestea, în urma executării algoritmului, să se afișeze 2020.

    Vezi rezolvarea

    Răspunsul din barem: 2020 4039

    Cu m=20 și x=2020, la s se adună doar valori care se împart exact la 2020. Ca să se afișeze chiar 2020, trebuie adunat exact o dată numărul 2020 și nimic altceva.

    Cea mai mică valoare: 2020. La primul pas n=2020 se împarte exact la 2020, iar m≠n, deci s devine 2020 și pn devine 2020. La pasul următor n ajunge 0, m ajunge 21, deci m>n și algoritmul se oprește. Pentru n=2019 nu se adună nimic: nici m, nici n nu ating vreodată un multiplu de 2020 înainte să se întâlnească, iar algoritmul afișează 0.

    Cea mai mare valoare: 4039. Pentru valori mari ale lui n, cel care ajunge pe 2020 nu mai e n, ci m: el crește din unu în unu de la 20 și, după 2000 de pași, ajunge la 2020, se adună la s și apoi sare din 2020 în 2020, oprind bucla. Asta merge atât timp cât n, coborând, nu trece el însuși peste un multiplu de 2020. Cum următorul multiplu este 4040, orice n de la 4040 în sus îl adaugă și pe acela: pentru n=4040 se afișează 6060, nu 2020. Ultima valoare care scapă este deci 4039.

  3. Exercițiul 1.c 10p Algoritmi în pseudocod

    Același algoritm ca la 1.a.

    S-a notat cu a%b restul împărțirii numărului natural a la numărul natural nenul b.

    Scrieți programul C/C++ corespunzător algoritmului dat.

    Vezi rezolvarea
    #include <iostream>
    using namespace std;
    
    int main()
    {
        int m, n, x;
        cin >> m >> n >> x;
        int s = 0, pm = 1, pn = 1;
        do
        {
            if (m % x == 0)
            {
                s = s + m;
                pm = x;
            }
            if (n % x == 0 && m != n)
            {
                s = s + n;
                pn = x;
            }
            m = m + pm;
            n = n - pn;
        } while (m <= n);
        cout << s;
        return 0;
    }
  4. Exercițiul 1.d 6p Algoritmi în pseudocod

    Același algoritm ca la 1.a.

    S-a notat cu a%b restul împărțirii numărului natural a la numărul natural nenul b.

    Scrieți în pseudocod un algoritm, echivalent cu cel dat, înlocuind structura repetă...până când cu o structură de alt tip.

    Vezi rezolvarea

    `` citește m,n,x (numere naturale nenule, m≤n) s←0; pm←1; pn←1 ┌cât timp m≤n execută │┌dacă m%x=0 atunci ││ s←s+m; pm←x │└■ │┌dacă n%x=0 și m≠n atunci ││ s←s+n; pn←x │└■ │ m←m+pm │ n←n-pn └■ scrie s ``

    repetă...până când execută corpul cel puțin o dată și se oprește când condiția devine adevărată; cât timp verifică înainte și se execută cât timp condiția e adevărată. De aceea condiția se neagă: ieșirea se făcea la m>n, deci repetarea se face cât timp m≤n.

    Înlocuirea e corectă aici fiindcă enunțul spune m≤n la citire: prima verificare trece sigur, deci corpul se execută cel puțin o dată, exact ca la repetă.

  5. Exercițiul 2 6p Tipul înregistrare (struct)

    Variabila p memorează, pentru fiecare dintre cele 20 de zone de parcare ale unui oraș, următoarele date specifice: identificatorul zonei, numărul de locuri închiriate pe parcursul lunii curente, precum și prețul practicat în zona respectivă pentru închirierea unui loc pentru o lună.

    Știind că expresiile C/C++ de mai jos au ca valori câte un număr natural, reprezentând identificatorul primei zone, respectiv suma obținută în urma închirierii pe parcursul lunii curente a tuturor locurilor de parcare din această zonă, scrieți definiția unei structuri cu eticheta parcare, care să permită memorarea datelor specifice unei zone de parcare, și declarați corespunzător variabila p.

    p[0].id       p[0].numar*p[0].pret
    Vezi rezolvarea

    ```cpp struct parcare { int id; int numar; int pret; };

    parcare p[20]; ```

    Numele câmpurilor nu sunt la alegere: expresiile din enunț — p[0].id și p[0].numar*p[0].pret — le impun. id e identificatorul zonei, numar numărul de locuri închiriate, pret prețul unui loc pe lună; produsul ultimelor două dă suma încasată în zonă.

    p este un tablou de 20 de astfel de structuri, câte una pentru fiecare zonă, cu indicii de la 0 la 19 — de aceea prima zonă e p[0].

  6. Exercițiul 3 6p Tablouri bidimensionale

    Variabila i este de tip întreg, iar variabila a memorează un tablou bidimensional cu 5 linii și 7 coloane, numerotate începând cu 0, cu elemente numere întregi.

    Fără a utiliza alte variabile decât cele menționate, scrieți o secvență de instrucțiuni în urma executării căreia să se afișeze pe ecran, separate prin câte un spațiu, indicii liniilor cu proprietatea că primul sau ultimul lor element are valoarea 2020.

    Vezi rezolvarea

    ``cpp for (i = 0; i < 5; i++) if (a[i][0] == 2020 || a[i][6] == 2020) cout << i << " "; ``

    Liniile au indicii de la 0 la 4, iar coloanele de la 0 la 6, deci primul element al liniei i este a[i][0], iar ultimul a[i][6].

    Enunțul cere „primul sau ultimul”, deci se folosește ||, nu &&: e de ajuns ca una dintre cele două valori să fie 2020. Cu || linia se afișează o singură dată chiar dacă amândouă elementele sunt 2020, fiindcă if-ul e testat o dată pe linie.

Subiectul al III-lea

30 de puncte
  1. Exercițiul 1 10p Subprograme

    Subprogramul duplicare are doi parametri:

    • n, prin care primește un număr natural (n∈[1,10^4]);

    • d, prin care furnizează numărul obținut prin duplicarea fiecărei cifre impare a lui n sau -1 dacă acesta nu are nicio cifră impară.

    Scrieți definiția completă a subprogramului.

    Exemplu: dacă n=2019, după apel d=201199.

    Vezi rezolvarea
    void duplicare(int n, int &d)
    {
        d = 0;
        int p = 1, impare = 0;
        while (n != 0)
        {
            int c = n % 10;
            d = d + c * p;
            p = p * 10;
            if (c % 2 == 1)
            {
                d = d + c * p;
                p = p * 10;
                impare = 1;
            }
            n = n / 10;
        }
        if (impare == 0)
            d = -1;
    }
  2. Exercițiul 2 10p Șiruri de caractere

    Un text are cel mult 100 de caractere, iar cuvintele sale sunt formate numai din litere mici ale alfabetului englez și sunt separate prin câte un spațiu.

    Scrieți un program C/C++ care citește de la tastatură un număr natural n (n∈[1,10^2]), apoi un text de tipul precizat mai sus, și afișează pe ecran cuvintele acestuia, pe rânduri separate, astfel încât primele poziții să fie ocupate de mulțimea formată de cele care au cel puțin n litere, iar următoarele poziții, în continuarea acestora, să fie ocupate de mulțimea celorlalte cuvinte.

    Cuvintele din aceeași mulțime sunt afișate într-o ordine oarecare, iar dacă una dintre cele două mulțimi este vidă, se afișează pe ecran doar mesajul nu exista.

    Exemplu: pentru n=5 și textul el mergea tot spre aleea pietruita datele afișate pot fi cele alăturate.

    mergea
    aleea
    pietruita
    el
    tot
    spre
    Vezi rezolvarea
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main()
    {
        int n;
        char s[101], cuvinte[51][101];
        cin >> n;
        cin.get();
        cin.getline(s, 101);
        int k = 0;
        for (char *p = strtok(s, " "); p != NULL; p = strtok(NULL, " "))
        {
            strcpy(cuvinte[k], p);
            k++;
        }
        int lungi = 0;
        for (int i = 0; i < k; i++)
            if ((int) strlen(cuvinte[i]) >= n)
                lungi++;
        if (lungi == 0 || lungi == k)
        {
            cout << "nu exista";
            return 0;
        }
        for (int i = 0; i < k; i++)
            if ((int) strlen(cuvinte[i]) >= n)
                cout << cuvinte[i] << "\n";
        for (int i = 0; i < k; i++)
            if ((int) strlen(cuvinte[i]) < n)
                cout << cuvinte[i] << "\n";
        return 0;
    }
  3. Exercițiul 3.a 2p Algoritmi eficienți

    Fișierul numere.in conține pe prima linie un număr natural n (n∈[2,10^9]), iar pe a doua linie un șir de cel mult 10^9 numere naturale din intervalul [1,n]. Numerele din șir sunt ordonate descrescător și sunt separate prin câte un spațiu. Se cere să se determine numărul valorilor naturale distincte din intervalul [1,n] care NU se găsesc în șirul menționat mai sus. Numărul determinat se afișează pe ecran. Proiectați un algoritm eficient din punctul de vedere al spațiului de memorie și al timpului de executare.

    Exemplu: dacă fișierul conține numerele se afișează pe ecran 7 (în șir nu se găsesc valorile 10 9 7 6 4 2 1).

    10
    8 8 8 5 3 3

    Descrieți în limbaj natural algoritmul proiectat, justificând eficiența acestuia.

    Vezi rezolvarea

    Timp de executare: O(n) · Memorie: O(1)

    Se citește șirul o singură dată, din fișier, fără să fie păstrat.

    Cum numerele sunt ordonate descrescător, toate aparițiile unei valori stau una lângă alta. Deci e de ajuns să compar fiecare termen cu cel dinaintea lui: dacă diferă, am dat peste o valoare nouă și cresc un contor distincte. La final, valorile din intervalul [1,n] care nu apar în șir sunt n - distincte.

    Pe exemplul din enunț: șirul 8 8 8 5 3 3 are trei valori distincte, iar 10 - 3 = 7.

    De ce e eficient: se face o singură trecere prin șir, deci timpul e proporțional cu numărul de termeni — O(n). Memoria e constantă, O(1): nu se păstrează șirul și nici un vector de apariții. Asta contează aici, fiindcă n poate fi 10⁹, iar un vector de frecvențe cu 10⁹ de poziții n-ar încăpea în memorie.

  4. Exercițiul 3.b 8p Algoritmi eficienți

    Același algoritm ca la 3.a.

    Exemplu: dacă fișierul conține numerele se afișează pe ecran 7 (în șir nu se găsesc valorile 10 9 7 6 4 2 1).

    Scrieți programul C/C++ corespunzător algoritmului proiectat.

    Vezi rezolvarea
    #include <fstream>
    #include <iostream>
    using namespace std;
    
    int main()
    {
        ifstream f("numere.in");
        long long n, x, anterior = -1, distincte = 0;
        f >> n;
        while (f >> x)
        {
            if (x != anterior)
                distincte++;
            anterior = x;
        }
        f.close();
        cout << n - distincte;
        return 0;
    }

Vrei să vezi ce notă ai lua la examen?

Intră în modul simulare: ai cronometru de 3 ore, codul ți se compilează pe loc, iar la final primești nota conform baremului.

Începe simularea3 ore

Ai găsit o problemă sau ai o idee?

Despre ce este vorba?