Sunteți pe pagina 1din 26

LICEUL TEORETIC “MIHAIL KOGĂLNICEANU” VASLUI

LUCRARE PENTRU OBȚINEREA


ATESTATULUI PROFESIONAL
= profil matematică – informatică =

Profesor îndrumator, Absolvent,


Nicu Vlad-Laurențiu Vîrlan Victor-Ștefan

Vaslui, 2022

1
PROGRAMAREA DINAMICĂ

2
INTRODUCERE
Am ales să scriu despre această tematică deoarece sunt pasionat de informatică și prin acest
proiect aș dori sa evidențiez în primul rând modul de abordare și gândire a unei probleme de natură
algoritmică, dar și diverse metode de implementare în limbajul C++. Prin urmare, aceasta lucrare
conține mai mult probleme de natură algoritmică care au aplicativitate chiar si in unele dintre
activitățile noastre zilnice, rezolvările lor bazându-se pe metoda programării dinamice.
Programarea Dinamică (PD) este o metodă de rezolvare a unor probleme de optimizare în care se
opereaza pe FAZE (SECVENTE) numite în continuare PERIOADE.
Prin aplicarea acestei metode, soluţia optimă se obtine în urma rezolvarii unui şir de probleme de
optimizare LEGATE INTRE ELE, fiecare de de dimensiune si complexitate mult mai mică decat
problema originală. Deşi teoretic PD este aplicabilă oricarei probleme de optimizare
multidimensională, totuşi metoda a dat rezultate foarte bune - în sensul uşurării rezolvării - numai
pe anumite clase de probleme cum sunt cele care modelează evoluţia în timp a unui proces
economic sau probleme de alocare (repartiţie). Programarea dinamică rezolvă problemele prin
descompunerea lor în subprobleme şi prin combinarea rezolvărilor acestora (termenul
„programare" se referă aici la o metodă tabulară). Spre deosebire de divide et impera, care
considera că subproblemele sunt independente, programarea dinamică se aplică atunci când
subproblemele nu sunt independente. Într-un astfel de caz, divide et impera ar efectua calcule
redundante, rezolvând fiecare subproblemă ca şi când nu ar mai fi întâlnit-o. Programarea
dinamică, însă, salvează rezultatul fiecărei subprobleme într-o tabelă, evitând astfel rezolvarea
redundantă a aceleiaşi probleme.

3
CONSIDERAȚII TEORETICE

A. Descrierea algoritmului

Programarea dinamică se aplică în general problemelor de optimizare, atunci când dorim să


determinăm rapid soluţia optimă pentru o problemă. De fapt, aplicând această tehnică determinăm una
din soluţiile optime, problema putând avea mai multe soluţii optime. Aplicarea acestei tehnici de
programare poate fi descompusă în următoarea secvenţă de paşi:

1. Descoperirea structurii şi "măsurii" pe care o are o soluţie optimă.


2. Definirea recursivă a valorii care caracterizează o soluţie optimă.

3. Calcularea "de jos în sus" a acestei valori.


4. Construirea soluţiei optime pornind de la calculele efectuate anterior.
Primii trei paşi reprezintă baza programării dinamice. Dacă trebuie să aflăm doar valoarea soluţiei
optime, ultimul pas nu mai este necesar. De aceea, pentru cazul în care dorim să determinăm şi soluţia
optimă poate fi nevoie de construirea unor structuri de date suplimentare.

4
O problemă ar putea fi rezolvată prin programare dinamică dacă poate fi descompusă în
subprobleme asemănătoare de dimensiuni mai mici şi soluţia optimă a problemei depinde de
soluţiile optime ale subproblemelor sale. Dar acest lucru nu garantează că programarea dinamică e
soluţia, sunt situaţii în care se poate aplica cu succes metoda Greedy sau Divide et Impera. Dacă
însă subproblemele nu sunt independente, ci se suprapun (Overlapping subproblems), un algoritm
Divide et Impera ar duce la un timp mare de execuţie, pe motiv că o aceeaşi subproblemă se rezolvă
de mai multe ori. Să analizăm calculul coeficientului binomial:
O funcţie recursivă ar arăta astfel:

Multe din valorile comb(n,k) sunt calculate în mod repetat, ceea ce face această abordare neeficientă.
În astfel de situaţii în care există un număr relativ mic de subprobleme independente, restul
suprapunându-se, soluțiile subproblemelor nu vor fi calculate decât o singură dată şi vor fi reţinute
într-o structură de date internă (de obicei un tablou). În acest caz, funcţia se poate scrie:

5
Acelaşi lucru se poate spune despre calculul termenilor şirului lui Fibonacci: o funcţie recursivă ar
calcula în mod repetat aceiaşi termeni, pe când un vector poate reţine termenii calculaţi o singură dată:

Putem spune că metoda Divide et Impera operează de sus în jos (top-down), descompunând problema
în subprobleme din ce în ce mai mici, pe care le rezolvă apoi separat. Programarea dinamică operează
de jos în sus (bottom-up). Se porneşte de obicei de la cele mai mici subprobleme. Combinând soluţiile
lor, se obţin soluţii pentru subprobleme din ce in ce mai mari, până se ajunge, în final, la soluţia
problemei iniţiale. Rezolvarea unei probleme prin programare dinamică presupune următorii paşi:
• Se identifică subproblemele problemei date;
• Se alege o structură de date suplimentară, care să reţină soluţiile subproblemelor;
• Se caracterizează substructura optimală a problemei printr-o relaţie de recurenţă;
• Pentru a determina soluţia optimă, se rezolvă relaţia de recurenţă în mod bottom-up (se rezolvă
subproblemele în ordinea crescătoare a dimensiunii lor);

6
APLICAȚII

I. Dificulate – UȘOR

(SumTri-pbinfo)

Cerința
Se consideră un triunghi de numere naturale format din n linii.Prima linie conține un număr, a doua
linie conține 2 numere, etc. ultima linie n, conține n numere. În acest triunghi se pot calcula diverse
sume cu n elemente, astfel:
 termenul i al sumei se află pe linia i din triunghi
 pentru un anumit termen al sumei, termenul următor se află pe linia următoare și pe aceeași
coloană, sau pe coloana imediat următoare spre dreapta.
Să se determine cea mai mare sumă care se poate obține în acest mod.

Date de intrare
Fişierul de intrare sumtri.in conţine pe prima linie numărul n. Fiecare dintre următoarele n linii conține
câte o linie a triunghiului.

Date de ieșire
Fişierul de ieşire sumtri.out va conţine pe prima linie numărul S, reprezentând cea mai mare sumă
care se poate obține.

Restricții și precizări
 1 ≤ n ≤ 100
 numerele din triunghi sunt mai mici decât 1000

7
Rezolvare

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cassert>
using namespace std;
#define NN 105

ifstream fin("sumtri.in");
ofstream fout("sumtri.out");

int n, a[NN][NN], S[NN][NN];

int main(){
fin >> n;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
assert(fin >> a[i][j]);
S[1][1] = a[1][1];
for(int i=2 ; i<=n ; ++i){
S[i][1] = S[i-1][1]+a[i][1];
S[i][i] = S[i-1][i-1]+ a[i][i];
for(int j=2 ; j < i ; ++j)
if(S[i-1][j-1]>S[i-1][j])
S[i][j] = S[i-1][j-1] + a[i][j];
else
S[i][j] = S[i-1][j] + a[i][j];
}
int pmax = 1;
for(int i = 2 ; i <= n ;++i)
if(S[n][i]>S[n][pmax])
pmax = i;
fout << S[n][pmax];
for(int i=1;i<=n;++i){
for(int j=1;j<=i;++j)
cout << S[i][j] << " ";
cout << endl;
}
return 0;
}

8
(SCLM-pbinfo):

Cerinţa
Se dă un șir cu n elemente, numere naturale. Determinați un cel mai lung subșir crescător al șirului

Date de intrare
Fişierul de intrare sclm.in conţine pe prima linie numărul n, iar pe a doua linie n numere naturale
separate prin spaţii, reprezentând elementele șirului

Date de ieşire
Fişierul de ieşire sclm.out va conţine pe prima linie numărul L, reprezentând lungimea maximă a unui
subșir crescător. A doua linie va conține L numere cu valori între 1 și n, ordonate crescător,
reprezentând indicii elementelor din șirul dat care dau subșirul crescător de lungime maximă.

Restricţii şi precizări
 1 ≤ n ≤ 1000
 numerele de pe a doua linie a fişierului de intrare vor fi cel mult egali cu 1.000.000
 orice șir de indici care duce la subșir crescător de lungime maximă este corect

Exemplu
sclm.in
10
5 10 7 4 5 8 9 8 10 2
sclm.out
5
13679

Explicație
Elementele șirului care corespund acestor indici sunt: 5 7 8 9 10.

9
Rezolvare

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cassert>
using namespace std;
#define NN 1005

ifstream fin("sclm.in");
ofstream fout("sclm.out");

int n, a[NN], L[NN], Next[NN];

int main(){
assert(fin >> n );
for(int i=1 ; i<=n ; ++i)
assert(fin >> a[i]);
L[n] = 1;
Next[n] = -1;
for(int i=n-1 ; i>0 ; i--)
{
L[i] = 1, Next[i] = -1;
for(int j=i+1 ; j<=n; ++j)
if(a[i]<=a[j] && L[i]<L[j]+1)
L[i] = L[j] + 1, Next[i] = j;
}
int pmax = 1;
for(int i=1 ; i<=n ; ++i)
if(L[pmax] < L[i])
pmax = i;
fout << L[pmax] << endl;
for(int i=pmax ; i!=-1 ; i = Next[i])
fout << i << " ";
return 0;
}

10
(Cladire-pbinfo)

Cerinţa
Se consideră o clădire de formă dreptunghiulară formată din n*m camere, dispuse pe n linii și m
coloane. Intrarea în clădire este în camera de coordonate (1,1), iar ieșirea în camera de coordonate
(n,m). Din orice cameră (i,j) se poate ajunge numai în camerele (i+1,j) sau (i,j+1). Determinați în câte
moduri se poate ajunge din camera (1,1) în camera (n,m).

Deoarece numărul de posibilități poate fi foarte mare, se cere doar restul acestui număr la împărțirea
cu 990.

Date de intrare
Fişierul de intrare cladire.in conţine pe prima linie numerele n m.

Date de iesire

Fişierul de ieşire cladire.out va conţine pe prima linie numărul P, reprezentând în câte moduri se poate
ajunge din camera (1,1) în camera (n,m), număr afișat modulo 9901.

Restricţii şi precizări
 1 ≤ n , m ≤ 1000

Exemplu
cladire.in
33
cladire.out
6

11
Rezolvare
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cassert>
using namespace std;
#define NN 1005

ifstream fin("cladire.in");
ofstream fout("cladire.out");

int n, m, a[NN][NN];

int main(){
fin >> n >> m;
for(int i=1;i<=n;++i)
a[i][1] = 1;
for(int j=1;j<=m;++j)
a[1][j] = 1;
for(int i=2;i<=n;++i)
for(int j=2;j<=m;++j)
a[i][j] = (a[i-1][j] + a[i][j-1]) % 9901;
fout << a[n][m];
return 0;
}

12
(Radiera-pbinfo):

Un numar natural se numeste “numar scara” daca toate cifrele lui sunt ordonate crescator, de la stanga
la dreapta. De exemplu 11223569 este un “numar scara”, dar 98873 si 122429 nu sunt.

Cerința
Mihnea primeste o radiera si o foaie pe care este scris un sir de cifre nenule. El trebuie sa stearga cat
mai putine cifre cu proprietatea ca daca lipim cifrele ramase in ordinea din sir vom avea un “numar
scara”. De exemplu daca avem sirul 2 1 1 3 4 8 5 , stergem cifra de pe pozitia 1 si cea de pe pozitia 6,
lipim cifrele ramase si ne rezulta numarul 11345 care este un “numar scara” , deci numarul minim de
stergeri este 2.

Să se determine numărul minim de ștergeri pe care trebuie să le facă Mihnea.

Date de intrare
Fișierul de intrare radiera.in conține pe prima linie numărul N, iar pe a doua linie n numere naturale
separate prin spații, reprezentand sirul.

Date de ieșire
Fișierul de ieșire radiera.out va conține pe prima linie numarul minim de stergeri care trebuie sa le faca
Mihnea.

Restricții și precizări
 3 ≤ N ≤ 1000

Exemplu
radiera.in
7
2113485
radiera.out
2

Explicație
Mihnea sterge cifra 2 si cifra 8.

13
Rezolvare

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

ifstream fin("radiera.in");
ofstream fout("radiera.out");

const int N=1005;

int n,a[N],lg[N],t[N],lmax,poz;

void in(){
fin >> n;
for(int i=1; i<=n; ++i)
fin >> a[i];
}

int sclm1(){
int i,j;
lg[1]=1; t[1]=0;
for(int i=1; i<=n; ++i){
lg[i]=1;t[i]=0;
for(int j=1; j<i;++j)
if(a[i]>=a[j] && lg[j]+1>lg[i]){
lg[i]=lg[j]+1;
t[i]=j;
}
}
for(int i=1; i<=n; ++i)
if(lg[i]>lmax) lmax=lg[i],poz=i;
return n-lmax;
}

int main()
{
in();
fout << sclm1() << "\n";
return 0;
}
14
(Bete-pbinfo):

Cerinţa

Gigel a primit cadou n bețe de diferite lungimi. Neștiind ce să facă cu ele, se întreabă dacă poate alege
dintre bețele date o parte, astfel încât, lipindu-le, să obțină un băț de lungime S.

Date de intrare
Fişierul de intrare bete.in conţine pe prima linie numerele n S, separate printr-un spațiu, iar pe a doua
linie n numere naturale separate prin spaţii, reprezentând lungimile bețelor.

Date de ieşire

Fişierul de ieşire bete.out va conţine pe prima linie mesajul DA, dacă pot fi alese bețe astfel încât
lungimea totală a lor să fie S, și NU în caz contrar. Dacă este posibilă alegerea bețelor, urmează o
alegere posibilă: pe linia a doua a fișierului se va afla un număr m, numărul de bețe alese; pe linia a
treia se vor afla m numere distincte cuprinse între 1 și n, reprezentând numerele de ordine ale bețelor
alese astfel încât suma lungimilor să fie S.

Restricţii şi precizări
 1 ≤ n ≤ 100

 numerele de pe a doua linie a fişierului de intrare vor fi numere naturale nenule mai mici decât
100

Exemplu 1:
bete.in
5 10
15368
bete.out
DA
3
134

15
#include <bits/stdc++.h>
using namespace std;
ifstream fin("bete.in");
ofstream fout("bete.out");

int n,S;
int v[103];
int a[10003];
int sol[103];

int main()
{
int i,ult;
int x,j,k;
fin >> n >> S;
for (i = 1; i <= n; i++)
fin >> v[i];
a[v[1]] = 1; ult = v[1];
for (i = 2; i <= n; i++)
{
x = v[i];
for (j = ult; j >= 1; j--)
if (a[j] != 0 && j+x <= S) a[j+x] = i;
a[x] = i;
ult = min(S,ult+x);

}
if (a[S] == 0) fout << "NU\n";
else
{
fout << "DA\n";

k = 0;
while (S != 0)
{
sol[++k] = a[S];
S-=v[a[S]];
}
fout << k << "\n";
for (i = k; i >= 1; i--)
fout << sol[i] << " ";
}
return 0;

16
}

II. Dificultate – MEDIU

(Rucsac1-pbinfo):

Cerința
Într-un magazin sunt n obiecte; pentru fiecare se cunoaște greutatea G și valoarea V. Un hoț intră în
magazin având un rucsac ce poate transporta o greutate maximă GMax. El va fura anumite obiecte,
astfel încât suma greutăților obiectelor furate să nu depășească GMax.

Să se stabilească câștigul maxim pe care îl poate obține hoțul. Câștigul este egal cu suma valorilor
obiectelor furate.

Date de intrare
Programul citește de la tastatură numerele naturale n GMax, iar apoi n perechi de valori G V,
reprezentând greutatea, respectiv valoarea fiecărui obiect.

Date de ieșire

Programul va afișa pe ecran numărul C, reprezentând câștigul maxim pe care îl poate obține hoțul.

Restricții și precizări
 1 ≤ n ≤ 1.000;
 1 ≤ G, V, GMax ≤ 10.000

Exemplu
Intrare
5 20
23
45
58
34
9 10
Ieșire

17
26

18
Rezolvare
#include <iostream>
#include <fstream>
using namespace std;
struct obiect{
int g,v;
};
const int N = 1005;
const int GMAX = 10005;
obiect a[N];
int n,G;
int C[N][GMAX];
void Citire(){
cin >> n >> G;
for(int i=1; i<=n; ++i)
cin >> a[i].g >> a[i].v;
}
void PD(){
int i,j;
for(i=1; i<=n; ++i)
for(j=1; j<=G; ++j)
if(a[i].g>j) C[i][j]=C[i-1][j];
else C[i][j]=max(C[i-1][j],a[i].v+C[i-1][j-a[i].g]);
}
int main()
{
Citire(); PD();
cout << C[n][G];
return 0;
}

19
(Rucsac2-pbinfo):

Cerința
Într-un magazin intergalactic sunt n tipuri de obiecte, o infinitate din fiecare; pentru fiecare se
cunoaște greutatea G și valoarea V. Un hoț intră în magazin având un rucsac ce poate transporta o
greutate maximă GMax. El va fura anumite obiecte, astfel încât suma greutăților obiectelor furate să
nu depășească GMax.

Să se stabilească câștigul maxim pe care îl poate obține hoțul. Câștigul este egal cu suma valorilor
obiectelor furate.

Date de intrare
Programul citește de la tastatură numerele naturale n și GMax, iar apoi n perechi de valori G V,
reprezentând greutatea, respectiv valoarea fiecărui tip de obiect.

Date de ieșire
Programul va afișa pe ecran numărul C, reprezentând câștigul maxim pe care îl poate obține hoțul.

Restricții și precizări
 1 ≤ n ≤ 1 000;
 1 ≤ G, V, GMax ≤ 10 000.

Exemplu
Intrare
4 10
6 30
3 14
4 16
29
Ieșire
48

Explicație
Hoțul poate lua un obiect de tipul 1 și două obiecte de tipul 4 (30+2∗9=48)

20
#include <bits/stdc++.h>
using namespace std;

int main()
{
int n, GMax, i, j;
int V[1005], G[1005];

cin >> n >> GMax;


for(i = 0; i < n; ++i)
cin >> G[i] >> V[i];

int rucsac[10005];
rucsac[0] = 0; // Cu 0kg, obtinem valoarea 0
for(j = 1; j <= GMax; ++j)
{
rucsac[j] = 0;
for(i = 0; i < n; ++i)
if(G[i] <= j)
rucsac[j] = max(rucsac[j], rucsac[j-G[i]] + V[i]);
}

cout << rucsac[GMax];


return 0;
}

21
III. Dificultate – DIFICIL

(SCLM2-pbinfo):

Cerința
Vlad și Alex sunt doi prieteni foarte buni. Aceștia sunt pasionați de jocuri și vor să vă propună unul: Se
dau n numere naturale sub forma unui șir. Un subșir se numește “corect” dacă este ordonat crescător.
Pentru a câștiga, jucătorul trebuie să ghicească lungimea maxima a unui astfel de subșir. Deoarece nu
le place sa piardă, cei doi vă cer ajutorul.

Date de intrare
Fișierul de intrare sclm2.in conține pe prima linie numărul n, iar pe a doua linie n numere naturale
separate prin spații.

Date de ieșire
Fișierul de ieșire sclm2.out va conține pe prima linie numărul l, lungimea maximă a unui subșir
“corect”.

Restricții și precizări
 1 ≤ n ≤ 100.000
 numerele de pe a doua linie a fișierului de intrare vor fi mai mici decât 1.000.000.000

Exemplu
sclm2.in
7
3143569
sclm2.out
5

Explicație
Subșirul “corect” de lungime maxima este: 1 3 5 6 9 și are lungime 5.

22
Rezolvare
#include <bits/stdc++.h>

using namespace std;

int D[100000],A[100000];

ifstream f("sclm2.in");
ofstream g("sclm2.out");

int main()
{
long long k,n;
f>>n;
for(int i=1; i<=n; i++)
f>>A[i];
k = 1, D[k] = A[1];
for(int i = 2 ; i <= n ; i ++)
{
if(A[i] >= D[k])
D[++k] = A[i];
else
{
int st = 1, dr = k, poz = k + 1;
while(st <= dr)
{
int m = (st + dr) / 2;

23
if(D[m] > A[i])
poz = m, dr = m - 1;
else
st = m + 1;
}
D[poz] = A[i];
}
}
g<<k;
}

24
BIBLIOGRAFIE

https://tudormaxim13.wixsite.com/website
https://www.pbinfo.ro/articole/17951/programare-dinamica-introducere
https://www.pbinfo.ro/probleme/categorii/158/programare-dinamica-probleme-de-numarare
https://www.pbinfo.ro/probleme/categorii/54/programare-dinamica-probleme-diverse-de-programare-
dinamica

25
CUPRINS

INTRODUCERE.........................................................................................................................3
CONSIDERAȚII TEORETICE...............................................................................................................4
Descrierea algoritmului................................................................................................................4
APLICAȚII.................................................................................................................................7
Ușor - SumTri..............................................................................................................................7
Ușor - SCLM................................................................................................................................9
Ușor - Cladire...............................................................................................................................11
Ușor - Radiera..............................................................................................................................13
Ușor - Bete...................................................................................................................................15
Mediu - Rucsac1..........................................................................................................................17
Mediu - Rucsac2..........................................................................................................................19
Dificil - SCLM2............................................................................................................................21
BIBLIOGRAFIE.........................................................................................................................24

26

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