Sunteți pe pagina 1din 32

Programare dinamică

Probleme rezolvate
1. Un om doreşte să urce o scară cu N trepte (1<N<100). El poate urca
una sau două trepte la un moment dat. Fiind o fire curioasă, el vrea să ştie în
câte moduri poate urca această scară. Programul va citi valoarea N de la
tastatură şi va afişa pe ecran rezultatul cerut.
Exemplu :
Date de intrare Rezultat
N=4 5 posibilităţi

using namespace std;


#include <iostream>
#define NMax 100
long long N,DP[NMax];
void Citire()
{
cout<<"N="; cin>>N;
}
void Rezolvare()
{
//DP[i] - Numarul de posibilitati de a urca i trepte
DP[0]=1;
//Este o singura posibilitate de a urca o scara cu 0 trepte
DP[1]=1;
// O scara cu 1 treapta se poate urca intr-un singur mod
for(int i=2;i<=N;i++)
DP[i]=DP[i-1]+DP[i-2];
}
void Afisare()
{
//Solutia va fi DP[N]
//Numarul de posibilitati de a urca N trepte
cout<<"Numarul de posibilitati este: "<<DP[N];
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
2. Se dă un şir S = (s1, s2, .., sN) de lungime N (1 ≤ N ≤ 1 000 000). O
subsecvenţă a şirului este de forma: (si, si+1, ..., sj) cu 1 ≤ i ≤ j ≤ N, iar suma
subsecvenţei este si + si+1 + ... + sj. Se cere să se determine subsecvenţa de
sumă maximă.
Datele de intrare se citesc din fişierul de intrare subsecventa.in care
conţine pe prima linie un număr N, reprezentând lungimea şirului. Următoarea
linie conţine N numere întregi separate printr-un spaţiu, reprezentând elementele
şirului. Programul va afişa pe ecran un număr, care reprezintă cea mai mare
sumă a unei subsecvenţe a sirului.
Exemplu:
Date de intrare Rezultat
N=7
Suma maximă este 8.
S = 5 -6 3 4 -2 3 -3

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 1000
#define oo 2000000000
int N,X[NMax],DP[NMax],SMax;

void Citire()
{
ifstream fin("subsecventa.in");
fin>>N;
for(int i=1;i<=N;i++)
fin>>X[i];
}

void Rezolvare()
{
//DP[i]-Secventa de suma maxima care se termina pe pozitia i
DP[0]=0;

for (int i=1; i<=N; ++i)


if (DP[i-1]+X[i]>=X[i])
DP[i]=DP[i-1]+X[i];
else
DP[i]=X[i];
}

void Afisare()
{
//Solutia va fi maximul din sirul DP, adica secventa de suma
//maxima care se termina pe una din pozitiile sirului
SMax=-oo;
for(int i=1;i<=N;i++)
SMax=max(SMax,DP[i]);
cout<<"Subsecventa de suma maxima este: "<<SMax;
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
3. Se dă un şir S = (s1, s2, .., sN) de lungime N (1 ≤ N ≤ 1 000). Un subşir
al şirului este de forma: S' = (si1, si2, ..., siK), i1 < i2 < ... < iK . Se cere să se
determine un subşir al şirului S, care este ordonat strict crescător şi care are
lungimea maximă.
Datele de intrare se citesc din fisierul subsir.in, care conţine pe prima linie
numărul N, iar pe linia a doua N numere naturale, despărţite de un spaţiu,
reprezentând elementele şirului. Programul va afişa pe ecran un număr natural,
care reprezintă lungimea maximă a unui subşir crescator, respectiv un subşir
crescător maximal.
Exemplu:
Date de intrare Rezultat
N=5 Lungimea maximă a unui subşir este 3.
S = 24 12 15 15 19 Un subşir crescător maximal este: 12 15 19.

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 100005
int N,X[NMax],DP[NMax],Succ[NMax];
void Citire()
{
ifstream fin("subsir.in");
fin>>N;
for(int i=1;i<=N;i++)
fin>>X[i];
}
void Rezolvare()
{
//DP[i]-lungimea celui mai lung subsir crescator
//care incepe pe pozitia i
for(int i=N;i>=1;i--)
{
DP[i]=0;
for(int j=i+1;j<=N;j++)
if(X[i]<X[j]&&DP[i]<DP[j])
{
DP[i]=DP[j];
Succ[i]=j;
}
DP[i]++;
}
}

void Afisare()
{
int i,Max=0,PMax=0;
for(int i=1;i<=N;i++)
{
if(DP[i]>Max)
{
Max=DP[i];
PMax=i;
}
}
// Solutia va fi maximul dintre valorile sirului DP,
cout<<Max<<"\n";

// Reconstituirea celui mai lung subsir crescator


//folosind vectorul Succ (succesor)
i=PMax;
while(i)
{
cout<<X[i]<<" ";
i=Succ[i];
}
}

int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
4. Se dau două şiruri: A = (a1, a2, .., aM), cu M elemente (1 ≤ M ≤ 1 000),
respectiv B = (b1, b2, .., bN) cu N elemente (1 ≤ N ≤ 1 000). Să se găsească cel
mai lung subşir care apare atât în şirul A cât şi în şirul B.
Datele de intrare se citesc din fişierul cmlsc.in, care conţine pe prima linie
un număr N, pe linia a doua N numere, pe linia a treia numărul M, apoi pe linia
următoare M numere. Programul va afişa pe ecran un număr, reprezentând
lungimea subşirului comun maximal, respectiv un subşir comun de lungime
maximă.
Exemplu:
Date de intrare Rezultat
N=5
A=17398 Lungimea maximă a unui subşir comun este 2.
M=3 Un subşir comun maximal este: 7 8 .
B=758

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 1000
int N, M, A[NMax],B[NMax], DP[NMax][NMax];
void Citire()
{
ifstream fin("cmlsc.in");
fin>>N;
for(int i=1;i<=N;i++)
fin>>A[i];
fin>>M;
for(int i=1;i<=M;i++)
fin>>B[i];
}
void Rezolvare()
{
int i,j;
// DP[i][j] - lungimea celui mai lung subsir comun al
// sirurilor A[1..i] si B[1..j]
for (i = 1; i <= N; ++i)
for (j = 1; j <= M; ++j)
{
DP[i][j] = max(DP[i - 1][j], DP[i][j - 1]);
if (A[i] == B[j])
DP[i][j] = max(DP[i][j], DP[i - 1][j - 1] + 1);
}
}
void Reconstituire(int n, int m)
{
if (n < 1 || m < 1)
return;
if (A[n] == B[m])
{
Reconstituire(n - 1, m - 1);
cout<<A[n]<<" ";
}
else
if (DP[n - 1][m] > DP[n][m - 1])
Reconstituire(n - 1, m);
else
Reconstituire(n, m - 1);
}
void Afisare()
{
//Solutia va fi DP[N][M]
//Lungimea celui mai lung subsir comun al sirurilor A si B
cout<<DP[N][M]<<endl;
Reconstituire(N, M);
}

int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

5. Fie S un şir oarecare de caractere. Asupra lui definim următoarele 3


operaţii:
 inserare: pe o poziţie oarecare se adaugă un caracter oarecare
 ştergere: se şterge caracterul de pe o poziţie oarecare din şir
 înlocuire: se înlocuieşte caracterul de pe o poziţie oarecare din şir cu
orice alt caracter.
Se dau două şiruri S1 si S2 conţinând doar litere mici ale alfabetului
englez şi având N şi, respectiv, M caractere (1 ≤ N, M ≤ 2000). Distanţa de
editare a celor două şiruri este numărul minim de operaţii definite mai sus ce
trebuie aplicat asupra primului şir pentru a-l transforma în cel de-al doilea şir.
Acelaşi tip de operaţie poate fi aplicat de mai multe ori.
Datele de intrare se citesc din fişierul edit.in, care conţine pe prima linie
şirul S1, iar pe linia a doua şirul S2. Programul va afişa pe ecran un număr întreg
reprezentând distanţa de editare a celor doua şiruri.
Exemplu:
Date de intrare Rezultat
carte
Costul de editare este 3.
antet

using namespace std;


#include <iostream>
#include <fstream>
#include <cstring>
#define NMax 1000
int N,M,DP[NMax][NMax];
char S1[NMax],S2[NMax];

void Citire()
{
ifstream fin("edit.in");
fin.getline(S1,NMax);
fin.getline(S2,NMax);
N=strlen(S1);
M=strlen(S2);
}

void Rezolvare()
{
int i,j;
//DP[i][j] - numarul minim de operatii pentru a
transforma //sirul format din primele i caractere din S1
//in sirul format din primele j caractere din S2

for(i=1;i<=N;i++)
DP[i][0]=i;
for(j=1;j<=M;j++)
DP[0][j]=j;
for(i=1;i<=N;i++)
for(j=1;j<=M;j++)
{
DP[i][j]=min(DP[i-1][j-1]+(S1[i]!=S2[j]),min(DP[i][j-
1]+1,DP[i-1][j]+1));
}
}

void Afisare()
{
//Solutia va fi DP[N][M] - numarul minim de operatii
pentru //a transforma sirul S1 in sirul S2
cout<<DP[N][M]<<'\n';
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
6. Se consideră un triunghi de numere care are pe prima linie un număr,
pe a doua linie două numere, pe a treia linie trei numere, triunghiul având în total
N linii (1 ≤ N ≤ 1000). Scrieţi un program care să calculeze cea mai mare dintre
sumele numerelor ce apar pe drumurile ce pleacă din vârf şi ajung la bază, astfel
încât în fiecare drum succesorul unui număr se află pe rândul de mai jos
dedesubt sau pe diagonală la dreapta. Datele de intrare se citesc din fişierul
triunghi.in, începând cu numărul de linii şi continuând cu triunghiul de numere.
Programul va afişa pe ecran valoarea cerută.
Exemplu:
Date de intrare Rezultat
5 Suma maximă este este 30.
7
38
810
2744
45265

using namespace std;


#include <fstream>
#include <iostream>
#define NMax 1000
int N,DP[NMax][NMax],A[NMax][NMax];
void Citire()
{
ifstream fin("triunghi.in");
fin>>N;
for (int i=1;i<=N;i++)
for (int j=1;j<=i;j++)
fin>>A[i][j];
}

void Rezolvare()
{
int i,j;
//DP[i][j] – Suma maxima a unui triunghi cu varful
// in pozitia (i,j)
for (i=1;i<=N;i++)
DP[N][i]=A[N][i];
for (i=N-1;i>=1;i--)
for (j=1;j<=i;j++)
DP[i][j]=A[i][j] + max(DP[i+1][j],DP[i+1][j+1]);
}
void Afisare()
{
//Solutia va fi DP[1][1]
//Suma maxima a triunghiului cu varful (1,1)
cout<<DP[1][1]<<"\n";
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

7. Se dă o mulţime formată din N (1 ≤ N ≤ 1000) obiecte, fiecare fiind


caracterizat de o greutate şi un profit. Să se găsească o submulţime de obiecte
astfel încât suma profiturilor lor să fie maximă, iar suma greutăţilor lor să nu
depăşească o valoare G (1 ≤ G ≤ 1000).
Datele de intrare se citesc din fişierul rucsac.in care conţine pe prima
linie valorile N şi G. Pe următoarele N linii se vor găsi perechile de valori Wi şi Pi
(0 ≤ Wi, Pi ≤ 10000), reprezentând greutatea, respectiv profitul obiectului i.
Programul va afişa o singură valoare Pmax, profitul maxim care poate fi obţinut.
Exemplu:
Date de intrare Rezultat
6 10
37
34
12 Profitul maxim este 29.
19
24
15

using namespace std;


#include <iostream>
#include <fstream>
#define MAXN 5010
#define MAXG 10010
int N, G, Pmax,W[MAXN], P[MAXN],DP[MAXN][MAXG];
void Citire()
{
ifstream fin("rucsac.in");
fin>>N>>G;
for(int i = 1; i <= N; ++i)
fin>>W[i]>>P[i];
}
void Rezolvare()
{
//DP[i][g] - profitul maxim care se poate obtine avand
la //dispozitie primele i obiecte,folosind un ruscac de
//greutate g
for(int i = 1; i <= N; ++i)
{
DP[i][0]=0;
for(int g = 0; g <= G; g++)
{
// Mai intai nu punem obiectul i.
DP[i][g] = DP[i-1][g];

// Daca acest obiect duce la o solutie mai buna,


// se adauga obiectul i la o solutie anterioara.
if(W[i] <= g)
DP[i][g] = max(DP[i][g], DP[i - 1][g - W[i]] +
P[i]);
}
}
}
void Afisare()
{
// Solutia se va afla in starea D[N][G]
// Profitul maxim care se poate obtine avand la
dispozitie //toate cele N obiecte si un rucscac de greutate
G
cout<<"Profitul maxim este: "<<DP[N][G];
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
8. Se dă un şir de N (1<N<1000) numere naturale, mai mici sau egale cu
1000. Să se împartă elementele din şir în două submulţimi disjuncte, astfel încât
sumele elementelor din cele două submulţimi să fie cât mai apropiate.
Exemplu:
Date de intrare Rezultat
6
Cele două sume sunt: 15 şi 16.
742945

using namespace std;


#include<iostream>
#include <fstream>
int N,X[1000],Sum[1000000],S;
void Citire()
{
ifstream fin("sume.in");
fin>>N;
for(int i=1;i<=N;i++)
{
fin>>X[i];
S=S+X[i];
}
}
void Rezolvare()
{
//Sum[i] este 0 daca valoarea i se poate obtine ca suma
//elementelor unei submultimi din vectorul X
int i,j,S2=S/2;
Sum[0]=1;
for(i=1;i<=N;i++)
for(j=S2-X[i];j>=0;j--)
if(Sum[j])
Sum[j+X[i]]=1;
}
void Afisare()
{
int j;
for(j=S;!Sum[j];j--);
cout<<j<<" "<<S-j;
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
9. Se dă un produs matricial M = M1M2...Mn (1 ≤ n ≤ 500). Cum înmulţirea
matricelor este asociativă, toate parantezările conduc la acelaşi rezultat. Însă,
numărul total de înmulţiri scalare al produsului matricial poate să difere
substanţial în funcţie de ordinea efectuării calculelor, ordine dată de paranteze.
Dimensiunile celor n matrici se dau sub forma unui şir d astfel încât perechea (di-
1 , di) reprezintă dimensiunile matricii Mi (1 ≤ di ≤ 10 000). Se cere să se
minimizeze numărul total de înmulţiri scalare al produsului matricial M, valoare ce
corespunde unei parantezări optime.
Datele de intrare se citesc din fişierul podm.in care conţine pe prima linie
un număr natural n, reprezentând numărul matricelor. Pe următoarea linie se
găsesc n+1 numere naturale, reprezentând valorile şirului d. Programul va afişa
un număr natural egal cu valoarea minimă a numărului total de înmulţiri scalare a
produsului matricial M.
Exemplu:
Date de intrare Rezultat
4
Numărul minim de înmulţiri este 2856.
13 5 89 3 34

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 505
#define oo 1LL<<60
long long N, D[NMax], DP[NMax][NMax];
void Citire()
{
ifstream fin("podm.in");
fin>>N;
for (int i=0; i<=N; ++i)
fin>>D[i];
}
void Rezolvare()
{
// DP[i][j] – Numarul minim de inmultiri pentru a efectua
//produsul matricilor Mi*…*Mj
for(int i = 1; i <= N; i++)
{
DP[i][i] = 0;
DP[i][i + 1] = D[i - 1] * D[i] * D[i + 1];
}
for(int diff = 2; diff < N; diff++)
for(int i = 1; i <= N - diff; i++)
{
int j = i + diff;
DP[i][j] = oo;
for(int k = i; k < j; k++)
DP[i][j] = min(DP[i][j], DP[i][k] + DP[k + 1][j]
+ D[i - 1] * D[k] * D[j]);
}
}
void Afisare()
{
//Solutia va fi DP[1][N] – numarul minim de inmultiri
//pentru a efectua produsul tuturor matricilor
cout<<DP[1][N];
}

int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
10. Fermierul Ion are o fermă de formă circulară, unde cresc N (2≤N≤
100000) găini. Ferma a fost împărţită în N sectoare, numerotate de la 1 la N,
astfel încât oricare două sectoare având numere consecutive sunt adiacente (se
află unul lângă altul). În plus, primul şi ultimul sector sunt adiacente. În fiecare
sector se află câte o găina, iar aceasta depune un anumit număr de ouă în
fiecare zi. După ce găinile depun ouăle, fermierul Ion doreşte să le adune, pentru
a le mânca. Deoarece fermierul este foarte lacom, de fiecare dată el alege două
sectoare adiacente din care adună ouăle simultan. Din păcate, din cauza
lăcomiei sale, găinile din sectoarele vecine cu cele două alese se sperie şi devin
violente, motiv pentru care fermierul nu mai poate aduna ouăle din aceste
sectoare. În exemplul din problemă, dacă fermierul adună simultan ouăle din
sectoarele 1 şi 2, el nu va mai putea aduna ouăle din sectoarele 3 şi 10.
Determinaţi numărul maxim de ouă pe care le poate aduna fermierul Ion, în urma
aplicării strategiei sale lacome.
Datele de intrare se citesc din fişierul oo.in care conţine pe prima linie
numărul de sectoare în care este împărţită ferma. Pe următoarea linie se află N
numere întregi din intervalul [0,100], reprezentând numărul de ouă depuse de
fiecare găina, în ordinea sectoarelor în care se află acestea.
Programul va afisşa numărul maxim de ouă pe care le poate aduna fermierul Ion.
(Osman Ay, Stelele Informaticii 2003, clasele 9-10)
Exemplu:
Date de intrare Rezultat
10
Numărul maxim de ouă este 20.
1 2 1 7 6 0 1 0 4 3

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 100005
int N, S, X[NMax], DP[NMax];
void Citire ()
{
ifstream fin("oo.in");
fin>>N;
for (int i=1; i<=N; ++i)
fin>>X[i];
X[N+1]=X[1];
}
void Rezolvare (int L, int R)
{
//DP[i] – Numarul maxim de oua care pot fi adunate pana
//in sectorul i
DP[L]=0;
DP[L+1]=X[L]+X[L+1];
for (int i=L+2; i<=R; ++i)
DP[i]=max (DP[i-1], DP[i-3]+X[i]+X[i-1]);
S=max (S, DP[R]);
}
void Afisare ()
{
cout<<S<<'\n';
}
int main()
{
Citire ();
Rezolvare (1, N-1);
Rezolvare (2, N);
Rezolvare (3, N+1);
Afisare ();
return 0;
}
11. Fermierul Ion are o grădină formată din N (2 ≤ N ≤ 1 000) zone
pătratice cu latura de 1 metru, aşezate una după alta, în linie. În fiecare astfel de
zonă creşte câte un morcov magic. Pentru fiecare morcov este cunoscută
valoarea sa energetică. Rilă Iepurilă, iepurele buclucaş, a invadat grădina
fermierului şi a început să mănânce morcovii. Iepuraşul urmează un anumit
traseu şi mănâncă integral morcovii din zonele prin care trece. După ce un
morcov este mâncat, creşte altul în loc, de aceeaşi valoare energetică (nu uitaţi
că morcovii sunt magici iar grădina este una imaginară). Gradul de satisfacţie al
lui Rilă este dat de suma valorilor energetice ale morcovilor pe care îi mănâncă.
Pentru că iepurele este un animal deştept, traseul acestuia are anumite
caracteristici. Rila începe dintr-o zonă oarecare şi execută un număr P1 ≤ P ≤ 12
de salturi iepureşti, astfel încât prin salturile sale să nu părăsească niciodată
grădina. Fermierul, după ce a studiat comportamentul iepurelui pentru o perioadă
lungă de timp, cunoaşte acum lungimile celor P salturi ale iepurelui şi ştie că
acesta execută toate aceste salturi într-o ordine oarecare. Să se determine
gradul de satisfacţie maxim pe care îl poate obţine iepurele.
Datele de intrare se citesc din fişierul morcovi.in care conţine pe prima
linie N, numărul de zone ale grădinii. Pe a doua linie din fişier se găsesc N
numere naturale, al i-lea număr de pe această linie indicând valoarea energetică
a morcovilor ce cresc în zona a i-a. Cea de a treia linie din fişier conţine numărul
P, numărul de salturi pe care le face iepurele. Ultima linie conţine P numere
naturale, într-o ordine oarecare, indicând lungimile salturilor iepurelui. Programul
va afişa gradul maxim de satisfacţie ce poate fi obţinut de iepurele Rilă îin traseul
său prin grădina fermierului.
(Filip Cristian Buruiană, Algoritmiada 2009, Runda Finală)
Exemplu:
Date de intrare Rezultat
7
1 20 3 1 1 40 10
Gradul maxim de satisfacţie este 71.
3
124

using namespace std;


#include <fstream>
#include <iostream>
int Morcovi[1005], DP[4100][1005], S, N, NP,n,Pas[15];
void Citire ()
{
ifstream fin ("morcovi.in");
fin >> N;
for (int i=0; i<N; ++i)
fin >> Morcovi[i];
fin >> NP;
for (int i=0; i<NP; ++i)
fin >> Pas[i];
}
void Rezolvare()
{
//DP[i][j] – Gradul maxim de satisfactie daca iepurele
face //pasii din configuratia i si se opreste pe pozitia j
int i, j, p;
n=(1<<NP)-1;
for (j=0; j<N; ++j)
DP[0][j]=Morcovi[j];
for (i=1; i<=n; ++i)
for (j=0; j<N; ++j)
for (p=0; p<NP; ++p)
if ((i&(1<<p))!=0)
{
if (j-Pas[p]>=0)
DP[i][j]=max (DP[i][j], DP[i-(1<<p)][j-
Pas[p]]+Morcovi[j]);
if (j+Pas[p]<N)
DP[i][j]=max (DP[i][j], DP[i-(1<<p)]
[j+Pas[p]]+Morcovi[j]);
}
}
void Afisare ()
{
for (int i=0; i<N; ++i)
S=max (S, DP[n][i]);
cout << S << "\n";
}

int main ()
{
Citire();
Rezolvare();
Afisare();
return 0;
}
12. O staţie de gaz are un rezervor în care poate depozita cel mult L (1 ≤
L ≤ 1 000) litri de gaz, dar se poate depozita o cantitate suplimentară de gaz într-
un rezervor închiriat de capacitate nelimitată pentru care se va plăti o taxă de C
dolari pentru fiecare litru de gaz depozitat de la o zi la alta. Staţia se
aprovizionează cu gaz cel mult o dată pe zi. Preţul unui litru de gaz este de D
dolari. Pentru fiecare aprovizionare trebuie plătită o taxă de P dolari în plus faţă
de costul gazului comandat. În aceste condiţii, comandarea unei cantităţi mari de
gaz poate creşte costul depozitării. Staţia de gaz se închide după N zile (1 ≤ N ≤
2 000 ). Aceasta livrează clienţilor săi Gi (1 ≤ Gi ≤ 1 000, i  1, n ) litri de gaz la
sfârşitul fiecărei zile i, unde i = 1, 2, ..., N. Problema constă în a alege cantităţile
de gaz ce vor fi comandate zilnic, astfel încât la sfârşitul celei de a N-a zi
întreaga cantitate de pe stoc să fie consumată şi costul total să fie minim. Se
consideră că rezervorul este iniţial gol. Scrieţi un program care determină costul
total minim pentru ca staţia să îşi servească clienţii în cele N zile şi întreaga
cantitate de gaz să fie consumată la sfârşitul celei de a N-a zi.
Datele de intrare se citesc din fişierul gaz.in care conţine pe prima linie
patru numere naturale separate prin câte un spaţiu, L P D C (1 ≤ P, D, C ≤ 5 000),
cu semnificaţia din enunţ. A doua linie conţine numerele naturale N G1 G2 ... GN,
separate prin câte un spaţiu, unde N reprezintă numărul zilelor după care staţia
va fi închisă şi Gi cantitatea de gaz necesară zilei i, i  1, n .

Programul va afişa costul total minim cerut.


(Marius Stroe , ONI 2010, clasa a 10-a)
Exemplu:
Date de intrare Rezultat
5311
Costul minim este 22.
532451

using namespace std;


#include <iostream>
#include <fstream>
#define NMax 2005
#define oo 1LL<<60
int N, ReqG[NMax], Cap, DPrice, GPrice, P;
long long DP[NMax];
void Citire ()
{
ifstream fin("gaz.in");
fin>>Cap>>P>>GPrice>>DPrice;
fin>>N;
for (int i=1; i<=N; ++i)
fin>>ReqG[i];
}
void Rezolvare()
{
//DP[i] – Costul minim pentru a rezolva cerintele
// de benzina pana in ziua i
for (int i=1; i<=N; ++i)
{
DP[i]=oo;
int CurrentG=0;
long long CurrentP=0;
for (int j=i-1; j>=0; --j)
{
if (CurrentG>Cap)
CurrentP+=(DPrice*(CurrentG-Cap));
CurrentG+=ReqG[j+1];
DP[i]=min (DP[i], DP[j]
+P+CurrentP+GPrice*CurrentG);
}
}
}
void Afisare ()
{
// Solutia va fi DP[N] – Costul minim pentru a rezolva
// cerintele pana in ziua N
cout<<DP[N];
}

int main()
{
Citire ();
Rezolvare();
Afisare ();
return 0;
}

13. Fiind foarte pasionat de civilizaţiile extraterestre, Andrei a început să


studieze limba marţienilor pentru a putea comunica uşor cu ei atunci când va fi
cazul. Oricât s-a documentat, el a putut afla doar că toate cuvintele acestei limbi
conţin N litere mici din alfabetul englez şi a mai găsit o listă cu perechi de litere
ce nu pot apărea pe poziţii vecine într-un cuvânt. Pentru că are de gand să
studieze fonetica fiecărui cuvânt posibil în parte, Andrei ar vrea mai întâi să vadă
cât de voluminoasă este munca pe care şi-o propune şi vă roagă să determinaţi
câte cuvinte poate avea limba. Determinaţi câte cuvinte de lungime N se pot
forma folosind doar litere mici ale alfabetului englez astfel încât oricare două
litere care formează o pereche în lista lui Andrei să nu se afle pe poziţii vecine.
Datele de intrare se citesc din fişierul cuvinte.in care conţine pe prima linie
numerele întregi N şi M (0 ≤ N, M ≤ 2000), reprezentând numărul de litere din
care este format un cuvânt şi numărul de perechi din lista lui Andrei. Următoarele
M linii conţin o pereche de forma "l1 l2" unde l1 şi l2 sunt litere mici ale alfabetului
englez. Programul va afişa pe ecran numărul cerut modulo 104659.
(Daniel Pasaila, preONI 2006, Runda 4)
Exemplu:
Date de intrare Rezultat
27
aa
ab
bc Numărul de cuvinte (modulo 104659)
cd este 667.
cf
ba
cf

using namespace std;


#include <fstream>
#include <iostream>
#define NMax 1010
#define MOD 104659
int N,M,Sol,DP[NMax][26];
bool Cuv[26][26];
void Citire()
{
ifstream fin("cuvinte.in");
fin>>N>>M;
for(int i=1;i<=M;i++)
{
char A,B;
fin>>A>>B;
Cuv[A-'a'][B-'a']=1;Cuv[B-'a'][A-'a']=1;
}
}
void Rezolvare()
{
int i,j,k;
// DP[i][j] – Numarul de cuvinte de i litere care se termina
// cu litera j
for(j=0;j<CMax;j++)
DP[1][j]=1;
for(i=2;i<=N;i++)
for(j=0;j<CMax;j++)
for(k=0;k<CMax;k++)
if(!Cuv[j][k])
DP[i][j]=(DP[i][j]+DP[i-1][k])%MOD;
}
void Afisare()
{
//Solutia va fi suma tuturor elementelor de pe linia N,
//a cuvintelor de N litere care se termina cu orice litera
for(int i=0;i<CMax;i++)
Sol=(Sol+DP[N][i])%MOD;
cout<<Sol<<'\n';
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

14. Gigel s-a decis să îşi cumpere un teren într-o zonă a oraşului în care
locuieşte. Oraşul este reprezentat printr-o matrice pătratică de dimensiune N (4 ≤
N ≤ 150), iar fiecare pătrat al matricii reprezintă o zonă de dimensiune unitară a
oraşului. Pentru fiecare zonă de dimensiune unitară a oraşului se cunoaşte
valoarea ei, care este un număr întreg între -150 şi 150. Gigel vrea să îşi
cumpere un teren de forma dreptunghiulară având P linii şi Q coloane, inclus
complet în oraş, care să aibă valoarea maximă posibilă. Valoarea terenului este
reprezentată de suma valorilor zonelor de dimensiune unitară pe care le include
(în număr de P*Q).
Datele de intrare se citesc din fişierul teren.in care conţine pe prima linie
numerele N, P şi Q (separate prin câte un spaţiu). Pe următoarele N linii se vor
afla câte N numere întregi (separate prin spaţii), reprezentând valorile zonelor de
dimensiune unitară care fac parte din oraş. Programul va afişa valoarea maximă
a terenului pe care vrea să îl cumpere Gigel.
Exemplu:
Date de intrare Rezultat
423
-1 -1 -1 -1
-1 1 -1 -1 Valoarea maximă a unui teren este 1.
-1 -1 -1 4
-1 -1 -1 -1
using namespace std;
#include <iostream>
#include <fstream>
#define oo 1000000000
int N, P, Q, A[155][155], S=-oo;
void Citire ()
{
ifstream fin ("teren.in");
fin >> N >> P >> Q;
for (int i=1; i<=N; ++i)
for (int j=1; j<=N; ++j)
fin >> A[i][j];
}
void Determinare_Sume_Partiale()
{
//A[i][j] – suma elementelor din submatricea cu coltul
//stanga sus (1,1) si dreapta jos (I,j)
for (int i=1; i<=N; ++i)
for (int j=1; j<=N; ++j)
A[i][j]+=(A[i-1][j]+A[i][j-1]-A[i-1][j-1]);
}
void Rezolvare()
{
Determinare_Sume_Partiale();
for (int i=P; i<=N; ++i)
for (int j=Q; j<=N; ++j)
S=max(S,A[i][j]-A[i-P][j]-A[i][j-Q]+A[i-P][j-
Q]);
}
void Afisare ()
{
cout << S << "\n";
}
int main(){
Citire ();
Rezolvare();
Afisare ();
return 0;
}
15. Se dă o tablă de şah, de dimensiune standard 8/8, pe care se află
diverse piese de şah. Un cal se află pe poziţia (i,j). Dorim să mutăm calul în
poziţia (k,l) dacă acest lucru este posibil, folosind oricare din cele 8 mutări
posibile ale calului în orice succesiune, astfel încât numărul de mutări să fie
minim.
Datele vor fi citite din fişierul cal.in, fiind date în următoarea formă:
*-**-***
**-***-*
*****-**
***-****
********
******-*
********
********
unde "*" înseamnă poziţie liberă, iar "-" poziţie ocupată.
Programul va afişa numărul minim în cazul în care calul poate ajunge la
poziţia (c,d) sau un mesaj corespunzător dacă nu poate ajunge.
using namespace std;
#include<fstream>
#include <iostream>
#define N 10
int A[N][N],X1,Y1,X2,Y2;
int L[N*N],C[N*N],K,dX[]={-2,-2,-1,-1,1,1,2,2},dY[]={-1,1,-
2,2,-2,2,-1,1};

void Citire()
{
ifstream fin("cal.in");
for(int i=1;i<=8;i++)
for(int j=1;j<=8;j++)
{
char x;
fin>>x;
if(x=='-')
A[i][j]=-1;
}
fin>>X1>>Y1>>X2>>Y2;
}

inline int Valid(int x, int y)


{
return((x>=1)&&(x<=8)&&(y>=1)&&(y<=8));
}

void Rezolvare()
{
A[X1][Y1]=1;
L[1]=X1;C[1]=Y1;
K=1;
for(int i=1;i<=K;i++)
{
int x,y;
x=L[i]; y=C[i];
for(int l=0;l<8;l++)
{
int xnou,ynou;
xnou=x+dX[l];
ynou=y+dY[l];
if (Valid(xnou,ynou) && A[xnou][ynou]==0)
{
A[xnou][ynou]=A[x][y]+1;
K++;
L[K]=xnou;
C[K]=ynou;
}
}
}
}
void Afisare()
{
if(A[X2][Y2])
cout<<A[X2][Y2]-1<<'\n';
else
cout<<"Imposibil";
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

16. Fie o pereche de numere de forma (a,b) asupra căreia putem face
următoarele operaţii:
 (a,b) -> (a-b,b)
 (a,b) -> (a+b,b)
 (a,b) -> (b,a)
Dându-se valorile (a,b) de la tastatură, să se determine numărul minim de
operaţii care pot fi efectuate astfel încât să se obţină perechea (c,d). Programul
va afişa acest număr minim în cazul în care se poate obţine perechea (c,d) sau
un mesaj corespunzător dacă nu se poate obţine.
using namespace std;
#include<iostream>
#include<cstring>
#define NMax 1005
#define oo 1<<30
struct Pereche
{
int first,second;
};
int A,B,C,D,DP[NMax][NMax],K;
Pereche Coada[NMax*NMax];

void Citire()
{
cout<<"A=";cin>>A;
cout<<"B=";cin>>B;
cout<<"C=";cin>>C;
cout<<"D=";cin>>D;
}

inline bool Valid(int x,int y)


{
return ( x>=0 && y>=0 && x<=1000 && y<=1000 && DP[x]
[y]==-1);
}

void Rezolvare()
{
memset(DP,-1,sizeof(DP));
Coada[++K].first=A;Coada[K].second=B;
DP[A][B]=0;
for(int i=1;i<=K&&DP[C][D]==-1;i++)
{
int x,y;
x=Coada[i].first;
y=Coada[i].second;
if(Valid(x+y,y))
{
P[x+y][y]=DP[x][y]+1;
Coada[++K].first=x+y;Coada[K].second=y;
}
if(Valid(x-y,y))
{
DP[x-y][y]=DP[x][y]+1;
Coada[++K].first=x-y;Coada[K].second=y;
}
if(Valid(y,x))
{
DP[y][x]=DP[x][y]+1;
Coada[++K].first=y;Coada[K].second=x;
}
}
}
void Afisare()
{
if(DP[C][D]!=-1)
cout<<DP[C][D]<<'\n';
else
cout<<"Imposibil";
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

17. Se dă un arbore cu N noduri (1 ≤ N ≤ 100.000), fiecare nod având un


cost (0 ≤ costul unui nod ≤ 10.000). Se cere să determinaţi centroidul acestui
arbore. Centroidul unui arbore este acel nod din arbore pentru care valoarea
maximă a numărului de noduri dintr-un subarbore de al său este minimă.
Datele de intrare se citesc din fişierul centroid. in
Programul va afişa unul sau mai multe numere, reprezentând centroidul
sau centroizii arborelui dat.
Exemplu:
Date de intrare Rezultat
7
12
23
24 Exista un singur centroid: nodul 1.
15
56
67
using namespace std;
#include <iostream>
#include <fstream>
#include <vector>
#define NMax 16005
ifstream fin("centroid.in");
int N,W[NMax],TT[NMax],Min;
vector <int> G[NMax],Sol;
void Citire()
{
fin>>N;
for(int i=1;i<N;i++)
{
int x,y;
fin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
}
void DFS(int Node,int Father)
{
W[Node]=1;
TT[Node]=Father;
for(vector <int> :: iterator it=G[Node].begin();it!
=G[Node].end();it++)
if(*it!=Father)
{
DFS(*it,Node);
W[Node]+=W[*it];
}
}
void Rezolvare()
{
//W[i] – Numarul de noduri aflate in subarborele nodului i
int Max=0;
DFS(1,0);Min=N;
for(int i=1;i<=N;i++)
{
Max=W[1]-W[i];
for(vector <int> :: iterator it=G[i].begin();it!
=G[i].end();it++)
if(*it!=TT[i])
Max=max(Max,W[*it]);
if(Max<Min)
{
Min=Max;
Sol.clear();
Sol.push_back(i);
}
else
if(Max==Min)
Sol.push_back(i);
}
}
void Afisare()
{
for(vector <int> :: iterator it=Sol.begin();it!
=Sol.end();it++)
cout<<*it<<" ";
}
int main()
{
Citire();
Rezolvare();
Afisare();
return 0;
}

18. Se dă un arbore cu N (1 ≤ N ≤ 100.000) noduri, fiecare nod având un


cost (0 ≤ costul unui nod ≤ 10.000). Se cere să găsiţi o submulţime de noduri de
cost minim astfel încât fiecare muchie din arbore să aibă incident cel puţin un nod
din submulţime. Costul unei submulţimi se defineşte ca fiind suma costurilor
fiecărui nod din submulţime.
Datele de intrare se citesc din fişierul de intrare mvc.in care conţine pe
prima linie un singur număr întreg N, reprezentând numărul de noduri. A doua
linie va conţine exact N valori, mai exact costurile nodurilor 1, 2 ... N. Următoarele
N-1 linii vor conţine fiecare cât 2 valori, x, y cu semnficaţa că există muchie
(neorientată) de la x la y. Programul va afişa costul minim al unei submulţimi
astfel încât orice muchie să fie incidentă la cel puţin un nod din acea submulţime.

#include <iostream>
#include <fstream>
#include <vector>
#define NMax 100005
#define oo 1<<30
using namespace std;
ifstream fin("mvc.in");
int N,Use[NMax],C[NMax],DP[2][NMax],Sol;
vector <int> G[NMax];
void Citire()
{
fin>>N;
for(int i=1;i<=N;i++)
fin>>C[i];
for(int i=1;i<=N;i++)
{
int x,y;
fin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
}
void DFS(int Nod)
{
Use[Nod]=1;
DP[0][Nod]=0;
DP[1][Nod]=C[Nod];
for(unsigned int i=0;i < G[Nod].size();i++)
{
int Fiu=G[Nod][i];
if(!Use[Fiu])
{
DFS(Fiu);
DP[0][Nod]+=DP[1][Fiu];
DP[1][Nod]+=min(DP[0][Fiu],DP[1][Fiu]);
}
}
}
void Rezolvare()
{
//DP[0][Nod]–Costul minim al a unei submultimi pentru
--subarborele nodului Nod,din care nu face parte nodul Nod
//DP[1][Nod]–Costul minim al a unei submultimi pentru
--subarborele nodului Nod,din care face parte nodul Nod
DFS(1);
}
void Afisare()
{
// Solutia va fi minimul dintre DP[0][1] si DP[1][1]
// Costul minim pentru arborele cu radacina in 1
// daca se alege nodul 1 sau nu se alege nodul 1
Sol=min(DP[0][1],DP[1][1]);
cout<<Sol<<'\n';
}
int main(){
Citire();
Rezolvare();
Afisare();
return 0;
}

Probleme propuse

1. Se consideră o tablă de şah de dimensiune N * N (n<=100) pe care


sunt dispuse obstacole. Se cere să se tipărească numărul minim de mutări
necesare unui nebun pentru a se deplasa, respectând regulile jocului de şah si
ocolind obstacolele, dintr-o poziţie iniţială dată într-o poziţie finală dată.

2. Vasele 1, 2 şi 3 au volumele A1, A2, A3. Iniţial vasul 1 este plin cu apă.
Se poate face o singură operaţie, şi anume se toarnă apa dintr-un vas până când
acesta se goleşte sau până când vasul în care se toarnă se umple. Problema
este că din număr minim de asemenea operaţii, să se ajungă la situatia ca într-
unul din vase să se afle o cantitate Q de apă.
Indicaţie: Se rezolvă prin Lee, A[i,j,k] = numărul minim de operaţii pentru
a ajunge de la situaţia iniţială la situaţia în care în primul butoi se află "i" unităţi de
apă, în al 2-lea butoi "j" unităţi de apă, iar în al 3-lea "k" unităţi de apă. Algoritmul
se încheie când A[Q,i,j] sau A[i,Q,j] sau A[i,j,Q] devine iniţializat.

3. Fie funcţiile "f","g" definite pentru numere întregi astfel:


f(a) = [a / 2] (parte întreagă din a împărţit la 2)
g(a) = 3 * a.
Să se determine din câte aplicări ale funcţiilor "f" şi "g" se poate ajunge de
la numărul X la numărul Y, ambele date iniţial.
Indicaţie: Se construieşte sirul S[i] = numărul minim de operaţii necesare
pentru a ajunge de la X la numărul "i". Iniţial S[X] = 0, iar toate celelalte elemente
ale şirului se consideră neiniţializate. Algoritmul se încheie în momentul în care
se iniţializează S[Y] sau când prin operaţiile de extindere nu se mai pot iniţializa
alte elemente ale şirului S.

4. Se introduce de la tastatură un număr natural N <= 100. Se cere să se


calculeze numărul şirurilor de caractere de lungime N care conţin doar paranteze
rotunde deschise şi închise, cu proprietatea că parantezele se potrivesc corect.
Indicaţie: Se foloseşte şirul S, cu S[0] = 1, şi relaţia de recurenţă S[i] = Sumă
după "j" între 0 şi "i" din S[j]*S[i-j]. În S[N+1] se obţine numărul dorit.

5. Pasiunea Mirunei este să coloreze. Vacanţa trecută şi-a petrecut-o la


bunica ei la ţară şi pentru că se cam plictisea s-a gândit să vopsească gardul de
la casa bunicii. Gardul este compus din N (1 ≤ N ≤ 5000) scânduri dispuse una
lângă alta. Miruna a găsit în garajul bunicii 5 cutii de vopsea de culori diferite:
albă, albastră, roşie, verde şi galbenă. Când a vopsit gardul, Miruna a respectat
următoarele reguli:
 Dacă o scândură era vopsită cu alb, următoarea scândură o vopsea
obligatoriu cu albastru
 Dacă o scândură era vopsită cu albastru, atunci următoarea scândură o
vopsea cu alb sau roşu
 Dacă o scândură era vopsită cu roşu, atunci următoarea scândură o
vopsea cu albastru sau verde
 Dacă o scândură era vopsită cu verde, atunci următoarea scândură o
vopsea cu roşu sau galben
 Dacă o scândură era vopsită cu galben, atunci următoarea scândură o
vopsea obligatoriu cu verde
După ce a şi-a terminat treaba Miruna îşi admira “opera de artă” şi se întreba în
câte moduri diferite ar fi putut să vopsească gardul bunicii. Ajutaţi-o pe Miruna să
găsească răspunsul la întrebarea sa.
Datele de intrare se citesc din fişierul culori3.in care conţine pe prima sa
linie un singur număr natural N. Programul va afişa un singur număr întreg
reprezentând numărul de moduri diferite în care Miruna ar fi putut să vopsească
gardul bunicii.
Indicatie: Se va folosi o dinamica DP[i][j] – Numarul de posibilitati de a
vopsi un gard cu i scanduri iar ultima scandura sa fie vopsita cu culoarea j.
Solutia va fi suma valorilor de pe ultima linie a matricei.

6. Se dau două numere naturale N şi K (1 ≤ K ≤ N ≤ 1000000) . Determinaţi


numărul de şiruri de lungime N formate doar din semnele + şi – şi în care nu apar
K semne – pe poziţii consecutive. Date de intrare se citesc din fişierul minusk.in
care conţine pe prima linie 2 numere naturale separate printr-un spaţiu, N şi K, cu
semnificaţia din enunţ. Programul va afişa un singur număr natural reprezentând
valoarea cerută, modulo 2011.
(Marius Nicoli, Lot Arad 2011 - Baraj 4 juniori)
Indicatie: Se vor folosi doua dinamici, Plus[i] – Numarul de posibilitati de
aranjare a unui sir de i elemente, in care ultimul element este + si Minus[i] –
Numarul de posibilitati de aranjare a unui sir de i elemente, in care ultimul
element este -. Solutia va fi Plus[N]+Minus[N].

7. Ilinca este o fetiţă căreia îi place foarte mult să deseneze; ea a făcut multe
desene pe care le-a numerotat de la 1 la d şi apoi le-a multiplicat (toate copiile
poartă acelaşi număr ca şi originalul după care au fost făcute). În vacanţă s-a
hotărât să-şi deschidă propria expoziţie pe gardul bunicilor care are mai multe
scânduri; pe fiecare scândură ea aşează o planşă (un desen original sau o
copie). Ilinca ţine foarte mult la desenele ei şi doreşte ca fiecare desen să apară
de cel puţin k ori (folosind originalul şi copiile acestuia). Ilinca se întreabă în câte
moduri ar putea aranja expoziţia. Două moduri de aranjare sunt considerate
distincte dacă diferă cel puţin prin numărul unei planşe (de exemplu: 2 1 3 3 este
aceeaşi expoziţie ca şi 2 3 1 3, dar este diferită de 2 1 3 1 şi de 1 3 3
1).Cunoscând n numărul de scânduri din gard, d numărul desenelor originale şi k
numărul minim de apariţii al fiecărui desen, să se determine în câte moduri poate
fi aranjată expoziţia, ştiind că Ilinca are la dispoziţie oricâte copii doreşte.
Datele de intrare se citesc din fişierul expozitie.in care conţine 3 numere: n d
k – numărul de scânduri, numărul desenelor originale, respectiv numărul minim
de apariţii. Programul va afişa numărul modurilor distincte de aranjare a
expoziţiei.
1 ≤ n ≤ 500 1 ≤ d ≤ 500 0 ≤ k ≤ n
(Carmen Popescu, OJI 2012 - clasa a 10-a)

8. Se consideră un arbore cu N (1 ≤ N ≤ 10.000) vârfuri, în care fiecare


vârf i are asociată o valoare întreagă Ci (-1000 ≤ Ci ≤ 1000). Se cere să
determinaţi un subarbore al arborelui dat, pentru care suma valorilor asociate
vârfurilor conţinute în subarbore să fie maximă.
Datele de intrare se citesc din fişierul de intrare asmax.in care conţine pe
prima linie numărul intreg N, reprezentând numărul de vârfuri ale arborelui. A
doua linie a fişierului conţine N valori întregi, reprezentând valorile asociate
nodurilor. A i-a valoare din acest şir reprezintă valoarea asociată nodului i.
Următoarele N-1 linii conţin câte doi întregi distincţi a şi b, separaţi printr-un
spaţiu, având semnificaţia că există muchie între vârful numerotat cu a şi cel
numerotat cu b. Programul va afişa suma maximă a unui subarbore al arborelui
dat.

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