Kako ubrzati rešenje: tipične optimizacije

Lekcija 4 od 4 · Složenost algoritama

Prvo rešenje koje padne na pamet često je tačno, ali presporo. Ova lekcija pokazuje najčešće načine da se rešenje ubrza – od manjeg broja koraka u petlji, preko pamćenja već izračunatog, do izbora bolje strukture podataka – i kada koja ideja pomaže.

Pre nego što počneš: potrebno predznanje – složenost algoritama.
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.

Manje koraka: delioci do √n

Delioci broja dolaze u parovima d i n / d, a manji od njih je najviše √n. Zato je dovoljno proveravati do korena: O(√n) umesto O(n). Za n = 1012 to je 106 koraka umesto 1012.

#include <iostream>
using namespace std;

int main() {
    long long n;
    cin >> n;
    long long br = 0;
    for (long long d = 1; d * d <= n; d++) {
        if (n % d == 0) {
            br++;                         // delilac d
            if (d != n / d) br++;         // i njegov par n/d (ako nije isti, kod kvadrata)
        }
    }
    cout << br << endl;
    return 0;
}

Ne računaj isto dva puta

Ako se ista vrednost računa više puta u petlji, izračunaj je jednom pre petlje. Ako upiti traže zbirove delova niza – pripremi prefiksne sume. Ako rekurzija računa iste potprobleme – zapamti rezultate (dinamičko programiranje).

SporoBržeDobitak
zbir dela niza petljom za svaki upitprefiksne sumeO(n·q) → O(n + q)
for (i = 0; i < strlen(s); i++)int d = strlen(s); pre petljeO(n²) → O(n)
rekurzivni Fibonačiniz sa zapamćenim vrednostimaO(2ⁿ) → O(n)
provera „da li je prost“ za svaki broj do nEratostenovo sitoO(n√n) → O(n log log n)

Bolja struktura podataka

Operacija koja se ponavljaSporoBrzo
„da li je x već viđen?“prolaz kroz niz, O(n)set ili niz brojača, O(log n) / O(1)
„koji je trenutno najmanji?“traženje minimuma, O(n)priority_queue, O(log n)
„koliko elemenata je ≤ x?“brojanje, O(n)sortiranje + upper_bound, O(log n)
brisanje sa početkavector::erase(begin), O(n)deque ili queue, O(1)
zbir dela niza uz izmenepetlja, O(n)Fenwick stablo, O(log n)

Brz unos i ispis

Kod velikih ulaza (106 brojeva) i samo učitavanje može da traje predugo. Dve linije na početku main višestruko ubrzavaju cin i cout, a "\n" umesto endl ne prazni izlaz posle svakog reda:

#include <iostream>
using namespace std;

int main() {
    ios::sync_with_stdio(false);   // cin/cout bez usklađivanja sa scanf/printf
    cin.tie(nullptr);              // cin ne čeka da se cout isprazni
    int n;
    cin >> n;
    long long s = 0;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        s += x;
    }
    cout << s << "\n";             // "\n" umesto endl
    return 0;
}

Posle ovih linija ne mešaj cin sa scanf ni cout sa printf.

Redosled razmišljanja

  1. Napiši najjednostavnije tačno rešenje (gruba sila) i proveri ga na primerima.
  2. Izračunaj njegovu složenost i uporedi sa ograničenjima (tabela).
  3. Ako je presporo: šta se ponavlja? Može li se zapamtiti, pripremiti unapred, izbrojati formulom, ili pronaći sortiranjem i binarnom pretragom?
  4. Sporije rešenje sačuvaj – njime proveravaš brže na slučajnim malim primerima.

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

Savršeni brojevi

Broj je savršen ako je jednak zbiru svojih delilaca manjih od njega (6 = 1 + 2 + 3). Za dati broj n ≤ 1012 ispiši DA ako je savršen, inače NE.

Primer 1

Ulaz

6

Izlaz

DA

Primer 2

Ulaz

28

Izlaz

DA

Primer 3

Ulaz

12

Izlaz

NE

Primer 4

Ulaz

137438691328

Izlaz

DA
ZADATAK 02Težina: Lako

Prvi ponovljeni broj

Dato je n ≤ 2·105 brojeva do 109. Ispiši prvi broj (gledano sleva) koji se već pojavio ranije, ili -1. Rešenje O(n²) je prespor.

Primer 1

Ulaz

6
5 3 9 3 5 1

Izlaz

3

Primer 2

Ulaz

3
1 2 3

Izlaz

-1
Česte greške i saveti
  • Optimizuj tek kada imaš tačno rešenje – brzo a netačno ne vredi.
  • U petlji d * d <= n koristi long long, inače d * d prekoračuje opseg.
  • endl u petlji sa milion ispisa može da uspori program više od samog računanja.

Proveri znanje

Otvori kviz

Ubrzavanje rešenja

1. Do kog broja je dovoljno tražiti delioce broja n?




2. Šta ubrzava ponovljene upite „zbir od l do r“?




3. Čemu služi ios::sync_with_stdio(false)?