PRIPREMA ZA OKRUŽNA TAKMIČENJA: 7 i 8. RAZRED

Sadržaj stranice

Dobrodošli na stranicu posvećenu pripremi za okružna takmičenja iz informatike za 5. i 6. razred. Ova stranica je deo sveobuhvatnog programa pripreme koji pretpostavlja osnovno znanje iz oblasti, a ovde se to znanje dopunjuje specifičnostima takmičarskog programa. Na ovoj stranici možete pronaći ključne teme, primere zadataka, objašnjenja i interaktivne alate koji će vam pomoći da se bolje pripremite za takmičenja.

Uvod


Ova stranica predstavlja vodič za pripremu rešavanja zadataka koji se pojavljuju na okružnim takmičenjima iz informatike, namenjen regionalnom nivou za učenike 7. i 8. razreda. Materijal ovde predstavlja dopunu osnovnog gradiva, a detaljnije osnove i dodatne primere možete pronaći putem priloženih linkova.

Takođe, dodatna priprema i vežbanje mogu se obavljati i na sajtu: https://takprog.petlja.org/osnovnaskola/posts/27.

Ovaj vodič sadrži jednostavne primere koji su sastavni deo većih zadataka, a služe kao osnova za dalju razradu i usavršavanje u rešavanju kombinatoričkih, geometrijskih i algoritamskih problema.


Analiza podserija

U ovom odeljku razmatramo kako se ulazna serija može razložiti na manje podserije. Primeri uključuju:

  • Podserije fiksne dužine – na primer, grupisanje dnevnih vrednosti u sedmice (podserije od 7 uzastopnih elemenata).
  • Podserije sa separatorom – na primer, niz brojeva razdvojenih nulom, gde se nula koristi kao separator između podserija.

Slede dva primera sa objašnjenjima i kodom:

Primer 1: Podserije fiksne dužine (sedmice)

Pretpostavimo da imamo niz dnevnih vrednosti i želimo da ih podelimo u podserije od 7 uzastopnih elemenata, što odgovara sedmici u jednoj godini. U ovom primeru, program učitava broj dnevnih vrednosti, zatim učitava vrednosti i na kraju ispisuje sedmične grupe.

#include<iostream>#include<vector>using namespacestd;intmain() {
    int n;
    cout << "Unesite broj dnevnih vrednosti: ";
    cin >> n; // Učitavanje broja elemenatavector<int> dnevne(n);
    
cout << "Unesite dnevne vrednosti: " << endl; for (int i = 0; i < n; i++) { cin >> dnevne[i]; // Učitavanje pojedinačne vrednosti }
cout << "Podserije (sedmice):" << endl; for (int i = 0; i < n; i++) { cout << dnevne[i] << " "; if ((i + 1) % 7 == 0) cout << endl; // Prelazak u novi red nakon svakih 7 elemenata }
return0; }

Primer 2: Podserije sa separatorom

U ovom primeru, ulazni niz brojeva sadrži podserije koje su razdvojene nulom kao separatorom. Na primer, ako je ulaz: 5 3 7 0 8 9 2 0 4 1, onda se niz deli na tri podserije:

  • 5 3 7 – elementi pre prve nule,
  • 8 9 2 – elementi između prve i druge nule,
  • 4 1 – elementi posle druge nule.

Program prepoznaje nulu kao separator i izdvaja sve podserije za dalju obradu.

#include<iostream>#include<vector>using namespacestd;intmain() {
    vector<int> brojevi;
    int x;
    cout << "Unesite brojeve (0 kao separator): " << endl;
    while (cin >> x) {
        brojevi.push_back(x); // Čuvanje unetih brojeva u vektor
    }
    
cout << "Podserije:" << endl; vector<int> segment; for (int i = 0; i < brojevi.size(); i++) { if (brojevi[i] == 0) { for (int j = 0; j < segment.size(); j++) { cout << segment[j] << " "; } cout << endl; segment.clear(); // Čišćenje segmenta nakon ispisivanja } else { segment.push_back(brojevi[i]); // Dodavanje broja u tekući segment } } if (!segment.empty()) { for (int j = 0; j < segment.size(); j++) { cout << segment[j] << " "; } cout << endl; } return0; }

Ovi primeri pokazuju kako se ulazna serija može razložiti na manje segmente (podserije) u zavisnosti od zadatog kriterijuma (fiksna dužina ili separator). Ova tehnika je korisna u analizi podataka i pripremi za takmičenja iz informatike.

Za dodatne informacije i primere, pogledajte resurse na stranici: Stringovi u C++ jeziku.

Za dodatne informacije o vektorima u c++, pogledajte resurse na stranici: Vektori u C++ jeziku.

Testirajte svoj kod ovde

// Ovde pišite C++ kod...

Transformacije sa nizovima


Formiranje novog niza na osnovu postojećeg


U ovom primeru ćemo formirati novi niz koji sadrži samo parne brojeve iz postojećeg niza. Na ovaj način se demonstrira kako možemo izvući određene elemente iz originalnog niza i smestiti ih u novi niz. Rezultujući niz može imati manji broj elemenata nego početni.

Program učitava postojeći niz, proverava uslov za svaki element, a zatim elemente koji zadovoljavaju uslov (parni brojevi) dodaje u novi niz.

#include<iostream>using namespacestd;intmain() {
    const int size = 8; // Veličina originalnog nizaint original[size] = {1, 2, 3, 4, 5, 6, 7, 8}; // Originalni nizint novi[size]; // Niz za parne brojeve (maksimalne dužine size)int count = 0; // Brojač za nove elementefor (int i = 0; i < size; i++) {
        if (original[i] %2 == 0) {
            novi[count] = original[i]; // Dodavanje parnog broja u novi niz
            count++; // Povećanje brojača
        }
    }
    
    cout << "Novi niz (samo parni brojevi): " << endl;
    for (int i = 0; i < count; i++) {
        cout << novi[i] << " ";
    }
    cout << endl;
    
    return0;
}
  

Premeštanje elemenata u okviru niza (ciklični pomeraj)


U ovom primeru prikazujemo kako se elementi niza mogu ciklično pomeriti udesno za određeni broj mesta. Program koristi privremeni niz za čuvanje poslednjih k elemenata, zatim pomera preostale elemente udesno, a na kraju vraća privremene elemente na početak.

Ovo je korisno kada je potrebno izvršiti rotaciju elemenata niza, na primer, kod problema iz takmičenja.

#include<iostream>using namespacestd;intmain() {
    const int size = 8;
    int arr[size] = {1, 2, 3, 4, 5, 6, 7, 8};
    int k; // Broj mesta za ciklični pomerajcout << "Unesite broj mesta za ciklični pomeraj: ";
    cin >> k;
    
// Normalizacija k (ako je k veći od veličine niza) k = k % size;
if (k ==0) { cout << "Niz ostaje nepromenjen." << endl; return0; }
int temp[k]; // Privremeni niz za čuvanje poslednjih k elemenata
// Čuvanje poslednjih k elemenata u privremeni nizfor (int i = 0; i < k; i++) { temp[i] = arr[size - k + i]; }
// Pomeranje preostalih elemenata udesnofor (int i = size - 1; i >= k; i--) { arr[i] = arr[i - k]; }
// Vraćanje elemenata iz privremenog niza na početakfor (int i = 0; i < k; i++) { arr[i] = temp[i]; }
cout << "Niz nakon cikličnog pomeraja: "; for (int i = 0; i < size; i++) { cout << arr[i] << " "; } cout << endl;
return0; }

