Lectia 9 - Programare Dinamica
Lectia 9 - Programare Dinamica
Lectia 9 - Programare Dinamica
Mihaela Ildegez
Programare dinamică
Programarea dinamică este în principal o optimizare a recursivităților simple. Oriunde vedem o soluție recursivă
care are apeluri repetate pentru aceleași intrări, o putem optimiza folosind programare dinamică.
Ideea este de a stoca pur și simplu rezultatele subproblemelor, astfel încât să nu fie necesar să le re-calculăm atunci
când va fi nevoie mai târziu. Această simplă optimizare reduce complexitatea timpului de la exponențial la polinomial.
De exemplu, dacă scriem o soluție recursivă simplă pentru numere Fibonacci, obținem o complexitate de timp
exponențială și dacă o optimizăm stocând soluții de subprobleme, complexitatea timpului se reduce la liniar.
Urmează două moduri diferite de a stoca valorile, astfel încât valorile unei sub-probleme să poată fi reutilizate. Aici,
vom discuta două modele de rezolvare a problemei DP:
Suprapunerea Subproblemelor
Găsirea proprietății optimă a subproblemei
#385 SumTri
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:
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
sumtri.in sumtri.out
5 21
4
14
213
9443
45221
Exemplu:
Vom lucra în paralel cu două matrice astfel A-matricea inițială și B matricea în care vom construi sumele parțiale:
A 1 2 3 4 5 B 1 2 3 4 5
Linia 1 4 Linia 1 21
Linia 2 1 4 Linia 2 17 14
Linia 3 2 1 3 Linia 3 16 10 9
Linia 4 9 4 4 3 Linia 4 14 9 6 5
Linia 5 4 5 2 2 1 Linia 5 4 5 2 2 1
+
Substructura optimă a sumei maxime în triunghi este dată de relația:
Pentru etapa 1 se trasează copie în matricea B linia n din matricea A. Vom forma apoi triunghiul rezultat
de la bază la vârf, cu sumele maxime care se pot forma cu fiecare număr folosind relația din structura de optim.
Soluția o vom găsi în B[1][1].
#include <fstream>
int main()
{
int A[101][101]={0};///matricea initiala
int B[101][101]={0};///matricea suma
int n;
f>>n;
for(int i=1;i<=n; i++)
for(int j=1;j<=i; j++)
f>>A[i][j];
///copii linia i din matricea A in matricea B
for(int i=1;i<=n; i++)
B[n][i]=A[n][i];
g<<B[1][1];///afisez rezultatul
return 0;
}
#386 SumTri1
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 mică sumă care se poate obține în acest mod și numerele care o alcătuiesc.
Date de intrare: Fișierul de intrare sumtri1.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 sumtri1.out va conține pe prima linie numărul S, reprezentând cea mai mică
sumă care se poate obține. Pe linia următoare se vor afla n numere din triunghi, care sunt termenii sumei minime,
după regula descrisă mai sus.
Restricții și precizări
1 ≤ n ≤ 100
numerele din triunghi sunt mai mici decât 1000
Exemplu:
sumtri1.in sumtri1.out
5 17
4 44342
14
993
9443
45256
Indicații: problema e asemănătoare cu cea precedentă. În loc să calculăm suma maximă vom calcula suma
minimă. Acest procedeu se va realiza aproximativ la fel.
T 1 2 3 4 5
Linia 1 2
Linia 2 1 3
Linia 3 2 2 3
Linia 4 1 3 3 4
Linia 5 0 0 0 0 0
#include <fstream>
using namespace std;
ifstream f("sumtri.in");
ofstream g("sumtri.out");
int main()
{
int A[101][101]={0}; ///matricea initiala
int B[101][101]={0}; ///matricea suma
int T[101][101]={0};///matricea traseu
int n, i, j;
f>>n;
for(i=1;i<=n; i++)
for( j=1;j<=i; j++)
f>>A[i][j];
///copii linia i din A in B
for(i=1;i<=n; i++)
B[n][i]=A[n][i];
Date de ieșire: 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 cladire.out
33 6
Explicație:
Indicații:
Vom construi o matrice A cu n linii și m coloane în care fiecare element A[i,j] reprezintă numărul de posibilități
prin care se poate ajunge în camera de coordonate i, j, modulo 9901.
Se constată că:
în oricare dintre camerele de pe linia 1 sau de pe coloana 1 se poate ajunge în exact un mod (venind din
camera din stânga, respectiv de sus), deci A[1,j]=1 și A[i,1]=1.
într-o cameră de coordonate i,j care nu se află pe prima linie sau pe prima coloană se poate intra din
camera din stânga, de coordonate i,j-1 sau din camera de deasupra, de coordonate i-1,j, deci A[i,j]=A[i-
1,j]+A[i,j-1].
Substructura optimă a numărului de moduri în care se poate ajunge în camera i,j este:
Operațiile necesare pentru determinarea valorilor din matrice se fac modulo 9901. Rezultatul se află în
elementul A[1, 1].
#include <fstream>
using namespace std;
ifstream f("cladire.in");
ofstream g("cladire.out");
int a[1001][1001];
int main()
{
int n, m, i, j;
f>>n>>m;
for(i=n-1;i>=1;i--)
for(j=m-1;j>=1;j--)///acum calculez nr de posibilitati din fiecare camera
a[i][j]=(a[i+1][j]+a[i][j+1])%9901;
#393 Clădire 1
La fel ca problema dinainte, dar exista camere blocate prin care nu pot trece
#include <fstream>
using namespace std;
ifstream f("cladire1.in");
ofstream g("cladire1.out");
int a[1001][1001];
int main()
{
int n, m, i, j, k, t;
f>>n>>m;
f>>k;
for(t=1;t<=k;t++)
{
f>>i>>j;
a[i-1][j-1]=-1;
}
//pana aici a fost citirea
Definim prefixul i al șirului X ca fiind șirul format din primele i elemente din șir
Prefixul Xi= x1 x2 x3 ... xi ... xm
Poziții: 1 2 3 m m
0, 𝑑𝑎𝑐ă 𝑖 = 0 ș𝑖 𝑗 = 0
𝑐[𝑖][𝑗] = { 𝑐[𝑖 − 1][𝑗 − 1] + 1, 𝑑𝑎𝑐ă 𝑥𝑖 = 𝑦𝑗 ș𝑖 𝑖 ≠ 0 ș𝑖 𝑗 ≠ 0
max(𝑐[𝑖][𝑗 − 1], 𝑐[𝑖 − 1][𝑗]) , 𝑑𝑎𝑐ă 𝑥𝑖 ≠ 𝑦𝑗 ș𝑖 𝑖 ≠ 0 ș𝑖 𝑗 ≠ 0
Vom utiliza un tablou bidimensional C cu m+1 linii( de la 0 la m) si n+1 coloane(de la 0 la n) al cărui
elemente le vom calcula în ordinea crescătoare a liniilor( de sus în jos pe linii și de la stânga la dreapta pe coloane).
Inițial vom completa linia 0 și coloana 0 cu valoarea 0. Lungimea maximă a CMLSC(Xm, Yn) se va găsi în C[m][n].
Dacă dorim să afișăm și CMLSC atunci vom utiliza o funcție recursivă care va reconstitui traseul.
funcția afișareCMLSC(B, X, i, j)
daca i≠0 și j≠0 atunci
daca B[i][j]=1 atunci
afișareCMLSC(B, X, i-1, j-1)
scrie X[i],’ ’
altfel
daca B[i][j]=2 atunci
afișareCMLSC(B, X, i-1, j)
altfel
afișareCMLSC(B, X, i, j-1)
sfDaca
sfDaca
sfDaca
sffuncție
#2528 LungimeSubsirComunMaximal
Se dau două șiruri de caractere, litere mici ale alfabetului englez. Să se determine lungimea celui mai lung
subșir comun al lor.
Date de intrare : Fișierul de intrare lungimesubsircomunmaximal.in conține cele două șiruri de caractere, unul
pe prima linie, unul pe cea de-a doua.
Date de ieșire
Fișierul de ieșire lungimesubsircomunmaximal.out va conține pe prima linie numărul L, reprezentând lungimea
subșirului comun maximal al celor două cuvinte.
Restricții și precizări
1 ≤ lungimea unui șir ≤ 1000
Exemplu:
lungimesubsircomunmaximal.in lungimesubsircomunmaximal.out
aaabcd 4
agahbdert
Indicații:
Vom aplica algoritmul de mai sus dar fără a memora direcțiile de construire a soluției, deoarece se cere
doar determinarea lungimii CMLSC. Aveți grija de faptul că primul element în șirurile de intrare ocupă poziția 0.
#include <fstream>
#include <cstring>
using namespace std;
ifstream f("lungimesubsircomunmaximal.in");
ofstream g("lungimesubsircomunmaximal.out");
int main()
{
char X[1001],Y[1001];
short int C[1002][1002]={0};
f.getline(X,1001);
f.getline(Y,1001);
int m=strlen(X);
int n=strlen(Y);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if(X[i-1]==Y[j-1])
C[i][j]=1+C[i-1][j-1];
else
if(C[i-1][j]>=C[i][j-1])
C[i][j]=C[i-1][j];
else
C[i][j]=C[i][j-1];
g<<C[m][n];
return 0;
}
Tema1: Monede Byteland - # Programare dinamică (https://www.spoj.com/problems/COINS/)
În Byteland au un sistem monetar foarte ciudat. Fiecare monedă de aur bytelandiană are un număr întreg scris
pe ea. O monedă n poate fi schimbată într-o bancă în trei monede: n / 2, n / 3 și n / 4. Dar aceste cifre sunt toate
rotunjite (băncile trebuie să aducă profit).
De asemenea, puteți vinde monede Bytelandiene pentru dolari americani. Rata de schimb este 1: 1. Dar nu poți
cumpăra monede Bytelandiene.
Cerința: Ai o monedă de aur. Care este suma maximă de dolari americani pe care o puteți obține pentru asta?
Intrare
Intrarea va conține mai multe cazuri de testare (nu mai mult de 10). Fiecare test conține o singură linie pe care
este scris cu un număr natural n. Acesta este numărul scris pe moneda ta.
Ieșire:
Pentru fiecare caz de test rezultatul va fi afișat pe o singură linie, care conține suma maximă de dolari americani
pe care îi puteți face.
Explicații:
Puteți schimba 12 în 6, 4 și 3, apoi le puteți schimba în 6 $ + 4 $ + 3 $ = 13 $. Dacă încercați să schimbați
moneda 2 în 3 monede mai mici, veți primi 1, 0 și 0, iar mai târziu puteți obține cel mult 1 dolar din ele. Este mai
bine doar să schimbați cele 2 monede direct în 2 dolari.