Sari la conținut
EduCamp

Probleme rezolvate pe categorii

Recursivitate și backtracking

4 probleme Date la examen între 2002 și 2025.

Înainte de probleme Tiparul problemelor rezolvate prin recursivitate și backtracking

Problemele din această categorie s-au dat la concursul de Titularizare, în 2002, 2005, 2009 și 2025. Cea din 2002 cere explicit recursivitatea. Cele din 2009 și 2025 cer toate soluțiile unei probleme: toate submulțimile cu m elemente, toate modurile de a plăti o sumă. Cerința „toate soluțiile” este semnul după care se recunoaște o problemă de backtracking. Cea din 2005 cere o singură soluție, cu cele mai puține elemente eliminate, găsită tot prin generarea variantelor.

ProblemaCe se cereMetoda
2002împărțirea unei mulțimi în două submulțimi, după o valoare Mrecursivitate
2005cele mai puține elemente eliminate dintr-un șir, ca suma celorlalte să fie întreagăbacktracking
2009toate submulțimile cu m elemente ale mulțimii {1, 2, …, n}backtracking
2025toate modurile de a plăti o sumă cu bancnotele dintr-o casieriebacktracking

Subprogramul recursiv

Un subprogram este recursiv dacă se autoapelează. Orice subprogram recursiv are două părți:

  • condiția de oprire: cazul în care rezultatul se cunoaște fără un nou apel, de exemplu o secvență fără niciun element;
  • autoapelul: subprogramul se apelează pentru o problemă mai mică, de același fel, de exemplu pentru secvența fără primul element.

Fiecare autoapel trebuie să apropie parametrii de condiția de oprire. La fiecare apel se creează în stivă un nivel nou, cu parametrii și variabilele locale ale apelului.

Modelul metodei backtracking

O soluție se reține într-un vector, numit stivă și notat st. Pozițiile lui se numesc niveluri și se completează pe rând, începând cu nivelul 1. Pentru nivelul k:

  1. se încearcă pe rând fiecare valoare posibilă, care se pune în st[k];
  2. se verifică condițiile interne, adică dacă valoarea pusă se potrivește cu cele de pe nivelurile 1, …, k − 1;
  3. dacă se potrivește:
    • dacă soluția este completă, se afișează;
    • altfel se trece la nivelul k + 1;
  4. după ce s-au încercat toate valorile, subprogramul se încheie și se revine la nivelul k − 1, care încearcă valoarea următoare.

Revenirea nu cere nicio instrucțiune: se face prin întoarcerea din apelul recursiv.

Programul-șablon

Programul de mai jos are părțile care se repetă. Problema aleasă este cea din 2009: submulțimile cu m elemente ale mulțimii {1, 2, …, n}. O submulțime se scrie cu elementele în ordine crescătoare, ca să nu fie afișată de mai multe ori: condiția internă cere ca st[k] să fie mai mare decât st[k − 1].

Problema 1 · Titularizare 2002 · Subiectul I.3 · 10 puncte Partiționarea recursivă a unei mulțimi după o valoare M

Ce cere problema

Numerele se împart în două grupe, după comparația cu M = 5:

Numărul132−16−119
Față de 5mai micmai micmai micmai micmai maremai micmai mare
Submulțimeaprimaprimaprimaprimaa douaprimaa doua

Prima submulțime este {1, 3, 2, −1, −11}, a doua este {6, 9}. Exemplul le afișează în ordine crescătoare, deci programul le ordonează înainte de afișare.

Enunțul cere ca partiționarea să se facă recursiv. Ea se poate scrie astfel: se cercetează primul element al secvenței, apoi aceeași prelucrare se face, prin autoapel, pentru secvența rămasă.

  • dacă primul element este mai mic decât M, el rămâne la începutul tabloului, iar autoapelul cercetează secvența de după el;
  • altfel, se schimbă cu ultimul element al secvenței și rămâne la sfârșitul tabloului, iar autoapelul cercetează secvența fără ultima poziție;
  • condiția de oprire: secvența nu mai are niciun element.

La sfârșit, elementele mai mici decât M ocupă începutul tabloului, iar celelalte, sfârșitul.

Încearcă împărțirea

Se scriu numerele și valoarea M. Fiecare pas este un apel al funcției imparte. În stânga se adună elementele mai mici decât M, în dreapta celelalte. Sub tablou sunt apelurile în desfășurare. După împărțire, fiecare parte se ordonează, apel cu apel.