Ovi primeri pokazuju kako se, pomoću ugnežđenih petlji, može formirati novi niz na osnovu postojećeg i kako se elementi niza mogu ciklično premeštati. Ove tehnike su od velikog značaja u rešavanju problema sa manipulacijom podataka na takmičenjima iz informatike.

Za dodatne informacije, pogledajte resurse na stranici: Nizovi u jeziku C++.

Uvod u matrice


Matrice (dvodimenzionalni nizovi) predstavljaju ključnu strukturu za organizaciju i obradu podataka u programiranju. One omogućavaju da se podaci rasporede u redove i kolone, što je od velikog značaja za rešavanje različitih problema – od jednostavnih tabela do kompleksnih matematičkih i statističkih analiza.

Za više informacija o osnovama rada sa matricama u C++, posetite stranicu Dvodimenzioni nizovi u programskom jeziku C++. Ukoliko vas zanima dinamički pristup i rad sa matricama, pogledajte i stranicu Dvodimenzioni dinamički nizovi i matrice u C++.

Primer 3: Prebrojavanje statistika nad matricom

U ovom primeru, imamo matricu i potrebno je izračunati osnovne statistike: zbir, proizvod, minimum i maksimum svih elemenata. Takođe, vrši se linearno pretraživanje za određeni element, preslikavanje (transformacija svakog elementa) i filtriranje (izdvajanje elemenata koji zadovoljavaju uslov).

#include<iostream>using namespacestd;intmain() {
    const int rows = 3, cols = 4;
    int matrix[rows][cols] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
int sum = 0, prod = 1, minVal = matrix[0][0], maxVal = matrix[0][0]; bool found = false; int searchValue = 7;
int mapped[rows][cols]; // Novi niz sa transformisanim elementimaint filtered[rows * cols]; // Niz za filtrirane elementeint countFiltered = 0;
for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { int val = matrix[i][j]; sum += val; prod *= val; if (val < minVal) minVal = val; if (val > maxVal) maxVal = val; if (val == searchValue) found = true; mapped[i][j] = val * 2; // Preslikavanje: svaki element se udvostručiif (val %2 == 0) { filtered[countFiltered] = val; countFiltered++; } } }
cout << "Sum: " << sum << endl; cout << "Product: " << prod << endl; cout << "Min: " << minVal << endl; cout << "Max: " << maxVal << endl; cout << "Element " << searchValue << " " << (found ? "je pronadjen." : "nije pronadjen." ) << endl;
cout << "Mapped matrix:" << endl; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { cout << mapped[i][j] << " "; } cout << endl; }
cout << "Filtered even numbers:" << endl; for (int i = 0; i < countFiltered; i++) { cout << filtered[i] << " "; } cout << endl;
return0; }

Primer 4: Statistika nad vrstama, kolonama i dijagonalama

U ovom primeru, imamo kvadratnu matricu i računamo statistike za svaku vrstu, kolonu i glavne dijagonale. Na primer, može se izračunati maksimum u svakoj vrsti, a zatim se iz tih maksimuma bira najmanji – što predstavlja kombinaciju "najmanji od maksimuma".

