Binarna pretraga ne služi samo za traženje u nizu. Ako je lako proveriti da li je neki broj dovoljno dobar odgovor, a teško ga direktno izračunati, odgovor tražimo binarnom pretragom po svim mogućim vrednostima. To je jedna od najčešćih tehnika na okružnim i državnim takmičenjima.
Pre nego što počneš: potrebno predznanje – binarna pretraga, pohlepni algoritmi. Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.
Ideja: monotona provera
Pretpostavimo da imamo funkciju moze(x) koja odgovara sa da/ne i ima osobinu monotonosti: ako je odgovor „da“ za neko x, onda je „da“ i za svako veće x. Tada vrednosti izgledaju ovako:
x: 1 2 3 4 5 6 7 8
moze(x): ne ne ne ne da da da da
^ tražimo prvo "da"
Granicu između „ne“ i „da“ nalazimo binarnom pretragom, uz samo log₂(opseg) poziva funkcije moze.
// najmanje x iz [lo, hi] za koje je moze(x) tačno (pretpostavka: moze(hi) je tačno)
long long lo = 1, hi = 1000000000, odgovor = hi;
while (lo <= hi) {
long long mid = lo + (hi - lo) / 2;
if (moze(mid)) {
odgovor = mid; // mid je dobar; probaj manje
hi = mid - 1;
} else {
lo = mid + 1; // mid nije dobar; treba veće
}
}
Primer 1: mašine
Zadatak. Fabrika ima n mašina; mašini i treba t[i] sekundi za jedan proizvod, a sve rade istovremeno. Za koje najmanje vreme mogu da naprave ukupno P proizvoda? (n ≤ 2·105, t[i], P ≤ 109)
Provera: za dato vreme T mašina i napravi T / t[i] proizvoda. Ako je zbir bar P, vreme T je dovoljno. Što je više vremena, proizvoda je više – provera je monotona. Gornja granica: najbrža mašina sama napravi P proizvoda za min(t) · P sekundi.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
long long P;
vector<long long> t;
bool moze(long long T) { // da li za T sekundi nastaje bar P proizvoda?
long long ukupno = 0;
for (int i = 0; i < n; i++) {
ukupno += T / t[i];
if (ukupno >= P) return true; // prekid i da zbir ne bi prekoračio long long
}
return false;
}
int main() {
cin >> n >> P;
t.resize(n);
for (auto &x : t) cin >> x;
long long lo = 1, hi = *min_element(t.begin(), t.end()) * P, odgovor = hi;
while (lo <= hi) {
long long mid = lo + (hi - lo) / 2;
if (moze(mid)) { odgovor = mid; hi = mid - 1; }
else lo = mid + 1;
}
cout << odgovor << endl;
return 0;
}
Za prvi primer: za T = 8 mašine naprave 2 + 4 + 1 = 7 proizvoda, a za T = 7 samo 2 + 3 + 1 = 6. Složenost: O(n · log(min(t)·P)) ≈ 60 · n operacija.
Primer 2: testera (najveće dobro x)
Zadatak. Data su stabla visina h[i]. Testera se podesi na visinu H i odseče sve iznad nje: od stabla visine h > H dobija se h − H metara drveta. Koja je najveća visina H uz koju se dobije bar M metara?
Ovde je provera monotona u drugom smeru: što je testera niže, drveta je više. Zato tražimo poslednje „da“: kada je mid dobar, pamtimo ga i idemo desno.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
long long M;
cin >> n >> M;
vector<long long> h(n);
for (auto &x : h) cin >> x;
long long lo = 0, hi = *max_element(h.begin(), h.end()), odgovor = 0;
while (lo <= hi) {
long long H = lo + (hi - lo) / 2;
long long drvo = 0;
for (long long x : h)
if (x > H) drvo += x - H;
if (drvo >= M) { odgovor = H; lo = H + 1; } // dovoljno: probaj višu testeru
else hi = H - 1; // premalo: spusti testeru
}
cout << odgovor << endl;
return 0;
}
Binarna pretraga nad realnim brojevima
Kada je odgovor realan broj, umesto lo <= hi ponavljamo korak fiksan broj puta (na primer 100). Svaki korak prepolovi interval, pa posle 100 koraka greška je zanemarljiva. Primer: kvadratni koren broja bez funkcije sqrt.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double x;
cin >> x; // x >= 1
double lo = 0, hi = x; // koren je u [0, x]
for (int k = 0; k < 100; k++) {
double mid = (lo + hi) / 2;
if (mid * mid >= x) hi = mid; // mid je dovoljno velik
else lo = mid;
}
cout << fixed << setprecision(6) << hi << endl;
return 0;
}
Binarnu pretragu nad realnim granicama koristi i zadatak Kružne zone.
Kako prepoznati zadatak?
Traži se najmanja ili najveća vrednost nečega (vreme, dužina, visina, kapacitet).
Za zadatu vrednost lako je proveriti da li je „dovoljna“ – obično jednim prolazom kroz niz, često pohlepno.
Ako je vrednost dovoljna, i svaka veća (ili svaka manja) je dovoljna.
Česte formulacije: „minimizovati maksimum“ (podela niza na k delova sa najmanjim najvećim zbirom) i „maksimizovati minimum“ (rasporediti krave/rutere da najmanje rastojanje bude najveće).
Zadaci za vežbu
Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.
ZADATAK 01Težina: Srednje
Podela niza na k delova
Niz od n pozitivnih brojeva treba podeliti na najviše k uzastopnih delova tako da najveći zbir nekog dela bude što manji. Ispiši taj najmanji mogući najveći zbir. (1 ≤ k ≤ n ≤ 105, brojevi do 109)
Primer 1
Ulaz
5 3
2 4 7 3 5
Izlaz
8
Primer 2
Ulaz
4 1
1 2 3 4
Izlaz
10
Uputstvo
Provera za granicu G: idi sleva i dodaj elemente u tekući deo dok zbir ne pređe G; tada započni novi deo. Ako je potrebno najviše k delova, G je dovoljno. Donja granica je najveći element, gornja zbir svih.
Rešenje (C++)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n, k;
vector<long long> a;
bool moze(long long G) { // može li se podeliti na <= k delova sa zbirom <= G?
int delova = 1;
long long zbir = 0;
for (long long x : a) {
if (zbir + x > G) { // x ne staje u tekući deo
delova++;
zbir = 0;
}
zbir += x;
}
return delova <= k;
}
int main() {
cin >> n >> k;
a.resize(n);
long long lo = 0, hi = 0;
for (auto &x : a) {
cin >> x;
lo = max(lo, x); // svaki deo mora da primi najveći element
hi += x; // jedan deo sa svim elementima
}
long long odgovor = hi;
while (lo <= hi) {
long long mid = lo + (hi - lo) / 2;
if (moze(mid)) { odgovor = mid; hi = mid - 1; }
else lo = mid + 1;
}
cout << odgovor << endl;
return 0;
}
ZADATAK 02Težina: Teže
Raspored rutera
Na ulici je n kuća na pozicijama x[i]. Treba postaviti c rutera u neke od kuća tako da najmanje rastojanje između dva rutera bude što veće. Ispiši to najveće moguće najmanje rastojanje. (2 ≤ c ≤ n ≤ 105, pozicije do 109)
Primer
Ulaz
5 3
1 2 8 4 9
Izlaz
3
Uputstvo
Sortiraj pozicije. Provera za rastojanje D: postavi ruter u prvu kuću, a zatim pohlepno u svaku sledeću kuću koja je bar D daleko od poslednjeg rutera. Ako je postavljeno bar c rutera, D je ostvarivo. Tražimo najveće ostvarivo D.
Rešenje (C++)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n, c;
vector<long long> x;
bool moze(long long D) {
int postavljeno = 1;
long long poslednji = x[0];
for (int i = 1; i < n; i++) {
if (x[i] - poslednji >= D) { // dovoljno daleko: postavi ruter
postavljeno++;
poslednji = x[i];
}
}
return postavljeno >= c;
}
int main() {
cin >> n >> c;
x.resize(n);
for (auto &v : x) cin >> v;
sort(x.begin(), x.end());
long long lo = 1, hi = x[n - 1] - x[0], odgovor = 0;
while (lo <= hi) {
long long mid = lo + (hi - lo) / 2;
if (moze(mid)) { odgovor = mid; lo = mid + 1; } // ide i veće
else hi = mid - 1;
}
cout << odgovor << endl;
return 0;
}
Česte greške i saveti
Proveri smer monotonosti: tražiš li prvo „da“ (idi levo kad je dobar) ili poslednje „da“ (idi desno)?
Granice izaberi tako da je odgovor sigurno u [lo, hi].
Koristi long long – proizvodi kao min(t) · P lako pređu 2·109.
U proveri prekini sabiranje čim je cilj dostignut, da zbir ne prekorači opseg.
Proveri znanje
Otvori kviz
Binarna pretraga po rešenju
1. Koju osobinu mora da ima provera moze(x)?
2. Opseg mogućih odgovora je od 1 do 10⁹. Koliko otprilike puta se poziva provera?
3. Traži se najveća vrednost za koju je odgovor „da“. Kada je mid dobar, šta radimo?
Podešavanja kolačića
Koristimo kolačiće da bismo vam pružili najbolje moguće iskustvo. Takođe nam omogućavaju da analiziramo ponašanje korisnika kako bismo stalno poboljšavali veb lokaciju za vas.