Programul și descrierea lui

În stânga sunt subprogramele și programul principal, așa cum se scriu pe foaia de examen. În dreapta este descrierea lor în limbaj natural, cu pașii așezați pe niveluri după structura programului.

Problema 2 · Titularizare 2005 · Subiectul II.2 · 15 puncte Eliminarea unui număr minim de elemente pentru o sumă întreagă

Ce cere problema

Suma tuturor elementelor din exemplu este 4 − 1 + 3,7 + 2 + 5,25 − 0,7 = 13,25, deci nu este număr întreg. Se elimină cât mai puține elemente, până când suma celor rămase devine întreagă:

Elemente eliminateSuma celor rămaseÎntreagă?
niciunul13,25nu
49,25nu
3,79,55nu
5,258da

Un singur element eliminat ajunge, deci se afișează 4 -1 3.7 2 -0.7.

Backtracking-ul. Se încearcă întâi k = 0 elemente eliminate, apoi k = 1, k = 2 și așa mai departe. Pentru un k fixat, pozițiile eliminate se generează ca submulțimile cu k elemente ale mulțimii {1, 2, …, n}: în stiva st, în ordine crescătoare. Prima eliminare găsită folosește cel mai mic k, deci numărul minim de elemente eliminate.

Suma, calculată exact. Numerele reale nu se pot aduna exact în calculator: 0,1 + 0,2 nu dă exact 0,3. Fiecare element are cel mult 3 zecimale, deci, înmulțit cu 1000, devine un număr întreg: 3,7 devine 3700, iar −0,7 devine −700. Suma este întreagă dacă suma acestor numere se împarte exact la 1000.

Încearcă eliminarea

Se scriu numerele. La fiecare pas se arată o alegere a pozițiilor eliminate, tăiate în șir, și suma elementelor rămase. Generarea se oprește la prima sumă întreagă.

Programul și descrierea lui

În stânga sunt subprogramele și programul principal, așa cum se scriu pe foaia de examen, cu comentariile cerute de enunț. În dreapta este descrierea lor în limbaj natural, cu pașii așezați pe niveluri după structura programului.

Problema 3 · Titularizare 2009 · Subiectul II.2 · 10 puncte Submulțimile cu m elemente ale mulțimii {1, 2, ..., n}

Ce cere problema

Se aleg m numere diferite dintre 1, 2, …, n. Ordinea lor nu contează: {1, 2, 4} și {4, 2, 1} sunt aceeași submulțime. Pentru n = 4 și m = 3 sunt patru submulțimi:

SubmulțimeaNumărul lăsat deoparte
1 2 34
1 2 43
1 3 42
2 3 41

Exemplul din enunț arată numai trei dintre ele: lipsește submulțimea 1 3 4. Programul trebuie să le afișeze pe toate patru.

Ca o submulțime să fie afișată o singură dată, elementele ei se scriu în ordine crescătoare. Soluția se reține în stiva st: st[1] < st[2] < … < st[m].

Ce valori poate primi nivelul k. st[k] pornește de la st[k − 1] + 1, ca să fie mai mare decât elementul dinaintea lui. Valoarea cea mai mare este n − m + k: după nivelul k mai urmează m − k niveluri, care au nevoie de m − k valori mai mari. Pentru n = 4 și m = 3:

Nivelul kCea mai mare valoare, n − m + k
12
23
34

Încearcă generarea

Se scriu n și m. La fiecare pas, nivelul curent al stivei primește o valoare. Când toate cele m niveluri sunt completate, submulțimea se afișează. Când un nivel a primit toate valorile, se revine la nivelul dinainte.

Programul și descrierea lui

În stânga sunt subprogramele și programul principal, așa cum se scriu pe foaia de examen. În dreapta este descrierea lor în limbaj natural, cu pașii așezați pe niveluri după structura programului.

Problema 4 · Titularizare 2025 · Subiectul II.1 · 15 puncte Toate modurile de a plăti o sumă cu bancnotele dintr-o casierie

Cerința este rezolvată și pe pagina subiectului: Modalitățile de obținere a unei sume.

Ce cere problema

O modalitate de plată spune câte bancnote se iau din fiecare cupiură. Numărul se reține în tabloul b: b[i] este numărul de bancnote de c[i] lei. Pentru s = 10 și cupiurile 1, 2, 3, 5:

b[1], de 1 leub[2], de 2 leib[3], de 3 leib[4], de 5 leiSumaSe afișează
00022·5 = 102x5lei
31013 + 2 + 5 = 103x1leu 1x2lei 1x5lei
10301 + 9 = 101x1leu 3x3lei

Ordinea în care se numără bancnotele nu contează: o bancnotă de 5 lei și una de 3 lei dau același tablou b, oricare ar fi numărată prima. Cupiurile cu b[i] = 0 nu se afișează.

Backtracking-ul. Tabloul b este stiva. Nivelul i corespunde cupiurii c[i] și primește pe rând valorile 0, 1, 2, …, cât timp bancnotele încap în suma rămasă de plătit. Soluția este completă când toate cupiurile au primit o valoare, iar suma rămasă este 0.

Încearcă generarea

Se scriu suma și cupiurile. La fiecare pas, cupiura curentă primește un număr de bancnote, iar suma rămasă scade. O soluție găsită se afișează pe ecran. O alegere care lasă la sfârșit o sumă diferită de 0 se respinge.

Programul și descrierea lui

În stânga sunt subprogramele și programul principal, așa cum se scriu pe foaia de examen. În dreapta este descrierea lor în limbaj natural, cu pașii așezați pe niveluri după structura programului.

#include <iostream>
using namespace std;

int n, s;
int c[11];   // cupiurile din casierie
int b[11];   // cate bancnote se folosesc din fiecare cupiura

void tipar() {
    for (int i = 1; i <= n; i++)
        if (b[i] > 0) {
            cout << b[i] << "x" << c[i];
            if (c[i] == 1)
                cout << "leu ";
            else
                cout << "lei ";
        }
    cout << endl;
}

Subprogramul tipar, fără parametri

  • lucrează cu variabilele globale n, c și b
  • pentru fiecare cupiură i, de la 1 la n
    • dacă b[i] > 0, cupiura a fost folosită
      • se scrie numărul de bancnote, litera x și cupiura, fără spații între ele
      • dacă cupiura este 1, se scrie leu și un spațiu
      • altfel se scrie lei și un spațiu
  • endl trece la linia următoare: fiecare soluție pe linia ei
void construieste(int i, int ramas) {
    if (i > n) {
        if (ramas == 0)
            tipar();
        return;
    }
    for (int k = 0; k * c[i] <= ramas; k++) {
        b[i] = k;
        construieste(i + 1, ramas - k * c[i]);
    }
}

Subprogramul construieste(i, ramas), care completează nivelul i

  • ramas este suma rămasă de plătit după cupiurile 1, …, i − 1
  • dacă i > n, toate cupiurile au primit un număr de bancnote
    • dacă ramas este 0, b conține o soluție și se apelează tipar
    • altfel nu este o soluție
    • în ambele cazuri, apelul se încheie
  • altfel, pentru fiecare număr k de bancnote, de la 0 cât timp k·c[i] ≤ ramas
    • b[i] primește k
    • se completează nivelul i + 1, prin autoapel, cu suma rămasă scăzută cu k·c[i]
  • la ieșirea din for, se revine la nivelul i − 1
int main() {
    cin >> s >> n;
    for (int i = 1; i <= n; i++)
        cin >> c[i];

    construieste(1, s);

    return 0;
}

Programul principal

  • citește s, n și cele n cupiuri
  • pornește generarea de la prima cupiură, cu toată suma s de plătit

Materialul acesta se citește pe educamp.ro și nu se tipărește.

Vezi problemele rezolvate ca și cursant

Lista categoriilor, cu problemele date la examen în fiecare, se citește gratuit și integral. Pentru a citi rezolvările, te înscrii la cursul de pregătire.

Te înscrii, alegi în cont când începe cursul și plătești prima lună prin transfer bancar. Pentru a vedea mai întâi ce probleme s-au dat, poți reveni la lista categoriilor.

Acces la suportul de curs

25 RON / lună, fără ședințe

Ce cuprinde:

  • Acces la toate lecțiile disponibile
  • Suport de curs pentru tematica științifică și metodică
  • Exemple, explicații și exerciții
  • Acces la materialele noi adăugate pe platformă
  • Posibilitatea de a adresa întrebări

Poți alege și variante cu ședințe online, în grup sau individuale. Le vezi pe pagina cursului.

Ai întrebări sau o problemă? Scrie-mi pe WhatsApp, la 0745 874 576.

Înscrie-te Alte varianteCum plătești Login