#include<iostream>using namespacestd;
intmain() { const int size = 3; int matrix[size][size] = { {5, 7, 3}, {9, 2, 8}, {4, 6, 1} };
// Računanje maksimuma u svakoj vrstiint rowMax[size]; for (int i = 0; i < size; i++) { int maxVal = matrix[i][0]; for (int j = 0; j < size; j++) { if (matrix[i][j] > maxVal) maxVal = matrix[i][j]; } rowMax[i] = maxVal; }
// Određivanje najmanjeg maksimuma iz svih vrstaint minOfRowMax = rowMax[0]; for (int i = 1; i < size; i++) { if (rowMax[i] < minOfRowMax) minOfRowMax = rowMax[i]; }
cout << "Najmanji od maksimuma po vrstama: " << minOfRowMax << endl;
return0; }

Primer 5: Pristupanje pravougaonim i trougaonim delovima matrice

U ovom primeru, prikazuje se kako se pristupa elementima pravougaonog dela matrice kao i trougaonom delu. Na primer, iz matrice se može izdvojiti pravougaoni podniz ili donji trougaoni deo.

#include<iostream>using namespacestd;
intmain() { const int rows = 4, cols = 4; int matrix[rows][cols] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16} };
// Pravougaoni podniz: izdvajanje elemenata iz redova 1-2 i kolona 2-3cout << "Pravougaoni podniz:" << endl; for (int i = 1; i <= 2; i++) { for (int j = 2; j <= 3; j++) { cout << matrix[i][j] << " "; } cout << endl; }
// Trougaoni podniz: donji trougaoni deo matricecout << "Donji trougaoni deo matrice:" << endl; for (int i = 0; i < rows; i++) { for (int j = 0; j < rows; j++) { if (j <= i) cout << matrix[i][j] << " "; elsecout << "0 "; } cout << endl; }
return0; }

Ovi primeri pokazuju kako se matrica može analizirati na više načina: izračunavanjem statistika, pretraživanjem, preslikavanjem, filtriranjem, kao i pristupanjem posebnim segmentima matrice.

Za više informacija, pogledajte resurse na stranici: Nizovi u jeziku C++.

Analiza podserija: Pristupanje elementima pravougaonih i trougaonih delova matrice


U ovom odeljku prikazujemo kako se, koristeći ugneždene petlje, može pristupiti određenom delu matrice. Na primer, iz kvadratne matrice možemo izdvojiti pravougaoni podniz ili trougaoni deo.

Primer 1: Pristupanje elementima pravougaonog dela matrice

U sledećem primeru imamo matricu dimenzija 4x4. Program izdvaja elemente iz reda 1 i 2 i kolona 2 i 3, čime se formira pravougaoni podniz.

#include<iostream>using namespacestd;intmain() {
    const int rows = 4, cols = 4;
    int matrix[rows][cols] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 16}
    };
    
// Izdvajanje pravougaonog dela: redovi 1 i 2 (indeksi 1 i 2) i kolone 2 i 3 (indeksi 2 i 3)cout << "Pravougaoni podniz:" << endl; for (int i = 1; i <= 2; i++) { for (int j = 2; j <= 3; j++) { cout << matrix[i][j] << " "; } cout << endl; }
return0; }

Primer 2: Premeštanje pravougaonog dela u okviru matrice

U ovom primeru prikazujemo kako se premešta određeni pravougaoni deo matrice na novu poziciju unutar iste matrice. Pretpostavimo da želimo da pomerimo podniz definisan redovima 1 i 2 i kolonama 1 i 2 na poziciju gde počinje na redu 2 i koloni 2.

#include<iostream>using namespacestd;
intmain() { const int rows = 4, cols = 4; int matrix[rows][cols] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16} };
// Definisanje pravougaonog dela za premeštanje: redovi 1 i 2, kolone 1 i 2int temp[2][2];
// Kopiranje podniza u privremeni nizfor (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { temp[i][j] = matrix[i+1][j+1]; } }
// Premeštanje kopiranog dela na novu poziciju: počevši od reda 2, kolone 2for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { matrix[i+2][j+2] = temp[i][j]; } }
// Ispis matrice nakon premeštanjafor (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { cout << matrix[i][j] << "\t"; } cout << endl; }
return0; }

Ovi primeri demonstriraju dve tehnike: formiranje novog niza (filtriranjem ili transformacijom elemenata) i premeštanje pravougaonih delova unutar matrice. Ove tehnike su ključne za analizu i obradu podataka u zadacima sa matricama, a posebno su korisne na takmičenjima iz informatike.

Za dodatne informacije, posetite: Nizovi u jeziku C++.

Metode efikasnosti algoritama 1


Efikasno sortiranje niza

U mnogim zadacima, naročito na takmičenjima, efikasno sortiranje niza je ključno za optimizaciju algoritama. Umesto kvadratnog vremena (O(n²)) za jednostavne metode sortiranja, bibliotečka funkcija sort (iz <algorithm>) omogućava sortiranje u prosečnom vremenu od O(n log n). Ovim se postiže brže prebrojavanje različitih elemenata ili određivanje preseka dva niza.

