Šta je Grid DP i zašto je važan?
Grid DP (dinamičko programiranje na mreži) predstavlja posebnu klasu problema u kojima radimo sa dvodimenzionalnom matricom (gridom) i želimo da izračunamo neku vrednost za svako polje na osnovu prethodno izračunatih polja.
Najčešći primer je sledeći:
- Data je matrica dimenzija n × m,
- nalazimo se u gornjem levom uglu,
- želimo da stignemo do donjeg desnog ugla,
- dozvoljeno je kretanje samo udesno ili nadole.
Pitanje koje se postavlja je: Na koliko različitih načina možemo stići do cilja?
Na prvi pogled, problem može delovati kombinatorno — ali broj mogućih puteva brzo postaje ogroman. Pokušaj da se generišu svi putevi bio bi prespor, jer broj kombinacija eksponencijalno raste.
Ovde dolazi do izražaja dinamičko programiranje. Umesto da ponavljamo ista računanja, koristimo činjenicu da se do svakog polja dolazi iz nekog prethodnog polja, pa rezultat možemo graditi postepeno.
Zašto je Grid DP važan u takmičenjima?
Grid DP problemi su izuzetno česti u takmičarskom programiranju i na prijemnim testovima za IT kompanije.
- Brojanje puteva kroz mrežu
- Minimalna suma puta
- Maksimalna vrednost prikupljenih poena
- Problemi sa preprekama
- Kretanje u više pravaca
Razumevanje ovog tipa problema pomaže da se lakše savladaju naprednije DP tehnike, jer uvodi rad sa dvodimenzionalnim stanjima.
Ako ste već učili 1D dinamičko programiranje (poput Fibonacci niza), Grid DP je prirodan sledeći korak — umesto jedne dimenzije, sada radimo sa dve.
Ovaj problem se često javlja u zadacima gde prelazimo kroz matricu koristeći srodne tehnike kao što su BFS (Breadth-First Search) za minimizaciju koraka. Za takve probleme, pogledajte sekciju BFS na gridu u lekciji o grafovima.
Za probleme koji se bave prelaskom kroz mrežu bez težina (npr. minimalan broj koraka) pogledajte i BFS na gridu, koji koristi pretragu grafa u matrici.
Opis osnovnog problema
Razmotrimo pravougaonu matricu dimenzija n × m. Polja su indeksirana od (0, 0) do (n−1, m−1).
Nalazimo se u polju (0, 0) — gornji levi ugao, a cilj nam je da stignemo do polja (n−1, m−1) — donji desni ugao.
Dozvoljena su samo sledeća kretanja:
- jedan korak udesno (i, j → i, j+1)
- jedan korak nadole (i, j → i+1, j)
Potrebno je odrediti na koliko različitih načina možemo stići od početnog do krajnjeg polja.
Zašto je naivno rešenje presporo?
Jedan pristup bi bio da rekurzivno generišemo sve moguće puteve. Međutim, broj puteva brzo raste kako se dimenzije matrice povećavaju.
Na primer, već za matricu 20 × 20 broj puteva je veoma veliki, pa bi generisanje svih kombinacija bilo izuzetno sporo.
Zbog toga koristimo dinamičko programiranje — umesto da ponavljamo ista računanja, čuvamo rezultate za svako polje.
Definicija DP stanja
Definišemo dvodimenzionalni niz:
dp[i][j]predstavlja broj različitih puteva kojima možemo stići do polja (i, j).
Ovo znači da za svako polje želimo da znamo koliko različitih načina postoji da do njega dođemo koristeći dozvoljena kretanja.
Naš konačni cilj je da izračunamo vrednost:
dp[n-1][m-1]
što predstavlja ukupan broj različitih puteva do cilja.
DP prelaz (relacija)
Da bismo izračunali vrednost dp[i][j],
postavimo pitanje:
Odakle možemo doći u polje (i, j)?
Pošto su dozvoljena samo dva kretanja — udesno i nadole — u polje (i, j) možemo stići:
- iz polja iznad:
(i-1, j) - iz polja levo:
(i, j-1)
Ako znamo broj puteva do tih polja, onda je ukupan broj puteva do polja (i, j) jednak zbiru ta dva broja.
Dakle, dobijamo osnovnu DP relaciju:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
Početni uslovi
Da bi relacija radila ispravno, moramo definisati početne vrednosti.
-
— postoji tačno jedan način da budemo na početnom polju.dp[0][0] = 1
Za prvu vrstu (i = 0):
- Možemo se kretati samo udesno, pa je svaki element jednak 1.
Za prvu kolonu (j = 0):
- Možemo se kretati samo nadole, pa je svaki element takođe jednak 1.
Time smo definisali sve potrebne uslove za popunjavanje cele DP tabele.
Kako se popunjava DP tabela
Svaka ćelija predstavlja broj načina da se stigne do te pozicije. Za svako unutrašnje polje sabiramo vrednosti ćelije iznad i ćelije sleva: dp[i][j] = dp[i-1][j] + dp[i][j-1].
U donjoj desnoj ćeliji nalazi se 6. Koristi dugmad da vidiš kako se tabela popunjava korak po korak.
Prvi red i prva kolona sadrže jedinice jer postoji samo jedan način da se stigne do svake od tih ćelija. Ostale vrednosti računaju se pomoću DP relacije.
Implementacija u C++ (sa objašnjenjima)
U ovom programu rešavamo klasičan Grid DP problem: želimo da iz gornjeg levog ugla matrice dimenzija n × m stignemo do donjeg desnog ugla, krećući se samo udesno ili nadole.
Formiramo DP tabelu dimenzija n × m, gde:
dp[i][j]predstavlja broj različitih puteva do polja (i, j),- u svako polje možemo doći ili iz polja iznad (i−1, j),
- ili iz polja levo (i, j−1).
Zbog toga važi sledeća relacija:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
Početno stanje:
dp[0][0] = 1— postoji tačno jedan način da budemo na početnom polju.
Konačan odgovor je vrednost dp[n-1][m-1],
odnosno broj načina da stignemo do donjeg desnog ugla.
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
// n = broj redova
// m = broj kolona
// Kreiramo DP tabelu n x m
// dp[i][j] = broj načina da dođemo do polja (i, j)
vector<vector<int>> dp(n, vector<int>(m, 0));
// Početno polje
dp[0][0] = 1;
// Popunjavanje DP tabele
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// Ako možemo doći odozgo
if (i > 0)
dp[i][j] += dp[i-1][j];
// Ako možemo doći sleva
if (j > 0)
dp[i][j] += dp[i][j-1];
}
}
// Konačan odgovor
cout << dp[n-1][m-1];
}
Grid sa preprekama
Često se u zadacima neka polja ne mogu koristiti (predstavljaju prepreke). U tom slučaju:
- Ako je polje prepreka →
dp[i][j] = 0 - Inače koristimo istu relaciju kao ranije
Pre nego što računamo vrednost za polje, proveravamo da li je ono dozvoljeno. Ako je početno polje prepreka — rezultat je 0.
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
vector<vector<long long>> dp(n, vector<long long>(m, 0));
// Učitavanje matrice
// 0 = slobodno polje
// 1 = prepreka
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> grid[i][j];
if (grid[0][0] == 0)
dp[0][0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1) {
dp[i][j] = 0; // prepreka
continue;
}
if (i > 0)
dp[i][j] += dp[i-1][j];
if (j > 0)
dp[i][j] += dp[i][j-1];
}
}
cout << dp[n-1][m-1];
}
Složenost algoritma
- Vremenska složenost: O(n × m)
- Prostorna složenost: O(n × m)
Pošto svako polje računamo tačno jednom, algoritam je veoma efikasan i pogodan za matrice srednje veličine (n, m do nekoliko hiljada).
Optimizacija memorije
Pošto nam je za računanje potrebna samo trenutna i prethodna vrsta, možemo smanjiti memoriju sa O(n × m) na O(m).
To je česta tehnika u takmičenjima kada su ograničenja velika.
Primer popunjavanja DP tabele
Razmotrimo jednostavan primer matrice dimenzija 3 × 3. Želimo da izračunamo broj puteva od (0,0) do (2,2).
Na početku postavljamo:
dp[0][0] = 1- Prva vrsta = 1 (kretanje samo udesno)
- Prva kolona = 1 (kretanje samo nadole)
| 1 | 1 | 1 |
| 1 | ? | ? |
| 1 | ? | ? |
Sada računamo ostala polja koristeći relaciju:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
Polje (1,1):
- Dolazimo iz (0,1) = 1
- Dolazimo iz (1,0) = 1
Zato je:
dp[1][1] = 1 + 1 = 2
| 1 | 1 | 1 |
| 1 | 2 | ? |
| 1 | ? | ? |
Polje (1,2):
- Dolazimo iz (0,2) = 1
- Dolazimo iz (1,1) = 2
dp[1][2] = 1 + 2 = 3
Polje (2,1):
- Dolazimo iz (1,1) = 2
- Dolazimo iz (2,0) = 1
dp[2][1] = 2 + 1 = 3
Na kraju, polje (2,2):
- Dolazimo iz (1,2) = 3
- Dolazimo iz (2,1) = 3
dp[2][2] = 3 + 3 = 6
| 1 | 1 | 1 |
| 1 | 2 | 3 |
| 1 | 3 | 6 |
Dakle, postoji 6 različitih puteva od gornjeg levog do donjeg desnog ugla u mreži 3 × 3.
Na ovom primeru jasno vidimo kako se tabela gradi postepeno — svako polje zavisi samo od već izračunatih suseda.
Implementacija (C++)
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
long long dp[n][m];
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(i == 0 && j == 0)
dp[i][j] = 1;
else {
long long fromUp = (i > 0) ? dp[i-1][j] : 0;
long long fromLeft = (j > 0) ? dp[i][j-1] : 0;
dp[i][j] = fromUp + fromLeft;
}
}
}
cout << "Broj puteva: " << dp[n-1][m-1];
return 0;
}
Grid sa preprekama
Često neka polja ne mogu da se koriste (prepreke).
Takva polja obeležimo, na primer, sa 1,
dok su slobodna polja 0.
Ako je polje prepreka, tada:
dp[i][j] = 0
Primer: grid sa preprekama (C++)
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int grid[n][m];
long long dp[n][m];
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cin >> grid[i][j];
if(grid[0][0] == 1) {
cout << 0;
return 0;
}
dp[0][0] = 1;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(grid[i][j] == 1) {
dp[i][j] = 0;
} else {
if(i == 0 && j == 0) continue;
long long fromUp = (i > 0) ? dp[i-1][j] : 0;
long long fromLeft = (j > 0) ? dp[i][j-1] : 0;
dp[i][j] = fromUp + fromLeft;
}
}
}
cout << dp[n-1][m-1];
return 0;
}
Grid DP sa preprekom
Crveno polje je blokirano, pa je njegova DP vrednost 0. Ne možemo proći kroz njega, a algoritam broji preostale puteve koji zaobilaze prepreku.
Centralna ćelija je blokirana, pa je njena vrednost 0. Konačna DP vrednost je 2, što znači da postoje dva validna puta.
Blokirana ćelija ne doprinosi broju puteva. Vrednosti susednih polja računaju se uobičajeno, koristeći vrednosti odozgo i sleva. U ovom primeru donja desna ćelija ima vrednost 2.
Složenost algoritma
Analizirajmo efikasnost našeg rešenja.
Vremenska složenost
DP tabelu dimenzija n × m popunjavamo tako što svako polje računamo tačno jednom.
Za svako polje radimo konstantan broj operacija (zbir dve vrednosti).
Zbog toga je ukupna vremenska složenost:
- O(n × m)
Ovo je veoma efikasno rešenje, jer je svako stanje izračunato samo jednom.
Prostorna složenost
Koristimo dvodimenzionalnu tabelu veličine n × m, pa je memorijska složenost:
- O(n × m)
Za matrice srednje veličine (n i m do nekoliko hiljada) ovo je potpuno prihvatljivo.
Kada može doći do problema?
- Ako su dimenzije veoma velike (npr. 105 × 105), memorija postaje problem.
- Ako je broj puteva veoma veliki,
može doći do prekoračenja opsega tipa
int.
U takvim situacijama koristimo:
long longtip promenljive- računanje rezultata modulo nekog broja (npr. 109 + 7)
- optimizaciju memorije na O(m)
Time Grid DP postaje primenljiv i za znatno veće ulaze.
Varijanta: Minimalna suma puta
U ovoj verziji problema, svako polje matrice sadrži neku vrednost (cenu).
Cilj više nije da brojimo puteve, već da pronađemo minimalnu moguću sumu vrednosti polja kroz koja prolazimo od (0,0) do (n−1,m−1).
Dozvoljena kretanja su i dalje:
- udesno
- nadole
Definicija DP stanja
dp[i][j]predstavlja minimalnu sumu potrebnu da stignemo do polja (i, j).
DP relacija
Pošto do polja (i, j) možemo doći iz polja iznad ili sleva, biramo onaj put koji daje manju sumu:
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
Početni uslovi
dp[0][0] = grid[0][0]- Prva vrsta se puni sabiranjem s leva
- Prva kolona se puni sabiranjem odozgo
Implementacija u C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
vector<vector<int>> dp(n, vector<int>(m));
// Učitavanje matrice
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> grid[i][j];
dp[0][0] = grid[0][0];
// Popunjavanje prve vrste
for (int j = 1; j < m; j++)
dp[0][j] = dp[0][j-1] + grid[0][j];
// Popunjavanje prve kolone
for (int i = 1; i < n; i++)
dp[i][0] = dp[i-1][0] + grid[i][0];
// Popunjavanje ostatka tabele
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
cout << dp[n-1][m-1];
}
Ovde vidimo kako se struktura problema ne menja — menja se samo operacija:
- kod brojanja → sabiranje
- kod optimizacije → minimum (ili maksimum)
Ovo pokazuje koliko je Grid DP fleksibilna i moćna tehnika.
Varijanta: Maksimalna suma puta
U ovoj verziji, svako polje matrice sadrži neku vrednost. Cilj je da pronađemo put sa maksimalnom sumom vrednosti od (0,0) do (n−1,m−1), krećući se samo udesno ili nadole.
Ova varijanta je slična Minimalnoj sumi puta, samo što sada biramo maksimum umesto minimuma.
Definicija DP stanja
dp[i][j]predstavlja maksimalnu sumu koju možemo prikupiti do polja (i,j).
DP relacija
Za svako polje (i,j), možemo doći iz polja iznad ili sleva:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
Početni uslovi:
dp[0][0] = grid[0][0]- Prva vrsta se puni sabiranjem s leva
- Prva kolona se puni sabiranjem odozgo
Implementacija u C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
vector<vector<int>> dp(n, vector<int>(m));
// Učitavanje matrice
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> grid[i][j];
dp[0][0] = grid[0][0];
// Popunjavanje prve vrste
for (int j = 1; j < m; j++)
dp[0][j] = dp[0][j-1] + grid[0][j];
// Popunjavanje prve kolone
for (int i = 1; i < n; i++)
dp[i][0] = dp[i-1][0] + grid[i][0];
// Popunjavanje ostatka tabele
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
cout << dp[n-1][m-1];
}
Ova varijanta pokazuje da ista struktura DP tabele može da reši različite zadatke samo promenom operacije (sabiranje → minimum/maksimum, ili brojanje).
Napomene za takmičenja i granični slučajevi
Kada rešavate Grid DP probleme na takmičenjima ili u vežbama, važno je obratiti pažnju na nekoliko praktičnih detalja.
1. Tip promenljive
- Broj puteva može brzo postati veoma veliki.
- Koristite
long longumestointkada broj prelazi ~109. - Ako je zadatak modulovan (npr. 109 + 7), računate modulo nakon svakog sabiranja.
2. Početno polje može biti blokirano
- Ako je
dp[0][0]prepreka, rezultat je odmah0. - Slično važi i ako je cilj blokiran — nema rešenja.
3. Dimenzije matrice
- Za vrlo velike matrice (n, m > 104) dvodimenzionalna tabela može zauzeti previše memorije.
- U tim slučajevima koristite optimizaciju memorije: čuvajte samo prethodni red (O(m) prostora).
4. Granični slučajevi
- Jedna vrsta ili jedna kolona — DP se svodi na jednostavno sabiranje.
- Negativne vrednosti u matrici — minimalna ili maksimalna suma puta može zahtevati pažljivo postavljanje početnih uslova.
- Prepreke na rubovima matrice — proveriti DP relaciju da ne dolazi do pristupa van opsega.
5. Saveti za takmičare
- Prvo rešite osnovni problem bez prepreka, da razumete popunjavanje tabele.
- Zatim dodajte prepreke i optimizaciju suma.
- Uvek testirajte male primere ručno kako biste proverili logiku.
Napredna varijanta: Putanja sa minimalnim ukupnim pomeranjem ljudi
U ovoj varijanti svako polje matrice sadrži broj ljudi. Cilj je da se pronađe
putanja od gornjeg levog polja (1,1) do donjeg desnog polja
(N,M), uz kretanje samo udesno ili nadole. U ovom naprednom zadatku
redovi i kolone numerišu se od 1.
- udesno
- nadole
Za razliku od klasičnih grid DP zadataka, cilj nije da minimizujemo zbir vrednosti na samoj putanji, već:
ukupnu udaljenost koju će svi ljudi preći do najbližeg polja na izabranoj putanji.
Zašto običan DP nije dovoljan?
Kod standardnih zadataka često koristimo formulu:
dp[i][j] =
min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
Ovde to nije moguće primeniti direktno.
Razlog je što vrednost grid[i][j] nije unapred poznata.
Cena nekog polja ne zavisi samo od tog polja, već od:
- svih ljudi u matrici
- njihove udaljenosti do putanje
- načina na koji se putanja prostire kroz matricu
Zbog toga moramo promeniti način razmišljanja.
Ključna ideja
Umesto da posmatramo problem ovako:
- „svaka osoba traži najbliže polje na putanji“
mnogo je lakše razmišljati ovako:
- „putanja se postepeno širi kroz matricu“
Kada putanja stigne do novog polja:
- dodaje se novi deo reda ili kolone
- deo ljudi tada prvi put dolazi do putanje
- njihov doprinos ukupnoj ceni tada se računa tačno jednom
Na taj način dobijamo mogućnost da koristimo dinamičko programiranje.
Kako se putanja „širi“?
Ako do polja (i,j) dolazimo:
- odozgo → dodajemo novi deo trenutnog reda
- sleva → dodajemo novi deo trenutne kolone
Važno je primetiti:
- ne obrađujemo celu matricu svaki put ❌
- već samo „novi sloj“ koji je upravo dodat ✔
Šta predstavlja DP stanje?
Koristimo standardnu grid DP ideju:
dp[i][j]
predstavlja:
-
minimalnu ukupnu cenu za neku putanju koja stiže do polja
(i,j)
Prelaz ostaje sličan kao kod običnog grid DP-a:
dp[i][j] =
min(
dp[i-1][j] + cena_dolaska_odozgo,
dp[i][j-1] + cena_dolaska_sleva
);
Razlika je u tome što:
- „cena“ nije unapred data
- već je moramo efikasno izračunati
Naivna ideja — i problem
Pretpostavimo da dolazimo odozgo u polje (i,j).
Tada moramo dodati doprinos svih polja:
(i,1), (i,2), ..., (i,j-1)
Svako od tih polja mora da se pomeri udesno do kolone j.
Naivan pristup bio bi:
for (k = 1; k < j; k++)
suma += a[i][k] * (j - k);
Ali ako ovo radimo za svako polje matrice:
- složenost postaje prevelika ❌
Optimizacija pomoću prefiksnih suma
Umesto obilaska svakog polja posebno, koristimo:
- prefiksne sume
Pamtićemo:
- običan zbir elemenata
- ponderisan zbir elemenata
Na primer:
sum a[i][k] * (j - k)
=
j * sum(a[i][k]) - sum(a[i][k] * k)
Na taj način:
- ceo zbir računamo u O(1)
- bez prolaska kroz sva polja
Šta predstavljaju promenljive k i r?
U formulama se često pojavljuju promenljive:
k— tekuća kolona u redur— tekući red u koloni
Na primer:
sum a[i][k] * (j - k)
znači:
- prolazimo kroz sve kolone
klevo odj - svako polje mora da pređe
(j-k)koraka udesno
Slično:
sum a[r][j] * (i - r)
- prolazimo kroz sve redove
riznadi - svako polje mora da pređe
(i-r)koraka nadole
Zašto ovo radi?
Ključna osobina zadatka je:
- ljudi takođe smeju da se kreću samo: udesno ili nadole
Zbog toga:
- svako polje „pripada“ tačno jednom trenutku kada ga putanja prvi put dodirne
- nijedan doprinos se ne računa više puta
Upravo to omogućava efikasan DP pristup.
Dileme koje se često javljaju
-
❓ Da li treba pamtiti koja su polja već obrađena?
✔ Ne — to je implicitno određeno DP stanjem. -
❓ Da li svaki put prolazimo kroz celu kolonu ili red?
✔ Ne — koristimo prefiksne sume i dobijamo rezultat u O(1). -
❓ Da li se isti osoba računa više puta?
✔ Ne — svaki doprinos se dodaje tačno jednom. -
❓ Da li bi isti pristup radio da je dozvoljeno kretanje i ulevo ili nagore?
✔ Ne nužno — tada više ne bismo imali jednostavno „širenje“ putanje.
Zaključak
Ovaj zadatak predstavlja odličan primer kombinovanja:
- dinamičkog programiranja
- prefiksnih suma
- pažljivog modelovanja problema
Najvažnija ideja nije sama DP formula, već:
da pravilno odredimo šta predstavlja novi doprinos kada se putanja proširi.
Kada se problem pravilno modeluje:
- skupa računanja nestaju
- a složen zadatak postaje standardan DP sa optimizacijama ✔
Vizuelna intuicija — kako se putanja „širi“
Najvažnija ideja u ovom zadatku je da se putanja ne posmatra odjednom, već kao proces koji se postepeno širi kroz matricu.
Kada putanja stigne u novo polje:
- dodaje se novi deo reda ili kolone
- samo ta nova polja prvi put dolaze u kontakt sa putanjom
- njihov doprinos ukupnoj ceni tada računamo
Dolazak odozgo
Pretpostavimo da dolazimo iz polja (i-1,j) u polje
(i,j).
Na primer:
i = 4j = 3
Dakle, dolazimo u polje:
(4,3)
Putanja tada prvi put dodiruje deo četvrtog reda.
| X | X | X | X |
| X | X | X | X |
| X | X | X | X |
| ? | ? |
(i,j)
(4,3) |
. |
- X = već obrađena polja
- ? = nova polja koja sada prvi put ulaze u obračun
- (i,j) = trenutno polje u koje DP dolazi
Kada dolazimo odozgo:
- dodaje se novi deo reda
- konkretno polja:
(i,1), (i,2), ..., (i,j)
Za primer iznad to su:
(4,1), (4,2), (4,3)
Svi ljudi iz tih polja moraju da se pomere udesno
do kolone j.
Ako se neko nalazi u polju:
(i,k)
onda prelazi:
j - k
koraka.
Šta predstavlja promenljiva k?
Promenljiva k predstavlja:
- tekuću kolonu unutar trenutnog reda
Naivan pristup bio bi:
for(int k = 1; k < j; k++) {
suma += a[i][k] * (j - k);
}
Dakle:
- obilazimo sva polja levo od kolone
j - i sabiramo njihov doprinos
Ali to bi bilo presporo.
Zato koristimo prefiksne sume i formulu:
∑ a[i][k] * (j-k)
=
j * ∑a[i][k] - ∑(a[i][k] * k)
Dolazak sleva
Sada pretpostavimo da dolazimo iz:
(i,j-1)
u polje:
(i,j)
Na primer:
i = 4j = 4
| X | X | X | ? |
| X | X | X | ? |
| X | X | X | ? |
| X | X | X |
(i,j)
(4,4) |
Ovde dodajemo novu kolonu:
(1,j), (2,j), ..., (i,j)
Za primer iznad:
(1,4), (2,4), (3,4), (4,4)
Ljudi sada moraju da se pomere nadole do reda i.
Ako se neko nalazi u:
(r,j)
onda prelazi:
i - r
koraka.
Šta predstavlja promenljiva r?
Promenljiva r predstavlja:
- tekući red unutar trenutne kolone
Naivan pristup bio bi:
for(int r = 1; r < i; r++) {
suma += a[r][j] * (i - r);
}
Ali i ovo bi bilo presporo za velike matrice.
Zato koristimo:
∑ a[r][j] * (i-r)
=
i * ∑a[r][j] - ∑(a[r][j] * r)
Zašto je sve ovo važno?
- svaki doprinos računamo tačno jednom ✔
- ne obilazimo celu matricu pri svakom koraku ✔
- DP ostaje efikasan ✔
- prefiksne sume omogućavaju računanje u O(1) ✔
Interaktivni primer — prefiksne sume i K×K kvadrat
Sledeći primer prikazuje kako funkcionišu prefiksne sume u matrici.
Pređi mišem preko ćelije tabele da vidiš:
- vrednost prefiksne sume za to polje
- zbir K×K kvadrata koji se završava u toj ćeliji (ako postoji)
Šta predstavlja svaka ćelija?
Svaka ćelija prikazuje:
prefix[i][j]
odnosno zbir svih elemenata:
(0,0) → (i,j)
Na primer:
prefix[2][2]
sadrži zbir svih elemenata unutar regiona:
X X X
X X X
X X X
Kako dobijamo zbir K×K kvadrata?
Kada želimo zbir nekog regiona:
- ne prolazimo kroz sva polja kvadrata ❌
- već koristimo već izračunate prefiksne vrednosti ✔
Formula:
sum =
prefix[i][j]
- prefix[i-K][j]
- prefix[i][j-K]
+ prefix[i-K][j-K]
Na ovaj način:
- svaki zbir dobijamo u O(1)
- bez dodatnih petlji
Šta treba da primetiš?
- Prefiksne sume pamte informacije o prethodnim poljima ✔
- Veliki regioni mogu da se računaju veoma brzo ✔
- Umesto ponovnog sabiranja, koristimo već pripremljene rezultate ✔
Povezivanje sa glavnim zadatkom
U naprednom zadatku sa minimalnim ukupnim pomeranjem:
- ne računamo samo običan zbir
- već zbir ponderisan udaljenošću
Ali osnovna ideja ostaje ista:
- izbegavamo prolazak kroz veliki broj polja
- koristimo unapred pripremljene sume
- dobijamo rezultat u konstantnom vremenu
Ključna intuicija
Prefiksne sume možeš posmatrati kao:
- „memoriju matrice“
One unapred pamte:
- zbirove prethodnih regiona
- kako kasnije ne bismo morali ponovo da obilazimo ista polja
Veza sa naprednim Grid DP
U Zadatku 7 ispod, ponderisane prefiksne sume omogućavaju da se brzo izračunaju dodatne cene pomeranja ljudi. Ovo pokazuje kako se ideje iz rada sa prefiksnim sumama mogu kombinovati sa Grid DP-om da bi se rešili složeniji problemi.
Zadaci za vežbu — dinamičko programiranje u mreži
Zadatak 1: Brojanje puteva u mreži 3×3
Data je mreža dimenzija 3 × 3. Dozvoljena su kretanja samo udesno i nadole. Odredi koliko različitih puteva vodi od gornjeg levog do donjeg desnog ugla.
Zadatak 2: Minimalna suma puta 4×4
Data je matrica dimenzija 4 × 4:
- {2, 3, 1, 4}
- {5, 6, 2, 1}
- {1, 2, 7, 3}
- {4, 3, 2, 1}
Odredi minimalnu sumu puta od gornjeg levog do donjeg desnog ugla. Kretanja: desno i dole.
Zadatak 3: Grid sa preprekama 5×5
Data je matrica dimenzija 5 × 5, gde 0 označava slobodno polje,
a 1 prepreku. Kretanje je dozvoljeno samo desno i dole.
Odredi broj puteva od gornjeg levog do donjeg desnog ugla, bez prolaska kroz prepreke.
0 0 0 0 0
0 1 0 1 0
0 0 0 0 0
1 0 1 0 0
0 0 0 0 0
Dodatni zadaci za vežbu (napredni)
Zadatak 4: Maksimalna suma puta 4×4
Data je matrica dimenzija 4 × 4 sa vrednostima polja:
- {1, 2, 3, 4}
- {2, 1, 4, 3}
- {3, 2, 1, 2}
- {4, 3, 2, 1}
Odredi maksimalnu sumu puta od gornjeg levog do donjeg desnog ugla. Dozvoljena kretanja: desno i dole.
Uputstvo (kratko):
dp[i][j] = grid[i][j] + max(dp[i-1][j], dp[i][j-1])
Zadatak 5: Maksimalna suma puta sa preprekama 5×5
Data je matrica dimenzija 5 × 5. Vrednost -1 označava
prepreku kroz koju se ne može proći, dok ostala polja sadrže vrednosti.
Dozvoljena kretanja su samo desno i dole. Odrediti maksimalnu sumu vrednosti
na putu od gornjeg levog do donjeg desnog polja, bez prolaska kroz prepreke.
0 2 3 4 1
2 -1 1 5 2
3 2 -1 1 3
1 4 2 2 1
2 1 3 1 2
Uputstvo (kratko):
dp[0][0] = grid[0][0], ako početno polje nije prepreka
dp[i][j] = -∞, ako je grid[i][j] == -1
best = max(dp[i-1][j], dp[i][j-1])
(polja van matrice tretiraju se kao -∞)
dp[i][j] = grid[i][j] + best, ako best != -∞
dp[i][j] = -∞, ako best == -∞
Zadatak 6: Takmičarska varijanta — Veliki grid i brojanje optimalnih puteva
Data je matrica dimenzija 1000 × 1000, sa vrednostima polja od 0 do 1000. Od polja (0,0) do polja (999,999) dozvoljena su kretanja samo udesno i nadole. Potrebno je pronaći broj puteva čiji je zbir vrednosti polja maksimalan. Pošto broj optimalnih puteva može biti veliki, rezultat ispisati modulo 1e9+7.
Uputstvo (kratko):
Za svako polje čuvaj:
- best[i][j]: maksimalnu sumu puta do tog polja
- ways[i][j]: broj puteva koji ostvaruju tu maksimalnu sumu
Uporedi maksimalne sume koje dolaze odozgo i sleva.
Ako su jednake, saberi brojeve odgovarajućih puteva modulo MOD.
Za O(m) memorije čuvaj samo jedan red maksimalnih suma i brojeva puteva.
Modulo primenjuj samo na broj puteva, a ne na maksimalnu sumu.
Šablon za proveru rešenja — Zadaci 4–6
Zadaci 4 i 5 — Maksimalna suma puta (male matrice)
Ovaj šablon možeš koristiti za proveru maksimalne sume puta u manjoj matrici.
Polje čija je vrednost -1 predstavlja prepreku.
Vrednost NEG_INF označava prepreke i polja do kojih nije moguće stići.
Provera DP tabele — C++ šablon
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
if (n <= 0 || m <= 0) {
cout << "Neispravne dimenzije\n";
return 0;
}
vector<vector<long long>> grid(
n, vector<long long>(m)
);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> grid[i][j];
const long long NEG_INF =
numeric_limits<long long>::lowest() / 4;
vector<vector<long long>> dp(
n, vector<long long>(m, NEG_INF)
);
if (grid[0][0] != -1)
dp[0][0] = grid[0][0];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == -1)
continue;
if (i == 0 && j == 0)
continue;
long long best = NEG_INF;
if (i > 0)
best = max(best, dp[i - 1][j]);
if (j > 0)
best = max(best, dp[i][j - 1]);
if (best != NEG_INF)
dp[i][j] = best + grid[i][j];
}
}
if (dp[n - 1][m - 1] == NEG_INF)
cout << "Ne postoji validan put\n";
else
cout << dp[n - 1][m - 1] << '\n';
return 0;
}
Zadatak 6 — Takmičarska varijanta (veliki grid i modul)
Za matricu dimenzija n, m ≤ 1000 potrebno je prebrojati puteve
koji ostvaruju maksimalnu moguću sumu. Sledeći kod koristi O(m) memorije
i ispisuje broj optimalnih puteva modulo 1e9+7.
Šablon — C++ (veliki grid)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const long long MOD = 1000000007LL;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
if (n <= 0 || m <= 0) {
cout << 0 << '\n';
return 0;
}
vector<long long> best(m, 0);
vector<long long> ways(m, 0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
long long vrednost;
cin >> vrednost;
if (i == 0 && j == 0) {
best[j] = vrednost;
ways[j] = 1;
continue;
}
// best[j] sadrži vrednost iz polja iznad.
long long topBest = (i > 0) ? best[j] : -1;
long long topWays = (i > 0) ? ways[j] : 0;
// best[j-1] sadrži vrednost iz polja sleva.
long long leftBest = (j > 0) ? best[j - 1] : -1;
long long leftWays = (j > 0) ? ways[j - 1] : 0;
if (topBest > leftBest) {
best[j] = topBest + vrednost;
ways[j] = topWays;
} else if (leftBest > topBest) {
best[j] = leftBest + vrednost;
ways[j] = leftWays;
} else {
best[j] = topBest + vrednost;
ways[j] = (topWays + leftWays) % MOD;
}
}
}
cout << ways[m - 1] << '\n';
return 0;
}
Vizuelna DP tabela — Zadatak 5 (grid sa preprekama)
Prepreke su označene slovom X.
Ćelija dp[i][j] predstavlja maksimalnu sumu puta od početnog polja
(0,0) do polja (i,j), uz kretanje samo udesno i nadole
i bez prolaska kroz prepreke.
| i \ j | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | 0 | 2 | 5 | 9 | 10 |
| 1 | 2 | X | 6 | 14 | 16 |
| 2 | 5 | 7 | X | 15 | 19 |
| 3 | 6 | 11 | 13 | 17 | 20 |
| 4 | 8 | 12 | 16 | 18 | 22 |
Kako čitati tabelu:
- Ćelija dp[1][2] = 6 predstavlja maksimalnu sumu puta do polja (1,2), uz izbegavanje prepreka.
- Ćelija označena sa X predstavlja prepreku kroz koju nije moguće proći.
- Odredišna ćelija dp[4][4] = 22 daje maksimalnu sumu puta od početnog do donjeg desnog ugla.
Tabela prikazuje kako se maksimalne sume prenose kroz mrežu i kako prepreke isključuju nedozvoljene puteve.
Interaktivna DP tabela — Zadatak 5
Pređi kursorom preko ćelije da vidiš vrednosti iz kojih se dobija maksimalna suma.
Napredni zadatak 7: Minimalno ukupno pomeranje ljudi
Napredni izazov: Ovaj zadatak kombinuje dinamičko programiranje sa ponderisanim prefiksnim sumama. Za razliku od standardnih Grid DP problema, ne brojimo puteve niti minimizujemo zbir vrednosti duž puta, već računamo ukupnu udaljenost koju pređu svi ljudi.
Data je matrica dimenzija N × M. Svako polje
a[i][j] sadrži broj ljudi koji se nalaze u tom polju.
Potrebno je izabrati put od gornjeg levog polja (1,1) do donjeg
desnog polja (N,M). Putanja može da se kreće samo:
- udesno →
- nadole ↓
Kada se putanja odabere, svaka osoba treba da stigne do najbližeg polja na toj putanji, krećući se takođe samo udesno ili nadole. Udaljenost osobe jednaka je broju koraka potrebnih da stigne do putanje.
Cilj: Pronaći putanju koja minimizuje zbir udaljenosti svih ljudi.
Napomena o indeksiranju: Formule i implementacija ovog zadatka
koriste indeksiranje od 1: redovi su numerisani od 1 do N,
a kolone od 1 do M. To se razlikuje od prethodnih primera,
koji koriste indeksiranje od 0.
Skriveni mini kviz — Grid DP (2D DP problem)
Proveri svoje razumevanje Grid DP-a, brojanja puteva, prepreka i optimalnih putanja.
Otvori kviz
Mini kviz: Grid DP i prepreke
1. Koja je standardna DP relacija za maksimalnu sumu puta u mreži kada su dozvoljena samo kretanja udesno i nadole i nema prepreka?
2. U Grid DP problemu brojanja puteva, koju vrednost treba da ima dp[i][j] ako se u polju (i,j) nalazi prepreka?
3. U problemu maksimalne sume puta, kako možemo rekonstruisati optimalan put nakon popunjavanja DP tabele?
4. U problemu brojanja puteva bez prepreka, kako se inicijalizuju prvi red i prva kolona kada su dozvoljena samo kretanja udesno i nadole?
5. Kolika je vremenska složenost standardnog Grid DP algoritma koji svako polje mreže dimenzija n × m izračunava jednom?
4. Povezane stranice
5. Spoljašnji resursi za dalje učenje
6. Preporučene knjige i online kursevi
- “Introduction to Algorithms” (Cormen, Leiserson, Rivest, Stein) — klasična knjiga koja detaljno pokriva DP paradigmu i njene primene.
- “Competitive Programming 3” (Steven Halim) — praktični primeri i takmičarski problemi sa DP-jem, grid problemima i optimizacijama.
- MIT OCW 6.006 — Introduction to Algorithms — besplatan online kurs sa predavanjima, problem setovima i DP lekcijama.
- Coursera: Algorithms Part I (Princeton) — detaljna obrada osnovnih i naprednih DP problema kroz praktične zadatke.
- freeCodeCamp — Dynamic Programming for Beginners — interaktivni tutorijal sa vizuelnim primerima i praktičnim zadacima.
| Prethodno |<DP: Problem minimalan broj kovanica |
Sledeće DP: Problem mreža (grid dp)>| |