Za detaljnija objašnjenja o efikasnom sortiranju nizova i binarnoj pretrazi, posetite stranicu: Sortiranje nizova. Binarna pretraga.

U sledećem primeru, niz se sortira, a zatim se koriste funkcije unique i aritmetičke operacije za prebrojavanje različitih elemenata:

#include<iostream>#include<algorithm>#include<vector>
using namespacestd;
intmain() { vector<int> arr = {5, 3, 8, 5, 2, 8, 1};
// Sortiranje niza u rastućem redosledusort(arr.begin(), arr.end());
cout << "Sortirani niz: " << endl; for (int x : arr) { cout << x << " "; }
// Prebrojavanje različitih elemenataint distinct = unique(arr.begin(), arr.end()) - arr.begin();
cout << "\nBroj različitih elemenata: " << distinct << endl;
return0; }

Binarna pretraga sortiranog niza

Kada je niz sortiran, korišćenjem funkcije binary_search (iz <algorithm>) može se brzo pronaći da li određeni element postoji u nizu. Ova metoda ima vremensku složenost O(log n) u odnosu na linearno pretraživanje koje traje O(n). Takođe, koristeći lower_bound i upper_bound moguće je odrediti broj pojavljivanja određenog elementa.

Sledeći primer prikazuje kako se vrši binarna pretraga i prebrojavanje pojavljivanja elementa 5 u sortiranom nizu:

#include<iostream>#include<algorithm>#include<vector>
using namespacestd;
intmain() { vector<int> arr = {1, 2, 3, 4, 5, 5, 5, 6, 7};
// Niz je već sortiran, ali ukoliko nije, pozvati: sort(arr.begin(), arr.end());
bool found = binary_search(arr.begin(), arr.end(), 5);
cout << "Element 5 " << (found ? "je pronadjen." : "nije pronadjen.") << endl;
// Prebrojavanje pojavljivanja elementa 5int count = upper_bound(arr.begin(), arr.end(), 5) - lower_bound(arr.begin(), arr.end(), 5);
cout << "Broj pojavljivanja elementa 5: " << count << endl;
return0; }

Elementi teorije brojeva 1


Ova tema pokriva osnovne koncepte teorije brojeva, uključujući izračunavanje broja i zbira delioca prirodnog broja, utvrđivanje da li je broj prost, kao i nalaženje prostih brojeva. Razumevanje ovih osnova je ključno za rešavanje mnogih zadataka, posebno na takmičenjima.

1. Broj i zbir delioca prirodnog broja

Za dati prirodan broj, potrebno je pronaći sve njegove delioca i izračunati koliko ih ima i koliki je njihov zbir. Na primer, za broj 12, delioci su 1, 2, 3, 4, 6 i 12, a zbir im je 28.

#include<iostream>using namespacestd;intmain() {
    int n;
    cout << "Unesite prirodan broj: ";
    cin >> n;
    
int count = 0, sum = 0; for (int i = 1; i <= n; i++) { if (n % i == 0) { count++; // Povećaj broj delioca sum += i; // Saberi delioca } } cout << "Broj delioca: " << count << endl; cout << "Zbir delioca: " << sum << endl; return0; }

2. Utvrđivanje da li je broj prost

Broj je prost ako ima tačno dva delioca, 1 i sam broj. Sledeći primer proverava da li je uneti broj prost korišćenjem jednostavne petlje.

#include<iostream>using namespacestd;intmain() {
    int n;
    cout << "Unesite broj: ";
    cin >> n;
    
bool isPrime = true; if (n < 2) { isPrime = false; } else { for (int i = 2; i * i <= n; i++) { if (n % i == 0) { isPrime = false; break; // Ako je pronađen delilac, broj nije prost } } } cout << n << " je " << (isPrime ? "prost." : "nije prost." ) << endl; return0; }

Za dodatne resurse i primere o prostim brojevima i faktorizaciji , pogledajte stranicu: Prosti brojevi i faktorizacija.

Analiza podserija: Rastavljanje broja na proste činioce i primene

U ovoj temi razmatramo kako se broj može rastaviti na svoje proste činioce korišćenjem jednostavnih algoritama sa kompleksnošću O(√n). Na osnovu dobijene faktorizacije, moguće je izračunati najmanji prirodni broj kojim treba pomnožiti dati broj da bi se dobio potpuni kvadrat ili kub. Ovaj pristup je od velikog značaja u takmičarskim zadacima gde se traže optimizovana rešenja.

#include<iostream>using namespacestd;intmain() {
    int n;
    cout << "Unesite broj: ";
    cin >> n;
    

// Rastavljanje broja na proste činiocecout << "Prosti činioci broja: "; int original = n; for (int i = 2; i * i <= n; i++) { while (n % i ==0) { cout << i << " "; n /= i; } } if (n > 1) cout << n; cout << endl;
// Primenjena metoda: nalaženje najmanjeg prirodnog broja kojim se daje broj udvostručuje (potpuni kvadrat)int multiplier = 1; n = original; for (int i = 2; i * i <= n; i++) { int count = 0; while (n % i ==0) { count++; // Broji eksponente n /= i; } if (count %2 == 1) multiplier *= i; // Ako je eksponent neparan, uključujemo i u multiplier } if (n > 1) multiplier *= n; cout << "Najmanji množioc za potpuni kvadrat: " << multiplier << endl;
return0; }

Analiza NZD i NZS korišćenjem Euklidovog algoritma

Euklidov algoritam omogućava brzo izračunavanje najvećeg zajedničkog delioca (NZD) dva broja. Na osnovu NZD, lako se može izračunati najmanji zajednički sadržalac (NZS). Ove tehnike se često koriste u problemima kao što su skraćivanje razlomaka ili optimizacija zadataka u takmičenjima.

#include<iostream>using namespacestd;intgcd(int a, int b) {
    while (b !=0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

intmain() {
    int a, b;
    cout << "Unesite dva broja: ";
    cin >> a >> b;
    
int nzd = gcd(a, b); int nzs = (a / nzd) * b; // NZS se računa korišćenjem NZD
cout << "NZD: " << nzd << endl; cout << "NZS: " << nzs << endl;
cout << "Skraćeni razlomak: " << (a / nzd) << "/" << (b / nzd) << endl;
return0; }

Ove primere možete detaljnije proučiti na stranici: Prosti brojevi i faktorizacija. Euklidov algoritam.

Elementi efikasnosti algoritama 2


Takmičar treba da ume da oceni vremensku složenost algoritma koji primenjuje. To znači da je važno razumeti osnovne principe analize algoritama, kako bi se moglo predvideti koliko brzo će program izvršavati određene operacije. Za osnovni uvod u efikasnost algoritama, pogledajte informacije na portalu petlja.org.

Mnogi problemi se mogu efikasnije rešiti u odnosu na naivno rešenje primenom jedne ili više tehnika. Primeri ovih tehnika uključuju:

  • Optimizaciju petlji (smanjenje broja iteracija ili zamena ugnežđenih petlji jednim, kada je to moguće),
  • Korišćenje dinamičkog programiranja za eliminisanje ponovljenih proračuna,
  • Tehniku "podeli i osvoji" (divide and conquer) za brže rešenje kompleksnih problema,
  • Upotrebu efikasnijih struktura podataka, kao što su hash tabele ili prioritetni redovi,
  • Korišćenje sortiranja i binarne pretrage umesto linearne pretrage,
  • Primenu memorisanja (memoization) u rekurzivnim algoritmima.

Razumevanje ovih tehnika omogućava takmičarima da prepoznaju kada je moguće unaprediti naivno rešenje i da izaberu najefikasniji pristup za rešavanje datog problema.

Elementi efikasnosti algoritama 2 – Transformisanje nizova

U ovoj temi razmatramo dve tehnike koje poboljšavaju efikasnost algoritama:

  1. Primena matematičkih formula: Umesto da se prolazi kroz sve elemente niza (što traje linearno vreme), možemo izračunati zbir ili druge operacije korišćenjem poznatih formula u konstantnom vremenu. Na primer, zbir prvih n članova aritmetičkog niza se računa pomoću formule: S = n/2 * (2a + (n - 1)*d), gde je a prvi član, d je razlika, a n broj članova.
  2. Inkrementalno računanje: Umesto da se svaki put iznova računa vrednost od početka (što može biti kvadratno vreme), prethodni rezultat se koristi za brzo izračunavanje sledećeg. Primeri ovoga su izračunavanje svih prefiksnih suma ili faktorijela brojeva od 1 do n.

Slede konkretni primeri sa objašnjenjima i komentarima u kodu:

Primer 1: Zbir prvih n članova aritmetičkog niza

Program učitava početni član a, razliku d i broj članova n i zatim izračunava zbir koristeći formulu: S = n/2 * (2a + (n - 1)*d). Ovaj proračun se vrši u konstantnom vremenu.

#include<iostream>using namespacestd;
intmain() { double a, d; // a: prvi član, d: razlikaint n; // Broj članova nizacout << "Unesite a, d i n: "; cin >> a >> d >> n;
double S = n / 2.0 * ( 2 * a + (n - 1) * d ); // Izračunavanje zbira pomoću formulecout << "Zbir prvih n članova: " << S << endl;
return0; }

Primer 2: Izračunavanje prefiksnih suma

U ovom primeru se učitava niz celih brojeva i zatim se za svaki element niza izračunava prefiksna suma – zbir svih elemenata od početka niza do tekućeg elementa. Ovaj pristup je efikasan jer se svaka suma izračunava inkrementalno, koristeći prethodni rezultat, što traje linearno vreme.

#include<iostream>#include<vector>using namespacestd;
intmain() { int n; cout << "Unesite broj elemenata niza: "; cin >> n;
vector<int> arr(n); cout << "Unesite elemente niza: " << endl; for (int i = 0; i < n; i++) { cin >> arr[i]; }
vector<int> prefixSums(n); prefixSums[0] = arr[0]; // Prva prefiksna suma je prvi elementfor (int i = 1; i < n; i++) { prefixSums[i] = prefixSums[i - 1] + arr[i]; // Inkrementalno računanje prefiksnih suma } cout << "Prefiksne sume: " << endl; for (int i = 0; i < n; i++) { cout << prefixSums[i] << " "; } cout << endl;
return0; }

Za dodatne informacije o tehnikama transformisanja i generisanja nizova, kao i efikasnom pristupu u algoritamskoj analizi, pogledajte resurse na stranici: Nizovi u jeziku C++.

Tehnika dva pokazivača (Two Pointer Technique)


Tehnika dva pokazivača je optimizovana metoda za rešavanje problema koji uključuju iteraciju kroz nizove ili liste. Koristi se kada je potrebno efikasno pronaći određene parove elemenata, segmentirati podatke ili obraditi niz u jednom prolazu.


Princip rada


Dva pokazivača (indeksa) postavljaju se na različite pozicije unutar niza, obično na:

  • Početak i kraj (za probleme sa sumama, pretragom parova).
  • Dva uzastopna elementa (za probleme sa spajanjem, pomeranjem podataka).
  • Različite delove niza (za specifične zadatke poput sortiranja ili particionisanja).

Primer: Pronalaženje dva broja u sortiranom nizu koji daju određeni zbir


Imamo sortiran niz i želimo da pronađemo dva broja čiji zbir daje određenu vrednost target. Umesto da koristimo dva ugnježdena for petlje (O(n2)), koristimo dva pokazivača za rešenje u O(n) vremenskoj složenosti.
Pokazivači u početku pokazuju na početak i na kraj niza. Proverava se da li zbir elemenata na koji pokazuju jednak traženom (target). Ako jeste petlja se prekida, a ako nije razmatramo dva slučaja:
1) zbir je manji od traženog i treba ga povećati i u tom slučaju pomera se pokazivač levog elementa na desno, s obzirom da je niz sortiran rastuće pa će se na taj način dobiti povećanje zbira.
2) zbir je veći od traženog pa ga treba snanjivati. To će se postići pomeranjem desnog pokazivača ulevo, ka manjim vrednostima.


#include<iostream>
using namespacestd;

bool twoSum(int arr[], int n, int target) {
int left = 0, right = n - 1;

while (left < right) {
int sum = arr[left] + arr[right];

if (sum == target) {
cout << "Brojevi su: " << arr[left] << " i " << arr[right] << endl;
returntrue;
    } else if (sum < target) {
      left++; // Povećavamo levog pokazivača (treba veći zbir)
    } else {
      right--; // Smanjujemo desnog pokazivača (treba manji zbir)
    }
  }
returnfalse;
}

int main() {
int arr[] = {1, 2, 4, 7, 11, 15};
int target = 9;
int n = sizeof(arr) / sizeof(arr[0]);

if (!twoSum(arr, n, target)) {
cout << "Nema takvih brojeva!" << endl;
  }
return0;
}

Objašnjenje koda:


- Postavljamo dva pokazivača: left na početak niza i right na kraj niza.
- Računamo zbir elemenata: arr[left] + arr[right].
- Ako je zbir manji od target-a, povećavamo left (potreban nam je veći broj).
- Ako je zbir veći, smanjujemo right (potreban nam je manji broj).
- Ako je zbir jednak target-u, ispisujemo brojeve i završavamo pretragu.
- Petlja se ponavlja dok se pokazivači ne sretnu.


Gde se koristi ova tehnika?


  • Pretraga u sortiranom nizu (kao u primeru iznad).
  • Određivanje najveće dužine podniza sa određenim svojstvima.
  • Preuređivanje niza bez dodatne memorije (npr. premještanje svih nula na kraj).
  • Određivanje trougaonih tripleta u sortiranom nizu.

Napredne strukture podataka u rešavanju problema


Napredne strukture podataka, kao što su torke (tuples), rečnici (dictionaries), mape (maps) i skupovi (sets), omogućavaju efikasnije rešavanje problema u programiranju. One pomažu u organizaciji podataka na način koji omogućava bržu pretragu, sortiranje i pristup, čime se optimizuju algoritmi i smanjuje ukupno vreme izvršavanja.

Na primer, korišćenje rečnika ili mapa omogućava brzo pronalaženje parova elemenata sa određenim svojstvima (kao što je pronalaženje svih parova čiji zbir odgovara datoj vrednosti) jer se pretraga vrši u vremenu O(log n) ili čak O(1) u slučaju hash mapa. S druge strane, skupovi se koriste za brzo testiranje pripadnosti i eliminisanje duplikata, dok torke omogućavaju čuvanje heterogenih podataka u fiksnom redosledu.

Kombinovanjem ovih struktura sa efikasnim algoritmima, kao što su sortiranje i binarna pretraga, možete postići znatno bolje performanse u rešavanju kompleksnih problema. Ovo je naročito važno u takmičarskim zadacima gde je efikasnost algoritma ključna.

Za dodatne informacije i primere upotrebe ovih naprednih struktura podataka, posetite stranicu: Rečnici, mape i skupovi u C++.

Upotreba torki/struktura, rečnika/mapa i skupova u C++ za efikasne algoritme


Napredne strukture podataka kao što su torke (tuples), rečnici (dictionaries), mape (maps) i skupovi (sets) omogućavaju brzo i efikasno rešavanje problema. U nastavku je prikazan primer za nalaženje parova elemenata niza koji daju zadati zbir S. Ovaj primer koristi unordered_map za evidentiranje brojeva iz niza, što omogućava pretragu u prosečnom vremenu O(1).

#include<iostream>#include<unordered_map>#include<vector>using namespacestd;voidpronadjiParoveHashMap(vector<int>& niz, int S) {
    unordered_map<int, int> mapa; // Kreiramo mapu za evidentiranje brojevafor (int broj : niz) { // Iteriramo kroz sve brojeve u nizuint potreban = S - broj; // Izračunavamo komplementarni brojif (mapa.find(broj) == mapa.end()) {
            // Ako broj nije ranije viđen, ne radimo ništa
        }
        if (mapa.find(potreban) != mapa.end()) { // Ako je komplementarni broj pronađencout << "Par: (" << potreban << ", " << broj << ")\n";
        }
        mapa[broj]++; // Evidentiramo trenutni broj u mapi
    }
}

intmain() {
    vector<int> niz = {1, 4, 7, 3, 2, 5, 8};
    int S = 9; // Zadati zbirpronadjiParoveHashMap(niz, S); // Poziv funkcije za pronalaženje parovareturn0; // Završetak programa
}
  

Objašnjenje rešenja

U ovom rešenju, za svaki element x iz niza se računa komplementarni broj (S - x). Ako je komplementarni broj već evidentiran u mapi, to znači da postoji par čiji zbir iznosi S i taj par se ispisuje. Koristeći unordered_map omogućeno je brzo pretraživanje, što čini ovaj algoritam efikasnim sa vremenskom složenošću O(n).

Za dodatne informacije o mapama i rečnicima, pogledajte resurse na stranici: Rečnici i mape u C++.

Upotreba prefiksnih i sufiksnih suma i kombinacija prethodnih tehnika


U ovoj temi, pokazaćemo kako se mogu koristiti tehnike koje smanjuju vremensku složenost algoritama:

  • Primena matematičkih formula: Umesto iterativnog računanja, neke operacije (npr. zbir aritmetičkog niza) mogu se izračunati u konstantnom vremenu koristeći poznate formule.
  • Inkrementalno računanje: Izračunavanje prefiksnih ili sufiksnih suma omogućava brzo dobijanje zbira bilo kog segmenta niza, što je linearno u odnosu na veličinu niza, a znatno brže od kvadratnog rešenja.
  • Kombinacija prethodnih tehnika: Kombinovanjem sortiranja, upotrebe mapa/skupova i inkrementalnog računanja moguće je rešavati složenije zadatke, kao što su broj pojavljivanja određenog elementa u nesortiranom nizu ili pronalaženje trojki čiji zbir odgovara zadatoj vrednosti.

Slede primeri sa objašnjenjima i komentarima u kodu:

Primer 1: Izračunavanje prefiksnih suma

Ovim primerom se demonstrira kako se, uz jedan prolaz kroz niz, mogu izračunati prefiksne sume, tj. zbir svih elemenata od početka do svakog indeksa. Ovo omogućava da se kasnije brzo odgovori na upit o sumi bilo kog segmenta niza, smanjujući vremensku složenost sa O(n²) na O(n).

#include<iostream>#include<vector>using namespacestd;intmain() {
    int n;
    cout << "Unesite broj elemenata niza: ";
    cin >> n;
    vector<int> arr(n);
    cout << "Unesite elemente niza: " << endl;
    for (int i = 0; i < n; i++) {
        cin >> arr[i]; // Učitavanje elemenata niza
    }
    
vector<int> prefix(n); prefix[0] = arr[0]; // Prvi element je početna sumafor (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] + arr[i]; // Inkrementalno računanje prefiksnih suma } cout << "Prefiksne sume: " << endl; for (int i = 0; i < n; i++) { cout << prefix[i] << " "; } cout << endl; return0; }

Primer 2: Trojke elemenata niza sa zadatim zbirom

U ovom primeru, iz nesortiranog niza se pronalaze sve trojke elemenata čiji zbir odgovara zadatoj vrednosti. Iako naivno rešenje koristi tri ugneždene petlje sa vremenskom složenošću O(n³), ova tehnika pokazuje kako se kombinacijom prethodnih metoda mogu rešavati kompleksniji problemi.

#include<iostream>#include<vector>using namespacestd;
intmain() { int n, target; cout << "Unesite broj elemenata niza: "; cin >> n; vector<int> arr(n); cout << "Unesite elemente niza: " << endl; for (int i = 0; i < n; i++) { cin >> arr[i]; }
cout << "Unesite ciljnu vrednost: "; cin >> target;
// Pronalaženje svih trojki čiji zbir je jednak targetfor (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { for (int k = j + 1; k < n; k++) { if (arr[i] + arr[j] + arr[k] == target) { cout << "Trojka: (" << arr[i] << ", " << arr[j] << ", " << arr[k] << ")" << endl; } } } } return0; }

Ovi primeri pokazuju kako se različite tehnike mogu kombinovati kako bi se optimizovalo rešavanje problema. Za dodatne informacije o transformisanju i generisanju nizova, kao i efikasnim algoritmima, posetite resurs: Nizovi u jeziku C++.

Problem prekoračenja i memorijska složenost algoritma


U ovom primeru prikazujemo dva važna aspekta:

  • Prekoračenje (overflow): Kada se sabiraju veliki brojevi u nizu koristeći tip int, može doći do prekoračenja ukoliko zbir prevaziđe maksimalnu vrednost koju taj tip može da drži. Ovo može dovesti do netačnih rezultata.
  • Memorijska složenost: Algoritam sabiranja niza zahteva memoriju proporcionalnu broju elemenata u nizu, tj. O(n). Odabir odgovarajućeg tipa podataka pomaže u optimizaciji memorijske upotrebe.

Sledeći primer demonstrira sabiranje elemenata niza korišćenjem tipa int i ukazuje na mogućnost prekoračenja, kao i na linearnu memorijsku složenost algoritma.

#include<iostream>using namespacestd;intmain() {
    int n;
    cout << "Unesite broj elemenata niza: "; // Učitavanje broja elemenatacin >> n;
    
// Alokacija niza – memorijska složenost O(n)int arr[1000]; // Pretpostavimo da n ne prelazi 1000
for (int i = 0; i < n; i++) { cin >> arr[i]; // Učitavanje elemenata niza }
int sum = 0; // Sabiranje elemenata nizafor (int i = 0; i < n; i++) { sum += arr[i]; // Moguće prekoračenje ako je zbir prevelik }
cout << "Zbir elemenata niza: " << sum << endl;
// Ovaj algoritam ima vremensku složenost O(n) i memorijsku složenost O(n)
return0; }

Uvod u rekurziju


Rekurzija je tehnika u kojoj funkcija poziva samu sebe kako bi rešila manji deo problema, a zatim kombinuje rezultate da bi se dobilo rešenje celokupnog problema. Ova metoda je posebno korisna kada se problem može podeliti na slične podprobleme. Za detaljnije objašnjenje rekurzivnih algoritama, posetite stranicu: Rekurzivni algoritmi.

Primer: Izračunavanje faktorijela (n!) rekurzivno

U sledećem primeru, rekurzivna funkcija factorial računa faktorijel broja n. Ako je n manji ili jednak 1, funkcija vraća 1 (osnovni slučaj). U suprotnom, funkcija vraća proizvod broja n i faktorijela od n-1 (rekurzivni slučaj).

#include<iostream>using namespacestd;
intfactorial(int n) { if (n <= 1) { // Osnovni slučaj: 0! i 1! su jednaki 1return1; } else { // Rekurzivni slučaj: n! = n * (n-1)!return n * factorial(n - 1); } }
intmain() { int n; cout << "Unesite broj: "; cin >> n;
cout << "Faktorijel od " << n << " je " << factorial(n) << endl;
return0; }

Primena rekurzije u kombinatoričkim problemima


Rekurzija je tehnika u kojoj funkcija poziva samu sebe kako bi rešila manji deo problema, a zatim kombinuje rezultate da bi se dobilo rešenje celokupnog problema. Ova metoda je posebno korisna u kombinatoričkim zadacima, gde se generišu sve moguće n-torke, permutacije, kombinacije i varijacije. Iako rekurzivna rešenja često imaju eksponencijalnu vremensku složenost, ona su elegantna i lako se implementiraju kada su ulazni podaci mali.

U nastavku su prikazani primeri za dve podteme:

  • Generisanje n-torki pomoću rekurzije (npr. binarne n-torke bez susednih istih elemenata),
  • Generisanje permutacija niza pomoću rekurzije.

Za dodatne informacije o rekurziji i kombinatorici, posetite resurse na relevantnim stranicama.

Primer 1: Generisanje binarnih n-torki bez susednih istih elemenata

Ovaj primer koristi rekurziju za generisanje svih binarnih n-torki (stringova dužine n sastavljenih od '0' i '1') sa uslovom da nema dve uzastopne '1'. Funkcija poziva samu sebe i dodaje '0' ili '1' u trenutni string, a uslov osigurava da, ukoliko je poslednji karakter '1', sledeći mora biti '0'.

#include<iostream>#include<string>using namespacestd;voidgenerateBinaryTuples(string curr, int n) {
    if (curr.size() == n) {
        cout << curr << endl; // Ako je n-torka kompletna, ispisujemo jereturn;
    }
    if (curr.empty() || curr.back() =='0') {
        generateBinaryTuples(curr + '0', n); // Može se dodati '0'generateBinaryTuples(curr + '1', n); // Može se dodati i '1'
    } else {
        generateBinaryTuples(curr + '0', n); // Ako je poslednji karakter '1', sledeći mora biti '0'
    }
}

intmain() {
    int n;
    cout << "Unesite dužinu n-torke: ";
    cin >> n;
    generateBinaryTuples("", n);
    return0;
}
  

Primer 2: Generisanje permutacija niza

U ovom primeru rekurzivno generišemo sve permutacije niza celih brojeva. Funkcija permute koristi tehniku backtrackinga kako bi razmenjivala elemente niza i ispisivala svaki jedinstveni raspored.

#include<iostream>#include<vector>using namespacestd;voidpermute(vector<int>& arr, int start) {
    if (start == arr.size() - 1) {
        for (int x : arr)
            cout << x << " ";
        cout << endl;
        return;
    }
    for (int i = start; i < arr.size(); i++) {
        swap(arr[start], arr[i]); // Razmena elemenatapermute(arr, start + 1);
        swap(arr[start], arr[i]); // Vraćanje elemenata na mesto (backtracking)
    }
}

intmain() {
    vector<int> arr = {1, 2, 3};
    permute(arr, 0);
    return0;
}
  

Za dodatne informacije o rekurziji i generisanju kombinatoričkih objekata, pogledajte resurse na relevantnim stranicama.