Univerzitet u Sarajevu
Elektrotehnički fakultet u Sarajevu
KANTONALNO TAKMIČENJE IZ INFORMATIKE
ZA SREDNJE ŠKOLE
ZADACI ZA PRIPREMU SA RJEŠENJIMA
Priredili:
mr Vedran Ljubović, dipl. ing. el.
Alvin Abdagić, MoE – dipl. ing. el.
Marko Lalić
Amer Mešanović
Doc. dr Samim Konjicija, dipl. ing. el.
Enio Kaljić MoE - dipl. ing. el.
Sarajevo, April 2014. godine
1
Ovaj dokument će biti aktivno dorađivan. Posljednja verzija dokumenta bit će dostupna na stranici
http://takmicenje.etf.unsa.ba. Trenutna verzija dokumenta je: 6.0.
Sadržaj:
PRAVILA KANTONALNOG TAKMIČENJA IZ INFORMATIKE................................................................5
SILABUS KANTONALNOG TAKMIČENJA IZ INFORMATIKE.................................................................7
1.Strategija rješavanja programskih zadataka.............................................................................................................11
Zadatak 1.1. Prethodni i sljedeći datum................................................................................................................11
Zadatak 1.2. Razlika datuma...................................................................................................................................19
Zadatak 1.3. Dan u sedmici.....................................................................................................................................22
Ulazni podaci.............................................................................................................................................................22
Izlazni podaci.............................................................................................................................................................22
Ograničenja................................................................................................................................................................22
Primjer 1.....................................................................................................................................................................22
Primjer 2.....................................................................................................................................................................23
Zadatak 1.4. Rimski brojevi....................................................................................................................................24
Zadatak 1.5. Numerička matematika.....................................................................................................................26
Zadatak 1.6. Bazen (jednostavnija varijanta)........................................................................................................29
2.Operacije s nizovima..................................................................................................................................................31
Zadatak 2.1. Najveći među najmanjima................................................................................................................31
Zadatak 2.2. Histogram...........................................................................................................................................33
Zadatak 2.3. Operacije sa skupovima....................................................................................................................35
3.Prosti brojevi................................................................................................................................................................36
Zadatak 3.1. Razdvojiti proste od složenih..........................................................................................................36
Zadatak 3.2. Kazna...................................................................................................................................................39
Zadatak 3.3. Igra.......................................................................................................................................................41
Zadatak 3.4. Prosti blizanci.....................................................................................................................................44
4.Iscrpna pretraga (exhaustive search, brute-force metod).....................................................................................46
Zadatak 4.1. Konjićev skok.....................................................................................................................................46
5.Binarna pretraga..........................................................................................................................................................49
Zadatak 5.1. Pretraga brojeva.................................................................................................................................49
6.Rekurzija.......................................................................................................................................................................52
Zadatak 6.1. Hanojske kule.....................................................................................................................................54
Zadatak 6.2. Flood fill..............................................................................................................................................56
7.Sortiranje......................................................................................................................................................................60
Zadatak 7.1. Elementarna nepogoda.....................................................................................................................62
8.Efikasno stepenovanje...............................................................................................................................................65
Zadatak 8.1. Efikasno stepenovanje......................................................................................................................65
9.Pohlepni (greedy) algoritmi.......................................................................................................................................67
Zadatak 9.1. Lopov...................................................................................................................................................67
10.Osnovna geometrijska tijela (pravougaonici, kružnice)......................................................................................70
Zadatak 10.1. Majansko prokletstvo......................................................................................................................70
Zadatak 10.2. Obuhvatanje tačaka.........................................................................................................................75
11.Rad sa stringovima....................................................................................................................................................78
Zadatak 11.1. Cenzura.............................................................................................................................................78
Zadatak 11.2. Pravilan jezik....................................................................................................................................78
2
Zadatak 11.3. Prijemni ispit....................................................................................................................................81
Zadatak 11.4. Spellchecker......................................................................................................................................82
Zadatak 11.5. T9.......................................................................................................................................................82
12.Veliki broj...................................................................................................................................................................85
Zadatak 12.1. Zbir i proizvod dva velika broja....................................................................................................85
Zadatak 12.2. Najveći broj......................................................................................................................................88
Ulazni podaci.............................................................................................................................................................89
Izlazni podaci.............................................................................................................................................................89
Ograničenja................................................................................................................................................................89
Primjer........................................................................................................................................................................89
13.Grafovi i stabla..........................................................................................................................................................92
Zadatak 13.1. Presjedanje........................................................................................................................................92
Zadatak 13.2. BIHAMK..........................................................................................................................................95
Zadatak 13.3. Nomenklatura..................................................................................................................................99
Zadatak 13.4. Posude.............................................................................................................................................104
Ulazni podaci...........................................................................................................................................................104
Izlazni podaci..........................................................................................................................................................104
Ograničenja..............................................................................................................................................................105
Primjer......................................................................................................................................................................105
Zadatak 13.5. Farma...............................................................................................................................................107
Literatura.......................................................................................................................................................................109
Zadatak
Naziv
Težina
1.1.
Prethodni i sljedeći datum
O
1.2.
Razlika datuma
S
1.3.
Dan u sedmici
O
1.4.
Rimski brojevi
O
1.5.
Numerička matematika
S
1.6.
Bazen (jednostavnija varijanta)
O
2.1.
Najveći među najmanjima
O
2.2.
Histogram
S
2.3.
Operacije sa skupovima
O
3.1.
Razdvojiti proste od složenih
S
3.2.
Kazna
S
3.3.
Igra
S
3.4.
Prosti blizanci
S
4.1.
Konjićev skok
N
5.1.
Pretraga brojeva
N
3
6.1.
Hanojske kule
N
6.2.
Flood fill
N
7.1.
Elementarna nepogoda
N
8.1.
Efikasno stepenovanje
S
9.1.
Lopov
N
10.1.
Majansko prokletstvo
N
10.2.
Obuhvatanje tačaka
S
11.1.
Cenzura
S
11.2.
Pravilan jezik
S-N
11.3.
Prijemni ispit
S
11.4.
Spellchecker
S-N
12.1.
Zbir i proizvod dva velika broja
N
12.2.
Najveći broj
N
13.1.
Presjedanje
N
13.2.
BIHAMK
N
13.3.
Nomenklatura
N
13.4.
Posude
N
13.5.
Farma
I
Legenda:
O – osnovno znanje (očekuje se da zadatak može uraditi učesnik sa znanjem stečenim u redovnoj
srednjoškolskoj nastavi, bez posebne pripreme)
S – srednje (preporučujemo takmičarima da ponove ove oblasti)
N – napredne teme (teme koje su moguće na kantonalnom takmičenju (max. jedan zadatak), potrebna je
posebna priprema)
I – teme koje neće biti obuhvaćene kantonalnim takmičenjem, ali su moguće na državnom takmičenju iz
informatike i međunarodnoj olimpijadi (IOI)
4
PRAVILA KANTONALNOG TAKMIČENJA IZ INFORMATIKE
Svi zadaci na Kantonalnom takmičenjimu iz informatike isključivo su programerski zadaci i težište je
na dizajniranju ispravnog i (vremenski i memorijski) efikasnog algoritma. Zadaci se mogu rješavati u bilo
kojem od ponuđenih programskih jezika. Ulazne i izlazne operacije ograničavaju se na osnovne operacije
(čitanje iz tekstualne datoteke i upis u tekstualnu datoteku), tako se pažnja takmičara može usmjeriti na sam
algoritam. Tačan pregled oblasti i tema koje su obuhvaćene takmičarskim zadacima (silabus) dostupan je u
nastavku dokumenta.
Oprema, pribor i materijal za takmičenje, tok takmičenja
Za takmičenje se koriste standardni PC računari sa ograničenim mrežnim pristupom. Takmičari će na
radnim mjestima dobiti printane verzije zadataka za takmičenje, dovoljno praznih papira, pribor za pisanje i
pristupne podatke za takmičarski web interfejs. Takmičarima nije dozvoljeno unositi bilo kakav dodatni
pribor, literaturu ili opremu. Takmičarima je dopuštena samo usmena komunikacija sa osobom koja je
dežurna u kabinetu ili sa administratorom i to isključivo na temu lične ili tehničke prirode a nikako na temu
zadataka. Bilo kakvo odstupanje od ovih pravila rezultirat će diskvalifikacijom.
Programski jezici i okruženje
Takmičarima je dopušteno korištenje programskih jezika Pascal, C i C++ za izradu rješenja. (Dijalekt
C++11 nije podržan sistemom na takmičenju 2014. godine.) Na samom takmičenju, računari će biti sa
instaliranim Linux (Ubuntu) operativnim sistemom, te FreePascal i Code::Blocks razvojnim okruženjima.
Svi detalji o okruženju mogu se preuzeti sa web stranice http://takmicenje.etf.unsa.ba
Broj zadataka i trajanje takmičenja
Takmičari imaju 120 minuta za izradu rješenja. Takmičari će usmeno biti napomenuti onda kada do
kraja takmičenja ostane 15 minuta i 5 minuta. Rješavaju se četiri zadatka.
Ograničenja i zahtjevi na program
Vaš program treba da ulazne podatke čita sa standardnog ulaza, te da svoja rješenja zapisuje na
standardni izlaz. Tačan format ulaza i izlaza bit će precizno definiran za svaki zadatak. Vaš program ne
smije koristiti bilo kakve datoteke. Vaš program treba da se izvršava unutar određenih vremenskih i
memorijskih ograničenja koja će biti navedena za svaki zadatak. Zabranjeno je pozivanje bilo kakvih drugih
sistemskih funkcija kao i fork-anje (otvaranje dodatnih thread-ova). Ispravnost zapisane izlaza za dati ulaz
bit će provjeravana samo ukoliko je program sa izvršavanjem završio “normalno”, odnosno ako je
operativnom sistemu vratio vrijednost 0.
Sistem bodovanja
Evaluacija takmičarskih rješenja vrši se automatiziranim sistemom odmah po završetku takmičenja. Za
svaki zadatak komisija unaprijed definiše skup testova koje će sistem provesti. Svaki test se evaluira
individualno i nosi određeni broj bodova. Test se sastoji od sadržaja ulazne datoteke i očekivanog sadržaja
izlazne datoteke. Test se smatra uspješnim i za njega se dodjeljuju bodovi onda kada takmičarski program
pod ranije navedenim ograničenjima za specifični sadržaj ulazne datoteke generiše očekivani sadržaj izlazne
datoteke. Neophodno je pridržavati se tačno definisanih formata obje datoteke. Svaki zadatak će uključivati
dovoljan broj testova različite kompleksnosti kako bi se na taj način osigurala adekvatna distribucija bodova
u skladu sa vremenskom i memorijskom efikasnošću takmičarskog rješenja (uz neophodnu ispravnost
rješenja).
Takmičarski web interfejs
5
Takmičari će za vrijeme takmičenja imati ograničen mrežni pristup samo prema serveru za takmičenje
kroz web preglednik u vidu takmičarskog web interfejsa. Ovaj interfejs prvenstveno omogućava
takmičarima da šalju svoju rješenja za vrijeme takmičenja. Jedino rješenja poslata na ovaj način će biti
evaluirana na ranije opisani način. Nakon slanja rješenja, sistem će rješenje odmah testirati na jednom ili
više tzv. trivijalnih testova i rezultate ponuditi takmičaru. Sadržaj ulaznih datoteka trivijalnih testova kao i
očekivani sadržaj izlaznih datoteka ovih testova bit će dostupan takmičarima u sklopu postavke zadatka.
Trivijalni testovi su po složenosti jako jednostavni i služe kako bi se testiralo da li takmičar ispravno koristi
ulazne i izlazne datoteka, pridržava se navedenog formata ulaznih i izlaznih podataka i slično. Trivijalni
testni slučajevi ne donose nikakve poene, ali se rješenja koja ne prolaze trivijalne testove neće evaluirati po
završetku takmičenja i bodovat će se sa 0 bodova. Ne postoji ograničenje na broj koliko puta takmičar
smije poslati rješenje nekog zadataka. Posljenje rješenje koje je prošlo trivijalne testove za određeni zadatak
će biti testirano po završetku takmičenja. Takmičarski web interfejs će se koristiti i za eventualna pitanja
koja takmičari mogu imati za komisiju. Takmičari će prije samog takmičenja imati priliku da se upoznaju sa
takmičarskim web interfejsom. Termini kada će ovo biti moguće će biti objavljeni na stranici
http://takmicenje.etf.unsa.ba
Pitanja
Takmičari pitanja postavljaju i komisija na njih daje odgovor kroz takmičarski web interfejs. Komisija
na pitanja takmičara može dati odgovor DA, NE ili BEZ KOMENTARA. Pitanja trebaju biti postavljena
tako da je na njih moguć DA ili NE odgovor. Ukoliko pitanje nije tako postavljeno, komisija daje odgovor
BEZ KOMENTARA. Osim toga, ukoliko takmičar postavi pitanje na koje komisija ne može dati odgovor
zato što bi to u nepovoljan položaj dovela druge takmičare, daje se odgovor BEZ KOMENTARA. U
slučajevima kada se daje odgovor BEZ KOMENTARA, komisija može dodatno pojasniti zašto takmičar
nije dobio odgovor na svoje pitanje.
Rezultati i reklamacije
Po završetku automatske evaluacije, objavljuju se preliminarni rezultati. Takmičari mogu korištenjem
pristupnih podataka koje se koristili i za vrijeme takmičenja, pristupiti takmičarskom web interfejsu.
Interfejs će po zavšetku takmičenja omogućiti detaljan pregled svih testova i rezultata testiranja. Svi
eventualni prigovori podnose se u pismenoj formi komisiji koja ih razmatra.
6
SILABUS KANTONALNOG TAKMIČENJA IZ INFORMATIKE
(pregled oblasti i tema koje su obuhvaćene takmičarskim zadacima)
Legenda zahtjevnosti pojedinih oblasti:
O – osnovno znanje (očekuje se da zadatak može uraditi učesnik sa znanjem stečenim u redovnoj
srednjoškolskoj nastavi, bez posebne pripreme)
S – srednje (preporučujemo takmičarima da ponove ove oblasti)
N – napredne teme (teme koje su moguće na kantonalnom takmičenju (max. jedan zadatak), potrebna je
posebna priprema)
I – teme koje neće biti obuhvaćene kantonalnim takmičenjem, ali su moguće na državnom takmičenju iz
informatike i međunarodnoj olimpijadi (IOI)
Znanja iz domena osnovne računarske pismenosti koja su preduslov za učešće na takmičenju su:
-
Osnovna struktura i način rada računara (CPU, memorija, ulaz/izlaz)
-
Upotreba standardnog grafičkog okruženja
-
Upotreba uobičajenih pomoćnih aplikacija u operativnom sistemu
-
Upotreba integrisanog razvojnog okruženja (IDE)
-
Rad sa datotečnim sistemom (kreiranje direktorija/foldera, kopiranje, premještanje i brisanje
datoteka)
-
Upotreba web preglednika i rad sa internetom (posebno: slanje datoteke putem web
preglednika)
1. MATEMATIKA
Težina TEMA
O
Cijeli brojevi (engl. integers) i brojevni sistemi
O
Matematičke operacije i poređenja
O
Osobine cijelih brojeva (pozitivni/negativni, parni/neparni, djeljivost, prosti brojevi /engl.
primes/)
O
Razlomci, postotni račun
O
Tačke u Dekartovom koordinatnom sistemu (dvodimenzionalnom), vektori
O
Euklidova udaljenost, Pitagorina teorema
I
Duži, osobine presjeka
S
Uglovi
O
Osnovni geometrijski oblici: trougao, pravougaonik, kvadrat, krug
7
I
Višeugaonik (poligon)
O
Matematske funkcije, relacije, skupovi
I
Dirihleov princip
O
Osnove matematske logike (logičke operacije, istinosne tabele)
I
Napredne teme iz matematske logike (predikatska logika, modus ponens i modus tolens)
I
Tehnike dokazivanja (direktan dokaz, dokaz preko kontradikcije, matematska indukcija)
N
Nizovi, redovi, aritmetičke i geometrijske progresije, Fibonaćijevi brojevi
S
Permutacije i kombinacije
S
Faktorijel, binomni koeficijenti, Paskalova jednačina, binomni teorem
N
Stabla
S
Neusmjereni i usmjereni grafovi
S
Strategije prolaska kroz grafove
I
Spanning trees, decorated graphs, multigraphs
2. OSNOVE PROGRAMIRANJA I STRUKTURE PODATAKA
Težina TEMA
O
Osnove sintakse i semantike programskog jezika Pascal ili C
O
Promjenljive (varijable), tipovi podataka (boolean, integer, character), izrazi, naredba dodjele
O
Jednostavan ulaz/izlaz
O
Uslovi i grananje
O
Petlje
O
Funkcije, prosljeđivanje parametara
O
Strukturna dekompozicija problema (razbijanje problema na podprobleme)
S
Strategije rješavanja programskih zadataka
S
Pojam algoritma, uloga algoritma u procesu rješavanja zadatka
S
Strategije uklanjanja grešaka (debugging)
N
Osobine algoritama: korektnost, efikasnost
O
Nizovi (arrays)
O
Slogovi (records)
O
Stringovi (strings)
S
Statičke (static) i globalne (global) varijable
N
Linkovane liste (linked list) - implementacija preko niza
N
Grafovi (graph) i stabla (tree) - implementacija preko niza
8
I
Stekovi (stack) i redovi (queue) - implementacija preko niza
I
Gomile (heap), Fenwick-ovo stablo
I
Strategije za izbor optimalne strukture podataka
I
Apstraktni tipovi podataka (abstract data types - ADT), prioritetni red (priority queue),
dinamički skup (dynamic set), dinamička mapa (dynamic map)
N
Rekurzija (recursion), jednostavne rekurzivne procedure
I
Rekurzivne matematske funkcije
I
Strategije "zavadi-pa-vladaj" (divide-and-conquer)
3. ALGORITMI
Težina TEMA
I
Formalna specifikacija algoritama, korektnost, invarijantnost
I
Asimptotska analiza gornje granice kompleksnosti, big-O notacija
I
Standardni slučajevi kompleksnosti
I
Vremenski i prostorni kompromisi u algoritmima
O
Jednostavne strategije dizajna petlje
S
Algoritmi brutalne sile (brute-force), odnosno iscrpne pretrage (exhaustive search)
N
Pohlepni (greedy) algoritmi
I
Algoritmi "zavadi-pa-vladaj" (divide-and-conquer)
I
Backtracking algoritmi (rekurzivni i nerekurzivni)
I
Branch-and-bound algoritmi
S
Prepoznavanje uzoraka i algoritmi za rad sa stringovima (pri čemu se ne zahtijeva razumijevanje
korektnosti i efikasnosti ovih algoritama)
I
Dinamičko programiranje (dynamic programming)
I
Algoritmi diskretne aproksimacije (discrete approximation)
O
Algoritmi za konverziju brojevnih sistema
O
Euklidov algoritam
S
Provjera da li je broj prost
S
Erastotenovo sito
S
Faktorizacija
S
Efikasno stepenovanje
N
Operacije nad cijelim brojevima proizvoljnog broja cifara
O
Popunjavanje niza
9
S
Pomjeranje niza, izbacivanje elementa iz niza, rotiranje niza
S
Okretanje niza
S
Promjena veličine niza
O
Traženje najvećeg/najmanjeg člana u nizu
S
Histogram niza (prebrojavanje članova niza)
O
Sumiranje niza (uključujući parcijalne sume)
O
Sekvencijalna obrada članova niza (uključujući sekvencijalno pretraživanje niza)
S
Binarna pretraga (binary search)
S
Jednostavni algoritmi sortiranja: selection sort, insertion sort, bubble sort
I
Quick sort, Heap sort, Merge sort
N
Algoritmi za kretanje kroz grafove (implementirane preko niza), uključući kretanje po dubini
(depth-first search - DFS) i po širini (breadth-first search - BFS)
N
Algoritmi za kretanje kroz stablo (implementirano preko niza)
I
Algoritmi najkraćeg puta (Dijkstra, Bellman-Ford, Floyd-Warshall)
I
Algoritmi minimalnog stabla (Jarnik-Prim, Kruskal)
I
Topološko sortiranje
I
Algoritmi za određivanje Eulerovog puta/ciklusa
I
Osnove teorije igara, minimaks algoritmi
I
Geometrijski algoritmi (presjeci duži, lokacija tačke u poligonu, algoritmi za određivanje
konveksnog omotača /convex hull/ itd.) ali:
S
Lociranje tačke u osnovnim geometrijskim oblicima: kružnica, pravougaonik, kvadrat
10
1. Strategija rješavanja programskih zadataka
U nastavku smo dali nekoliko primjera zadataka u kojima se diskutuje generalan pristup rješavanju
programskih zadataka kod kojih ne postoji neki specifičan algoritam ili model rješavanja, nego je potrebno
koristiti zdrav razum, dekompoziciju problema i proceduralni pristup.
Zadatak 1.1. Prethodni i sljedeći datum
Težina: O
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2010. godine
Dat je niz datuma u obliku "dd mm gggg". Za svaki datum potrebno je ispisati prethodni i sljedeći datum.
Da bi neka godina bila prestupna mora biti djeljiva sa 4. Međutim, od onih godina koje su djeljive sa 4,
godine koje su djeljive sa 100 a nisu sa 400 nisu prestupne. Npr. 1900. nije bila prestupna dok je 2000.
godina bila prestupna. U prestupnoj godini mjesec februar ima 29 dana dok u ostalim godina ima 28.
Ulaz:
Najprije je dat jedan cijeli broj N (maksimalno 100) koji predstavlja broj datuma. Nakon toga, svaka linija
ulaza sastoji se od tri cijela broja razdvojena znakom razmak koji predstavljaju dan, mjesec i godinu.
Izlaz:
Za svaku liniju ulaza treba ispisati tekst oblika:
Datum: dd.mm.gggg Predhodni dd.mm.gggg. Naredni dd.mm.gggg. godine
ili, ukoliko je datum na ulaznoj liniji neispravan, treba ispisati:
Datum: dd.mm.gggg neispravan.
Primjer ulaza:
5
28 2 1900
1 3 1900
28 2 2000
1 3 2000
29 2 2001
Primjer izlaza:
Datum: 28.2.1900 Prethodni 27.2.1900. Naredni 1.3.1900. godine
Datum: 1.3.1900 Prethodni 28.2.1900. Naredni 2.3.1900. godine
Datum: 28.2.2000 Prethodni 27.2.2000. Naredni 29.2.2000. godine
Datum: 1.3.2000 Prethodni 29.2.2000. Naredni 2.3.2000. godine
Datum: 29.2.2001 neispravan.
Pojašnjenje:
Kao i svi programski zadaci, ovaj zadatak treba rješavati dio po dio krećući od stvari koje su vam poznate,
te koristiti osobinu programskih jezika da dijelove problema izdvojite u zasebne cjeline (funkcije) kako biste
ih odvojeno rješavali i testirali.
Najprije ćemo uraditi najlakši dio zadatka, a to je napraviti petlju za unos niza od n datuma i njihov ispis na
11
ekran u traženom formatu: "Datum: dd.mm.yyyy". Biće nam potrebne sljedeće varijable (promjenljive):
• cjelobrojne promjenljive za dan, mjesec i godinu
• cjelobrojna promjenljiva n za broj datuma
• pošto za blok koda koji se izvršava poznat broj puta koristimo for petlju, uvešćemo i cjelobrojnu
promjenljivu i kao kontrolnu promjenljivu for petlje.
Programski jezik C:
int n,i;
scanf("%d",&n);
int dd,mm,gg;
for(i=0;i<n;i++)
{
scanf("%d %d %d",&dd,&mm,&gg);
printf("Datum: %d.%d.%d ",dd,mm,gg);
}
Okvirni program u Pascal-u koji ćemo popunjavati blokovima koda:
program Datumi(output);
var
i,n,dd,mm,gg : integer;
begin
readln(n);
for i := 1 to n do
begin
readln(dd,mm,gg);
writeln('Datum: ',dd,'.',mm,'.',gg);
end;
end.
Najjednostavnije rješenje je povećati varijablu dd za jedan kako bismo dobili sljedeći datum, odnosno
umanjiti za jedan kako bismo dobili prethodni:
printf("Prethodni: %d.%d.%d ",dd-1,mm,gg);
printf("Sljedeci: %d.%d.%d ",dd+1,mm,gg);
Međutim ovo rješenje bi bilo netačno za određeni broj slučajeva. Da vidimo koji su to slučajevi:
• Ako je dd=1, oduzimanjem 1 dobićemo 0 što je neispravan datum, nego bismo u tom slučaju
trebali umanjiti mm za jedan, a dd treba imati broj dana u prethodnom mjesecu. Npr. ako je unijet
datum 1.2.2012 prethodni datum će biti 31.1.2012 pošto januar ima 31 dan.
Da bismo ovo mogli utvrditi, potrebno nam je da znamo koliko koji mjesec u godini ima dana. U pitanju je
12 cijelih brojeva dana u mjesecima, pa ćemo deklarisati niz od 12 elemenata koji imaju te vrijednosti.
Programski jezik C:
int broj_dana[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
Programski jezik Pascal:
broj_dana: array[1..12] of integer = (31,28,31,30,31,30,31,31,30,31,30,31);
12
U programskom jeziku C prvi indeks u nizu je 0. Da bismo mogli dobiti tačan broj dana naredbom tipa
broj_dana[mm], možemo deklarisati niz od 13 elemenata pri čemu je vrijednost prvog člana nula ili
možemo uvijek od mjeseca oduzimati broj 1. Opredijelili smo se za prvu varijantu.
Pascal nema ovakav problem.
Zatim određujemo prethodni broj dana u kodu na sljedeći način (C):
prethodni_dan = dd-1;
prethodni_mjesec = mm;
prethodna_godina = gg;
/*
provjeravamo da li je potrebno smanjivati mjesec
*/
if(prethodni_dan<1)
{
prethodni_mjesec--;
/*
podesavamo da je prethodni dan zadnji dan u prethodnom mjesecu
*/
prethodni_dan = broj_dana[prethodni_mjesec];
}
Pascal:
prethodni_dan := dd-1;
prethodni_mjesec := mm;
prethodna_godina := gg;
{ provjeravamo da li je potrebno smanjivati mjesec }
if prethodni_dan<1 then
begin
prethodni_mjesec := prethodni_mjesec-1;
{ podesavamo da je prethodni dan zadnji dan u prethodnom mjesecu }
prethodni_dan := broj_dana[prethodni_mjesec];
end;
Ali šta ako je mjesec bio januar, odnosno ako tražimo prethodni datum od datuma 1.1.2012 ? Ispravan
odgovor je 31.12.2011. Dakle, ako je prethodni_mjesec postao 0, moramo umanjiti godinu za jedan a
mjesec postaviti na 12. Dakle kod sada glasi ovako (C):
prethodni_dan = dd-1;
prethodni_mjesec = mm;
prethodna_godina = gg;
/*
provjeravamo da li je potrebno smanjivati mjesec
*/
if(prethodni_dan<1)
{
prethodni_mjesec--;
/*
provjeravamo da li je potrebmo smanjivati godinu
*/
if(prethodni_mjesec<1)
{
prethodni_mjesec = 12;
13
}
/*
prethodna_godina--;
podesavamo da je prethodni dan zadnji dan u prethodnom mjesecu
*/
prethodni_dan = broj_dana[prethodni_mjesec];
}
Pascal:
prethodni_dan := dd-1;
prethodni_mjesec := mm;
prethodna_godina := gg;
{ provjeravamo da li je potrebno smanjivati mjesec }
if prethodni_dan<1 then
begin
prethodni_mjesec := prethodni_mjesec-1;
{ provjeravamo da li je potrebmo smanjivati godinu }
if prethodni_mjesec<1 then
begin
prethodni_mjesec := 12;
prethodna_godina := prethodna_godina-1;
end;
{ podesavamo da je prethodni dan zadnji dan u prethodnom mjesecu }
prethodni_dan := broj_dana[prethodni_mjesec];
end;
Analognim kodom rješavamo pitanje sljedećeg datuma. Uvešćemo još tri varijable naredni_dan,
naredni_mjesec i naredna_godina te postaviti uslove za slučaj da je varijabla naredni_dan poprimila
vrijednost veću od broja dana u trenutnom mjesecu (u kojem slučaju se naredni_mjesec uvećava za jedan),
kao i slučaj kada je naredni_mjesec dobio vrijednost 13 (u kojem slučaju se naredna_godina uvećava za
jedan).
Ostaje još jedan problematičan slučaj a to je prestupna godina. U prestupnoj godini kao što je 2012.
februar ima 29 dana umjesto uobičajenih 28. Napravićemo pomoćnu funkciju koja provjerava da li je
godina gg prestupna, pa ako ona vrati logičku istinu (u C-u broj 1) odmah nakon unosa datuma ćemo
postaviti broj dana u februaru na 29, u suprotnom na 28. Funkcija glasi ovako u C-u:
/*
Odredjuje da li je godina prestupna
*/
int prestupna(int gg)
{
/*godina je prestupna ako je djeljiva sa 400*/
if(gg%400==0) return 1;
/*godina je prestupna ako je djeljiva sa 4, ali ne sa 100*/
if(gg%4==0 && gg%100!=0) return 1;
}
return 0;
14
Pascal:
function prestupna(gg : integer) : boolean;
begin
if (gg mod 400 = 0) then prestupna := true
else if (gg mod 4 = 0) and (gg mod 100 <> 0) then prestupna := true
else prestupna := false;
end;
Konačno, trebali bismo ispisati poruku za neispravne datume na ulazu. Napravićemo pomoćnu funkciju
koja provjerava da li je datum ispravan i vraća logičku istinu (u C-u 1) ako jeste, u suprotnom logičku
neistinu. Datum je neispravan ako je:
• dan ili mjesec manji od 1
• dan veći od broja dana u mjesecu
• mjesec veći od 12
Ako funkcija odredi da je datum neispravan, preskočićemo sve ostale naredbe u glavnoj petlji naredbom
continue.
Funkcija u C-u glasi:
int ispravan(int dd,int mm,int gg)
{
if(dd<1 || dd>broj_dana[mm])
return 0;
if(mm<1 || mm>12) return 0;
}
return 1;
A u Pascalu:
function ispravan(dd,mm,gg : integer) : boolean;
begin
if (dd<1) or (dd>broj_dana[mm]) then ispravan := false
else if (mm<1) or (mm>12) then ispravan := false
else ispravan := true;
end;
Time je program završen.
Programski kod (C):
#include <stdio.h>
#include <stdlib.h>
/*Niz deklariran kao globalna varijabla (dostupna je svakoj funkciji u programu)
koji sadrzi broj dana svakog mjeseca, ubacena je nula na pocetku jer indeksiranje
u C-u pocinje od nule */
int broj_dana[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
15
/*
Odredjuje da li je godina prestupna
*/
int prestupna(int gg)
{
/*godina je prestupna ako je djeljiva sa 400*/
if(gg%400==0) return 1;
/*godina je prestupna ako je djeljiva sa 4, ali ne sa 100*/
if(gg%4==0 && gg%100!=0) return 1;
}
return 0;
/*
Pretpostavljeno je da se radi o pozitivnoj godini, tj. poslije nove ere
funkcija ispituje da li je ispravan uneseni datum, tj. da li su dani i mjeseci u
datim granicama
*/
int ispravan(int dd,int mm,int gg)
{
if(dd<1 || dd>broj_dana[mm])
return 0;
if(mm<1 || mm>12) return 0;
return 1;
}
int main()
{
int n,i;
scanf("%d",&n);
int dd,mm,gg;
int naredni_mjesec,naredni_dan,naredna_godina;
int prethodni_mjesec,prethodni_dan,prethodna_godina;
for(i=0;i<n;i++)
{
scanf("%d %d %d",&dd,&mm,&gg);
printf("Datum: %d.%d.%d ",dd,mm,gg);
/*
podesavamo broj dana februara u zavisnosti da li je godina prestupna
*/
if(prestupna(gg)) broj_dana[2] = 29;
else broj_dana[2] = 28;
/*
provjeravamo ispravnost datuma
*/
if(!ispravan(dd,mm,gg))
{
printf("neispravan\n");
continue;
}
naredni_dan = dd+1;
naredni_mjesec = mm;
16
naredna_godina = gg;
/*
ispitujemo da li je naredni dan u sljedecem mjesecu, u tom slucaju
povecavamo broj mjeseca, a dan stavljamo da je 1
*/
if(naredni_dan>broj_dana[mm])
{
naredni_dan=1;
naredni_mjesec++;
}
/*
ukoliko je naredni_mjesec veci od 12 (tj. jedak 13), potrebno je
povecati godinu za 1
*/
if(naredni_mjesec>12)
{
naredni_mjesec = 1;
naredna_godina++;
}
prethodni_dan = dd-1;
prethodni_mjesec = mm;
prethodna_godina = gg;
/*
provjeravamo da li je potrebno smanjivati mjesec
*/
if(prethodni_dan<1)
{
prethodni_mjesec--;
/*
provjeravamo da li je potrebmo smanjivati godinu
*/
if(prethodni_mjesec<1)
{
prethodni_mjesec = 12;
prethodna_godina--;
}
/*
podesavamo da je prethodni dan zadnji dan u prethodnom mjesecu
*/
prethodni_dan = broj_dana[prethodni_mjesec];
}
printf("Predhodni %d.%d.%d. Naredni %d.%d.%d. godine\n", prethodni_dan,
prethodni_mjesec, prethodna_godina, naredni_dan, naredni_mjesec, naredna_godina);
}
}
return 0;
Programski kod (Pascal):
program Datumi(output);
17
var
i,n,dd,mm,gg : integer;
prethodni_dan, prethodni_mjesec, prethodna_godina, naredni_dan,
naredni_mjesec, naredna_godina : integer;
broj_dana: array[1..12] of integer = (31,28,31,30,31,30,31,31,30,31,30,31);
function prestupna(gg : integer) : boolean;
begin
if (gg mod 400 = 0) then prestupna := true
else if (gg mod 4 = 0) and (gg mod 100 <> 0) then prestupna := true
else prestupna := false;
end;
function ispravan(dd,mm,gg : integer) : boolean;
begin
if (dd<1) or (dd>broj_dana[mm]) then ispravan := false
else if (mm<1) or (mm>12) then ispravan := false
else ispravan := true;
end;
begin
readln(n);
for i := 1 to n do
begin
readln(dd,mm,gg);
write('Datum: ',dd,'.',mm,'.',gg);
if prestupna(gg) then broj_dana[2] := 29 else broj_dana[2] := 28;
if not ispravan(dd,mm,gg) then
begin
writeln(' neispravan');
continue;
end;
prethodni_dan := dd-1;
prethodni_mjesec := mm;
prethodna_godina := gg;
{ provjeravamo da li je potrebno smanjivati mjesec }
if prethodni_dan<1 then
begin
prethodni_mjesec := prethodni_mjesec-1;
{ provjeravamo da li je potrebno smanjivati godinu }
if prethodni_mjesec<1 then
begin
prethodni_mjesec := 12;
prethodna_godina := prethodna_godina-1;
end;
{ podesavamo da je prethodni dan posljednji dan u prethodnom
mjesecu }
prethodni_dan := broj_dana[prethodni_mjesec];
end;
naredni_dan := dd+1;
18
naredni_mjesec := mm;
naredna_godina := gg;
{ provjeravamo da li je potrebno povecati mjesec }
if naredni_dan>broj_dana[mm] then
begin
naredni_mjesec := naredni_mjesec+1;
{ provjeravamo da li je potrebno povecati godinu }
if naredni_mjesec>12 then
begin
naredni_mjesec := 1;
naredna_godina := naredna_godina+1;
end;
{ podesavamo da je naredni dan 1 }
naredni_dan := 1;
end;
write(' Prethodni: ', prethodni_dan, '.', prethodni_mjesec, '.',
prethodna_godina, '.');
writeln(' Naredni: ', naredni_dan, '.', naredni_mjesec, '.',
naredna_godina, '.');
end.
end;
Zadatak 1.2. Razlika datuma
Težina: S
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2008. godine
Napisati program koji učitava dva datuma i ispisuje broj dana koliko je proteklo između ta dva datuma.
Ulaz:
Dvije linije koje predstavljaju datume. Svaka linija sastoji se od tri cijela broja razdvojena razmakom koji
predstavljaju dan, mjesec i godinu.
Izlaz:
Jedan pozitivan cijeli broj koji predstavlja broj dana koji su protekli između dva datuma. Ukoliko je prvi
datum bio poslije drugog treba ispisati apsolutnu vrijednost razlike. Ukoliko je jedan od datuma ilegalan
treba ispisati "Ilegalan datum".
Princip rješavanja:
Osnovni princip je sljedeći: najprije oba datuma trebamo pretvoriti u dva cijela broja koji predstavljaju broj
dana od nekog hipotetskog datuma 1.1.1. godine do željenog datuma, a zatim jednostavno oduzmemo ta
dva broja. U slučaju da je drugi datum poslije prvog, dobićemo negativan broj pa trebamo uzeti apsolutnu
vrijednost tog broja.
Pri tome pravimo pretpostavku da se isključivo koristi Gregorijanski kalendar; odnosno ovakav program
19
nema nikakvog smisla za datume ranije od 1582. godine. Prema tome i broj dana od "1.1.1." ustvari nije
tačan, ali je za potrebe rješavanja zadatka zadovoljavajući.
Pretvaranje datuma dd.mm.gg. u željeni broj dana se obavlja na sljedeći način:
• najprije saberemo broj dana u godinama od 1. do (gg-1). (zato što je godina gg još uvijek u toku),
pri čemu za prestupne godine dodajemo 366 dana, a za ostale 365; ovdje će nam pomoći funkcija
za provjeru da li je godina prestupna koju smo razvili u prethodnom zadatku;
• zatim saberemo dane za sve mjesece koji su protekli, od januara do mjeseca (mm-1). iz istih razloga;
ovdje će nam trebati broj dana u mjesecu, što smo također riješili u prethodnom zadatku;
• konačno saberemo dane koji su protekli u tekućem mjesecu a to je broj dd.
Radi ljepšeg rješenja u ovom zadatku uvodimo i koncept struktura u programskom jeziku C (ključna riječ
struct). Zbog ovoga je neznatno izmijenjena funkcija ispravan().
Programski kod (C):
/*
napomena:
u proracunu nije uzeta u obzir
Gregorijanski, ali jesu prestupne godine
*/
promjena
kalendara
sa
Julijanskog
na
#include <stdio.h>
#include <stdlib.h>
/*Niz deklariran kao globalna varijabla (dostupna je svakoj funkciji u programu)
koji sadrzi broj dana svakog mjeseca, ubacena je nula na pocetku jer indeksiranje
u C-u pocinje od nule */
int broj_dana[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
struct datum
{
int dan;
int mje;
int god;
};
/*
Odredjuje da li je godina prestupna
*/
int prestupna(int gg)
{
/*godina je prestupna ako je djeljiva sa 400*/
if(gg%400==0) return 1;
/*godina je prestupna ako je djeljiva sa 4, ali ne sa 100*/
if(gg%4==0 && gg%100!=0) return 1;
}
return 0;
20
/*
Pretpostavljeno je da se radi o pozitivnoj godini, tj. poslije nove ere
funkcija ispituje da li je ispravan uneseni datum, tj. da li su dani i mjeseci u
datim granicama
*/
int ispravan(struct datum d)
{
if(d.dan<1 || d.dan>broj_dana[mm])
return 0;
if(d.mje<1 || d.mje>12) return 0;
}
return 1;
/* Broj dana od 1.1.1. godine do datuma */
int broj_dana(struct datum d)
{
int rezultat = 0, i;
/* Najprije sabiramo godine */
for (i=1; i<d.god; i++)
{
if (prestupna(i))
rezultat += 366;
else
rezultat += 365;
}
/* zatim mjesece */
for (i=1; i<d.mje; i++)
{
rezultat += broj_dana[i];
}
/* Dodatni dan u februaru za prestupnu godinu */
if (i == 2 && prestupna(d.god))
rezultat++;
/* i na kraju dane */
rezultat += d.dan;
return rezultat;
}
/* Glavni program */
int main()
{
/* Deklaracija i unos datuma */
struct datum prvi, drugi;
int razlika;
scanf("%d %d %d", &prvi.dan, &prvi.mje, &prvi.god);
scanf("%d %d %d", &drugi.dan, &drugi.mje, &drugi.god);
/* Da li su ispravni datumi */
if (!ispravan(prvi) || !ispravan(drugi)) {
printf("Ilegalan datum\n");
return 0;
21
}
/* Uzimamo razliku */
razlika = broj_dana(prvi) - broj_dana(drugi);
/* Apsolutna vrijednost razlike */
if (razlika < 0) razlika = -razlika;
printf ("%d", razlika);
return 0;
}
Zadatak 1.3. Dan u sedmici
Težina: O
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2012. godine
Napisati program koji za određeni datum ispisuje dan u sedmici tog datuma.
Savjet. Ako znate današnji dan u sedmici i možete odrediti broj dana koliko je proteklo između datog i
današnjeg datuma, nije teško odrediti dan u sedmici datog datuma.
Napomena. Godina je prestupna ako je redni broj godine djeljiv sa 4 i nije djeljiv sa 100, ili ako je djeljiv sa
400.
Ulazni podaci
Na ulazu se nalazi datum u obliku:
DD.MM.GGGG
pri čemu su DD dvije cifre koje predstavljaju redni broj dana u mjesecu, MM predstavlja redni broj
mjeseca, a GGGG predstavlja godinu. Pretpostaviti da će ulaz uvijek biti u ovom obliku.
Izlazni podaci
Ukoliko je uneseni datum nemoguć (npr. 29. februar u godini koja nije prestupna ili 32. januar), program
treba ispisati poruku GRESKA nakon koje slijedi znak za novi red.
U suprotnom, treba ispisati jedan od stringova: "Ponedjeljak", "Utorak", "Srijeda", "Cetvrtak", "Petak",
"Subota" ili "Nedjelja" koji odgovara danu u sedmici primljenog datuma, nakon kojeg slijedi znak za novi
red.
Ograničenja
Vaš program ne smije raditi duže od 0.1s i ne smije koristiti više od 16 MiB memorije.
Primjer 1
Ulazni podaci
23.04.2012
Izlazni podaci
Ponedjeljak
22
Primjer 2
Ulazni podaci
31.04.2012
Izlazni podaci
GRESKA
Programski kod (C):
#include <stdio.h>
/* Broj dana u mjesecu */
int bdum[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
/* Da li je godina prestupna */
int prestupna(int g) {
if (g%4 == 0 && g%100 != 0 || g%400 == 0) return 1;
return 0;
}
int datum_u_dane(int d, int m, int g) {
/* Posto je godina uvijek pozitivna, pretvoricemo datum u broj dana od
1.1.1. godine */
/* Gregorijanski kalendar se poceo primjenjivati od 1582. godine tako da
* je striktno govoreci ova funkcija netacna za datume starije od tog. */
int i;
int brojdana=d;
for (i=1; i<m; i++) {
brojdana += bdum[i];
/* 29. februar u prestupnoj godini */
if (i==2 && prestupna(g)) brojdana++;
}
for (i=1; i<g; i++)
/* prestupne godine su imale 366 dana */
brojdana += 365 + prestupna(i);
return brojdana;
}
int provjeri_datum(int d, int m, int g) {
if (d<1 || m<1 || g<1) return 0;
if (m>12) return 0;
/* 29. februar a godina nije prestupna */
if (m==2 && d==29 && !prestupna(g)) return 0;
if (d != 29 && d>bdum[m]) return 0;
return 1; /* datum je ispravan */
}
int main() {
int d,m,g;
int d1, d2, dan_u_sedmici;
scanf("%d.%d.%d", &d, &m, &g);
char imena_dana[7][13] = { "Ponedjeljak", "Utorak", "Srijeda", "Cetvrtak",
23
"Petak", "Subota", "Nedjelja" };
if (!provjeri_datum(d,m,g)) {
printf ("GRESKA\n");
return 0;
}
d1 = datum_u_dane(d,m,g);
d2 = datum_u_dane(30, 4, 2012); /* 30.4.2012. je ponedjeljak */
if (d1>d2)
dan_u_sedmici = (d1-d2)%7;
else {
dan_u_sedmici = 7 - (d2-d1)%7;
if (dan_u_sedmici == 7) dan_u_sedmici = 0;
}
printf("%s\n", imena_dana[dan_u_sedmici]);
return 0;
}
/*
Zadatak 1.4. Rimski brojevi
Težina: O
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2010. godine
Napisati program koji učitava parove rimskih brojeva, a zatim ispisuje zbir ovih parova, takođe u formi
rimskog broja.
O rimskim brojevima
Rimskim brojevima se ne mogu predstaviti nula ili negativni brojevi, a za predstavljanje broja 4000 i većih
koriste se simboli koji se ne mogu predstaviti ASCII znakovima. Iz tog razloga će svi rimski brojevi
obavezno u opsegu 1-3999.
Rimske cifre su:
I
V
X
L
C
D
M
1
5
10
50
100
500
1000
Ostali brojevi dobijaju se kombinovanjem ovih vrijednosti, od većih ka manjim ciframa npr.
2637 = MMDCXXXVII
Izuzetak su cifre 4 i 9 koje se dobijaju po sljedećoj tabeli:
IV
IX
XL
XC
CD
CM
4
9
40
90
400
900
24
Ulaz:
Najprije je dat jedan cijeli broj N (maksimalno 100) koji predstavlja broj rimskih brojeva. Nakon toga. svaki
red ulaza predstavlja jedan rimski broj opisan kombinacijom velikih slova čije je značenje dato iznad.
Izlaz:
Za svaka dva broja na ulazu biće ispisan jedan rimski broj koji predstavlja njihov zbir. U slučaju da zbir
brojeva bude veći od 3999, umjesto zbira biće ispisana poruka "Prekoracenje". U slučaju da se na ulazu
nalazi neparan broj redova posljednji red će biti ignorisan.
Programski kod (C++):
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
string rimcifre[13] = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V",
"IV", "I" };
int arapcifre[13] = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
int rimski2arapski(string rimski)
{
int k(0);
int arapski(0);
for (int i(0); i<13; i++) {
while (rimski.substr(k, rimcifre[i].length()) == rimcifre[i]) {
arapski += arapcifre[i];
k += rimcifre[i].length();
}
}
return arapski;
}
string arapski2rimski(int arapski) {
string rimski("");
for (int i(0); i<13; i++) {
while (arapski>=arapcifre[i]) {
arapski -= arapcifre[i];
rimski += rimcifre[i];
}
}
return rimski;
}
int main() {
string sabirak1, sabirak2,zbir;
getline(cin,sabirak1);
getline(cin,sabirak2);
zbir = arapski2rimski( rimski2arapski(sabirak1) +
rimski2arapski(sabirak2) );
25
cout<<zbir<<endl;
return 0;
}
Zadatak 1.5. Numerička matematika
Težina: S
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2007.
godine
Metodom polovljenja intervala naći rješenje funkcije (nulu
funkcije):
f(x) = a1xN+a2xN-1+...aN-x+aN+1
na intervalu (A,B) sa greškom manjom od ε (N<10). Rezultat
ispisati na ekran.
Ulaz:
Na ulazu se nalazi redom (svaki u zasebnom redu):
- cijeli brojevi A i B razdvojeni razmakom
- realan broj ε
- prirodan broj N (manji od 10) koji definiše red funkcije
- realni brojevi koji predstavljaju vrijednosti koeficijenata a1,
a2,... aN+1
Izlaz:
Na izlazu treba ispisati realan broj koji predstavlja vrijednost
na intervalu x∈( A , B) za koju je f(x)=0.
Pretpostaviti da će uvijek postojati tačno jedna takva vrijednost
na zadatom intervalu.
Slika 1: Metoda polovljenja intervala
Pojašnjenje:
Metoda polovljenja intervala (metoda bisekcije, engl. bisection method) je jedna od osnovnih metoda
numeričke matematike. Ovom metodom može se odrediti vrijednost x∈( A , B) za koju je f(x)≈0
(ustvari f (x)∈(−ε ,ε) gdje je ε neka unaprijed zadata maksimalna greška).
Uslovi za primjenu metode su da je funkcija neprekidna na intervalu (A,B) i da ima jedno rješenje (nulu) na
tom intervalu. Matematički se može dokazati da iz ova dva uslova proizlazi da vrijednosti f(A) i f(B) moraju
imati različit predznak.
Princip rada algoritma je sljedeći: neka je trenutni interval (A,B):
• Najprije se izračunava vrijednost funkcije na sredini intervala f(S) pri čemu je S=(A+B)/2.
• Ako je ta vrijednost u opsegu (-ε,ε) onda je S naša tražena vrijednost x i petlja se prekida.
• Ako f(S) ima isti predznak kao f(A) novi interval je (S,B), a ako ima isti predznak kao f(B) novi
interval je (A,S).
26
Drugim riječima, interval se sužava oko tačke u kojoj se nalazi nula funkcije kao na slici.
Da bismo riješili ovaj programski zadatak, najprije ćemo napisati funkciju koja izračunava vrijednost datog
polinoma za dati niz koeficijenata:
f(x) = a1xN+a2xN-1+...aN-x+aN+1
Ova funkcija bi mogla izgledati ovako:
/* Izracunavanje vrijednosti polinoma.
a = niz koeficijenata, N = red polinoma, x = tacka u kojoj trazimo vrijednost */
float polinom(float a[], int N, float x) {
float rezultat = 0;
int i;
for (i=0; i<=N; i++) {
rezultat += a[i] * pow(x, N-i);
}
return rezultat;
}
Niz a i vrijednost N su date na ulazu, dok umjesto x uvrštavamo S. Zatim možemo postaviti okvirnu
petlju. Petlja se izvršava sve dok je f(S) po apsolutnoj vrijednosti veće od ε, odnosno:
while (polinom(a,N,S) > epsilon || polinom(a,N,S) < -epsilon) {
...
}
U petlji ćemo izračunati novu vrijednost za A, B i S. Za ovo je potrebno da odredimo predznak vrijednosti
f(A), f(B) i f(S). Možemo napraviti pomoćnu funkciju za određivanje predznaka (ovakva funkcija se u
matematici obično zove signum ili sgn):
int sgn(float x) {
if (x<0) return -1;
return 1;
}
Sada popunjavamo u petlji dio koji smo označili sa tri tačke:
while (polinom(a,N,S) > epsilon || polinom(a,N,S) < -epsilon) {
if (sgn(polinom(a,N,S)) == sgn(polinom(a,N,A)) {
A = S; /* Novi pocetak intervala */
} else {
B = S; /* Novi kraj intervala */
}
S = (A+B)/2; /* Nova sredina intervala */
}
Ovakvo rješenje je prilično neefikasno. Naime, najsporiji dio ovog programa je izračunavanje polinoma
koje se u ovoj petlji izvršava čak 4 puta. Da bismo to popravili, uvesti ćemo pomoćne varijable fS i fA koje
predstavljaju ranije izračunate vrijednosti polinoma:
while (fS > epsilon || fS < -epsilon) {
if (sgn(fS) == sgn(fA)) {
A = S; /* Novi pocetak intervala */
fA = fS; /* Koristimo ranije izracunatu vrijednost polinoma */
27
}
} else {
B = S; /* Novi kraj intervala */
}
S = (A+B)/2; /* Nova sredina intervala */
fS = polinom(a,N,S);
Vidimo da se u ovoj petlji funkcija polinom() poziva samo jednom u odnosu na ranija 4 puta. Ostaje još
samo da napravimo deklaracije, ulaz i izlaz vrijednosti.
Programski kod (C):
#include <stdio.h>
#include <math.h> /* Funkcija pow() */
/* Izracunavanje vrijednosti polinoma.
a = niz koeficijenata, N = red polinoma, x = tacka u kojoj trazimo vrijednost */
float polinom(float a[], int N, float x) {
float rezultat = 0;
int i;
for (i=0; i<=N; i++) {
rezultat += a[i] * pow(x, N-i);
}
return rezultat;
}
/* Predznak broja x */
int sgn(float x) {
if (x<0) return -1;
return 1;
}
int main() {
float a[10], A, B, S, fA, fS, epsilon;
int N, i;
/* Unos vrijednosti */
/* Pretpostavljamo da sve vrijednosti zadovoljavaju uslove zadatka */
scanf("%f %f", &A, &B);
scanf("%f", &epsilon);
scanf("%d", &N);
for (i=0; i<=N; i++) {
scanf("%f", &a[i]);
}
/* Postavljam pocetne vrijednosti za S, fA, fS */
S = (A+B)/2;
fA = polinom(a,N,A);
fS = polinom(a,N,S);
/* Polovljenje intervala */
while (fS > epsilon || fS < -epsilon) {
28
if (sgn(fS) == sgn(fA)) {
A = S; /* Novi pocetak intervala */
fA = fS; /* Koristimo ranije izracunatu vrijednost polinoma */
} else {
B = S; /* Novi kraj intervala */
}
S = (A+B)/2; /* Nova sredina intervala */
fS = polinom(a,N,S);
}
printf ("%f", S);
return 0;
}
Zadatak 1.6. Bazen (jednostavnija varijanta)
Težina: O
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2003. godine
Izračunati broj pločica potrebnih za prekrivanje bazena. U obračunu predvidjeti otpad pločica: Ako postoji
ostatak dijeljenja dužina/širine površine bazena i dužine/širine pločice dodaj jedan red pločica.
Ulaz:
U prvom redu ulaza su data tri cijela broja x, y i z koji predstavljaju dimenzije bazena (dužina, širina i
visina) u cm.
U drugom redu su dva cijela broja koji predstavljaju dimenzije jedne pločice (dužina i širina) u cm.
Izlaz:
Jedan cijeli broj koji predstavlja broj pločica potrebnih za prekrivanje bazena.
Programski kod (Pascal):
Program Bazen(input, output);
Var
a,b,x,y,z,p : Integer;
(*a duzina, b sirina plocice*)
(*x duzina, y sirina, z visina bazena*)
Function po_duz(k,l : Integer) : Integer;
Begin
If (k mod l = 0) Then po_duz := k div l
Else po_duz := (k div l) +1
End;
Function po_pov(b1,b2,p1,p2 : Integer) : Integer;
Begin
po_pov := po_duz(b1, p1) * po_duz(b2, p2)
End;
Begin
Write(‘Unesi dimenzije bazena: ’); Readln(x,y,z);
29
End.
Write(‘Unesi dimenzije plocice: ’); Readln(a,b);
p := 2*(po_pov(x, z, a, b) + po_pov(y, z, a, b)) + po_pov(x, y, a, b);
Writeln(‘Broj potrebnih plocica je: ’,p)
30
2. Operacije s nizovima
Zadatak 2.1. Najveći među najmanjima
Težina: O
Data je matrica dimenzija R x K, vaš zadatak je da za svaku kolonu te matrice odredite najmanji element, te
zatim da među tim elementima odredite najveći. Elementi matrice mogu biti proizvoljni brojevi.
Ulaz:
U prvom redu ulaza se nalaze dva broja r i k ( r,k ≤ 1000 ) koji predstavljaju broj redova i kolona matrice,
respektivno. U sljedećih r redova će se nalaziti po k brojeva koji predstavljaju elemente matrice.
Izlaz:
U jedini red izlaza potrebno je ispisati traženi broj.
Pojašnjenje (Nalaženje minimuma i maksimuma niza):
U takmičarskim zadacima često se zahtjeva od programera da nađe najefikasnije rješenje nekog problema,
što se često postiže traženjem najveće ili najmanje određene vrijednosti. Zbog toga ćemo ovdje ukratko
opisati proceduru nalaženja najmanjeg/najvećeg elementa niza.
Za nalaženje najvećeg elementa nekog niza, polazimo od sljedećeg:
- Deklarišemo varijablu npr. max i dodijelimo joj vrijednost prvog člana niza. Eventualno možemo
koristiti najmanju moguću vrijednost ako je ona poznata (npr. ako znamo da su svi članovi pozitivni
možemo postaviti max na 0).
- Prethodno će možda biti potrebno provjeriti da li niz ima ijedan element, jer u tom slučaju traženje
maksimuma/minimuma neće imati smisla.
- Analiziramo svaki član niza redom te ukoliko je on veći od vrijednosti varijable max, dodijelimo
vrijednost tog člana niza našoj varijabli max. Ponekad je potrebno i dodatno čuvati indeks najvećeg
člana, što se jednostavno implementira pomoću dodatne varijable.
Implementacija navedene procedure u programskom jeziku C:
int max = a[0];
for(i=1;i<a_velicina;i++) {
if(a[i]>max) {
max = a[i];
}
}
Implementacija navedene procedure u programskom jeziku Pascal:
max := niz[1];
for i := 2 to velicina do
begin
if niz[i]>max then max := niz[i];
end
Procedura je analogna i za nalaženje najmanjeg elementa niza.
Primijetimo da navedeni proces ima O(n) kompleksnost. Najefikasniji algoritmi za sortiranje niza imaju
31
O(n log n) kompleksnost i zbog toga ih ne trebamo koristiti osim ako nam nije potreban cijeli sortiran niz.
Programski kod (C):
#include <stdio.h>
#include <stdlib.h>
int main()
{
int r,k;
float matrica[1000][1000];
int i,j,prvi;
float min, max;
scanf("%d %d",&r,&k);
for(i=0; i<r; i++)
{
for(j=0;j<k;j++)
{
scanf("%f",&matrica[i][j]);
}
}
/*
kod za odredjivanje najmanjeg elementa, odmah nakon sto smo ga odredili,
poredimo ga sa dosadasnjim najvecim elementom, da ih ne bismo
morali cuvati u zasebnom nizu.
Ovo se moglo implementirati odmah tokom ulaza, i ne bi postojala potreba
za matricom, ali radi jednostavnosti uradjena je ovakva implementacija
*/
max = 0;
prvi = 1;
for(i=0;i<r;i++)
{
min = matrica[i][0];
for(j=1; j<k;j++)
{
if(matrica[i][j]<min)
{
min = matrica[i][j];
}
}
}
if(min > max || prvi == 1)
{
max = min;
prvi = 0;
}
printf("%f",max);
return 0;
32
}
Programski kod (Pascal):
program NajveciNajmanjih(output);
var
i,j,max,min,prvi,r,k : integer;
matrica: Array[1..1000] of Array[1..1000] of Integer;
begin
readln(r);
readln(k);
for i := 1 to r do
begin
for j := 1 to k do
begin
readln(matrica[i][j]);
end;
end;
max := 0;
prvi := 1;
for i := 1 to r do
begin
min := matrica[i][1];
for j := 2 to k do
begin
if matrica[i][j]<min then min := matrica[i][j];
end;
if (min>max) or (prvi=1) then
begin
max := min;
prvi := 0;
end;
end;
end.
writeln(max);
Zadatak 2.2. Histogram
Težina: S
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2003. i 2008. godine.
Potrebno je odrediti histogram niza, odnosno broj ponavljanja različitih članova niza prirodnih brojeva
manjih od 1000.
Ulaz:
Cijeli broj N koji predstavlja broj članova niza (maksimalno 100).
Nakon toga slijedi N brojeva, svaki u zasebnom redu, pri čemu su svi brojevi na intervalu [0,999].
Izlaz:
Svaki red sadrži dva broja razdvojena razmakom. Prvi broj je član niza, a drugi broj je broj ponavljanja toga
33
člana u nizu. Članovi niza trebaju biti poredani po veličini u rastućem redoslijedu i ne smiju se ponavljati. U
slučaju da se član niza ne javlja niti jednom, ne treba ga ispisati.
Primjer ulaza:
5
234
12
234
8
12
Primjer izlaza:
81
12 2
234 2
Programski kod (C):
#include <stdio.h>
int main()
{
int histogram[1000];
int i,n,broj;
/* Postavljamo sve članove niza histogram na 0 */
for (i=0; i<1000; i++)
histogram[i] = 0;
/* Učitavamo broj članova niza */
scanf ("%d", &n);
/* Učitavamo članove i istovremeno računamo njihov histogram */
for (i=0; i<n; i++) {
scanf("%d", &broj);
histogram[broj]++;
}
/* Ispisujemo histogram */
for (i=0; i<1000; i++) {
if (histogram[i]>0) {
printf("%d %d\n", i, histogram[i]);
}
}
return 0;
}
Programski kod (Pascal):
program HistogramProg(output);
var
i,n,broj : integer;
histogram: Array[1..1000] of Integer;
34
begin
{ Postavljamo sve članove niza histogram na 0 }
for i := 1 to 1000 do
histogram[i] := 0;
{ Učitavamo broj članova niza }
readln(n);
{ Učitavamo članove i istovremeno računamo njihov histogram }
for i := 1 to n do
begin
readln(broj);
histogram[broj+1] := histogram[broj+1] + 1;
end;
end.
{ Ispisujemo histogram }
for i := 1 to 1000 do
begin
if histogram[i]>0 then
begin
writeln(i-1, ' ', histogram[i]);
end;
end;
Zadatak 2.3. Operacije sa skupovima
Težina: O
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2003. godine
Data su dva skupa cijelih brojeva X i Y. Ispisati nizove koji predstavljaju:
a) uniju skupova X i Y
b) presjek skupova X i Y
c) razliku skupova X i Y
35
3. Prosti brojevi
Prost broj se definiše kao prirodan broj koji je djeljiv samo sa jedinicom i samim sobom. Ovdje treba
naglasiti riječ samo. Naime svaki prirodan broj je djeljiv sa jedinicom i samim sobom, ali prosti brojevi nisu
djeljivi niti sa jednim drugim brojem. Jedini način da provjerimo da li je neki broj N prost je da ga probamo
podijeliti sa svim brojevima na intervalu (1,N). Ustvari dovoljno je da idemo do √ N (uključivo) jer N
sigurno nije djeljiv većim brojevima.
Pri rješavanju zadatka možemo koristiti operator modulo, pa ako on vrati nulu znači da je prvi operand
djeljiv drugim.
Kod u programskom jeziku C:
prost = 1;
for (i=2; i<=sqrt(N); i++) {
if (n%i == 0) {
prost = 0;
break;
}
}
Kod u programskom jeziku Pascal:
prost := true;
for i := 2 to Trunc(sqrt(n)) do
{ cijeli i realni brojevi se ne mogu direktno porediti u Pascalu }
begin
if n mod i = 0 then
begin
prost := false; break;
end;
end;
Primjena break naredbe za prekid petlje ako smo ustanovli da broj nije prost u praksi može donijeti znatno
poboljšanje performansi.
Ovdje je potrebno naglasiti da broj 1 nije niti prost niti složen. Kod dat iznad će za N=1 vratiti da je prost,
pa ukoliko to ima efekta na vaš program trebate dodati poseban uslov koji provjerava da li je u pitanju broj
1 ili ne.
Ova relativna kompleksnost provjere da li je broj prost dovela je do vrlo raširene upotrebe prostih brojeva
u kriptografiji. Ako biste našli efikasniji algoritam za provjeru prostosti broja, čeka vas svjetska slava jer
biste mogli "provaliti" mnoge poznate oblike kriptografske zaštite!
Zadatak 3.1. Razdvojiti proste od složenih
Težina: S
Na ulazu se nalazi niz od maksimalno 100 pozitivnih cijelih brojeva. Program treba da ispiše iste te brojeve
presložene tako da se najprije ispišu svi članovi niza koji su prosti brojevi, a zatim svi članovi koji su složeni
36
brojevi. Unutar skupova prostih i složenih brojeva treba biti očuvan redoslijed iz polaznog niza, dakle
brojevi trebaju biti dati istim redom kao i u polaznom nizu.
Ulaz:
Broj N koji označava veličinu niza, nakon čega slijedi N pozitivnih cijelih brojeva.
Izlaz:
Cijeli brojevi presloženi na opisani način razdvojeni znakom novi red.
Primjer ulaza:
5
8
7
9
3
5
Primjer izlaza:
7
3
5
8
9
Programski kod (C):
#include <stdio.h>
int main() {
int ulaz[100], prosti[100], slozeni[100];
int n, i, j, prost, brprostih, brslozenih;
scanf("%d", &n);
/* Unosimo niz ulaz */
for (i=0; i<n; i++)
scanf("%d", &ulaz[i]);
/* Stavljamo clanove niza ulaz u odgovarajuce nizove */
brprostih = brslozenih = 0;
for (i=0; i<n; i++) {
/* Da li je ulaz[i] prost broj? */
prost=1;
for (j=2; j<sqrt(ulaz[i]); j++) {
if (ulaz[i] % j == 0) {
prost=0;
break;
}
}
/* Ako je prost i veci od jedan stavljamo u niz prosti */
if (ulaz[i]>1 && prost==1) {
/* brprostih nam sluzi kao indeks u nizu prosti */
prosti[brprostih] = ulaz[i];
37
brprostih++;
} else {
}
/* a brslozenih u nizu slozeni */
slozeni[brslozenih] = ulaz[i];
brslozenih++;
}
/* Ispis niza prosti */
for (i=0; i<brprostih; i++)
printf("%d\n", prosti[i]);
/* Ispis niza slozeni */
for (i=0; i<brslozenih; i++)
printf("%d\n", slozeni[i]);
}
return 0;
Programski kod (Pascal):
program RazdvajanjeProstih(output);
var
ulaz,prosti,slozeni: Array[1..100] of integer;
n,i,j,brprostih,brslozenih : integer;
prost : boolean;
begin
readln(n);
{ unosimo niz brojeva }
for i := 1 to n do
readln(ulaz[i]);
{ stavljamo clanove niza u odgovarajuce nizove }
brprostih := 1;
brslozenih := 1;
for i := 1 to n do
begin
{ da li je ulaz[i] prost broj? }
prost := true;
for j := 2 to Trunc(sqrt(ulaz[i])) do
begin
if ulaz[i] mod j = 0 then
begin
prost := false; break;
end;
end;
{ ako jeste, stavljamo ga u niz prostih }
if (ulaz[i]>1) and prost then
begin
prosti[brprostih] := ulaz[i];
brprostih := brprostih + 1;
end else
{ a ako nije, u niz slozenih }
38
begin
end;
slozeni[brslozenih] := ulaz[i];
brslozenih := brslozenih + 1;
end;
{ ispisujemo sve proste brojeve }
for i := 1 to brprostih-1 do
writeln(brprostih[i]);
end.
{ ispisujemo sve slozene brojeve }
for i := 1 to brslozenih-1 do
writeln(brslozenih[i]);
Zadatak 3.2. Kazna
Težina: S
Mali Alvin nikada nije volio matematiku, zbog čega je često nemiran na času. Zbog toga mu nastavnik
često zadaje poseban problem tokom časa, kao pokušaj da izađe na kraj sa Alvinovim nestašlucima.
Profesor bi mu napisao u svesku broj N (gdje je N ≤ 2 31-1), i Alvinov zadatak bi bio da napiše najmanji
broj takav da je proizvod njegovih cifara jednak broju N. Naravno, prije toga bi profesor uvijek provjerio
da li je to moguće.
Kako Alvin ne želi da dugo radi zadatke, zamolio je vas da mu pomognete da vara tako što ćete napisati
program koji rješava dati problem.
Ulaz:
Jedan broj N (gdje je N ≤ 231-1)
Izlaz:
Jedan broj koji zadovoljava navedene uslove
Pojašnjenje (Faktorizacija):
U matematici, faktorizacija broja N predstavlja njegovu dekompoziciju na skup prostih brojeva sa tom
osobinom kad se pomnože da daju broj N. Tako su:
faktorizacije brojeva 60, 1024 i 296846, dok su npr. brojevi 2, 3 i 5 prosti faktori broja 60.
Nalaženje prostih faktora je problem sa kojim se takmičari često susreću. Ovaj problem rješavamo bruteforce metodom, tj. ispitivanjem djeljivosti sa svakim manji brojem.
Najjednostavnija faktorizacija broja se postiže tako što redom provjeravamo svaki broj koji je manji od N.
Ukoliko je N djeljivo tim brojem, dijelimo ga sve dok je taj uslov ispunjen, a zatim prelazimo na sljedeći
veći broj.
for(i=2; i<=N;i++)
{
while(N%i==0)
{
N /=i;
faktori[indeks] = i;
39
indeks++;
}
}
Moguće je napraviti određene optimizacije da bismo smanjili vrijeme izvršavanja našeg koda.
Kao prvo, primjetimo da ukoliko smo provjerili djeljivost od N brojem 2, ne moramo provjeravati njegovu
djeljivost bilo kojim parnim brojem. Tako da djeljivost brojem 2 možemo ispitati posebno izvan for–petlje,
dok ćemo u for petlji ispitivati djeljivost samo sa neparnim brojevima.
Sljedeća optimizacija koju možemo napraviti je i gornja granica do koje je potrebno provjeravati djeljivost.
Matematički je jednostavno pokazati kako je dovoljno da provjeravamo djeljivost sa prostim brojevima koji
su manji od , sa još malim dodatkom na kraju koda. Sada naš kod dobija svoj konačni oblik:
while(!(N&1))
{
/* provjera djeljivosti brojem 2 koristeci binarnu
logiku – dodatna optimizacija */
/* djeljenje brojem 2 pomocu pomjeranja bita – dodatna
optimizacija */
faktori[broj_faktora++] = 2;
N=N>>1;
}
korjen = (int) sqrt(n);
for(i=3;i<=korjen;i+=2)
{
while(N%i==0)
{
N/=i;
faktori[broj_faktora++] = i;
}
}
if(N>0) faktori[broj_faktora++] = N;
Moguće je da je da nakon for–petlje u n „ostane“ još jedan prost broj, koji na ovaj način ubacujemo u niz,
npr. ako je n = 26, provjeru ćemo raditi do int ()= 5, a znamo da je 26 = 2*13
Programski kod (C):
/*
Primjetimo da se navedeni problem svodi na nalazenje svih prostih faktora
broja N, i na njihov ispis od najmanjeg ka najvecem
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/*
n je broj koji zelimo faktorizirati
faktori pretstavljaju niz koji popunjavamo prostim faktorima, funkcija se ne
brine o tome da il je taj niz dovoljno velik
kao rezultat funkcija vraca velicinu niza faktora
*/
int faktorizacija(int n, int *faktori)
{
40
int i,korjen;
int broj_faktora=0;
while(!(n&1))
{
n=n>>1;
faktori[broj_faktora++] = 2;
}
korjen = (int) sqrt(n);
for(i=3;i<=korjen;i+=2)
{
while(n%i==0)
{
n/=i;
faktori[broj_faktora++] = i;
}
}
if(n>0) faktori[broj_faktora++] = n;
}
return broj_faktora;
int main()
{
int i,n;
/*
niz "faktori" je velicine 32 jer je autor logicki zakljucio da je to
najveci moguci broj prostih faktora
kako je n<=2^31 -1 i kako je 2 najmanji prosti faktor, 31 pretstavlja
najveci moguci broj prostih faktora (i tome je dodata jedinica
radi dodatne sigurnosti. Tokom takmicenja autor bi vjerovatno napravio
ovaj niz da bude velicine npr. 50
*/
int faktori[32];
}
int br_faktora = faktorizacija(12,faktori);
for(i=0;i<br_faktora;i++)
printf("%d",faktori[i]);
return 0;
Zadatak 3.3. Igra
Težina: S
Ivica i Haso su iz dosade odlučili da naprave takmičenje iz matematike. To rade na sljedeći način: obojica
dobiju po N brojeva ki ( 0<i≤N ) i njihov zadatak je da za svaki od brojeva k i odrede koji je ki-ti prosti broj
po redu. Na kraju, provjeravaju svoje rezultate i pobjednik je onaj koji je tačno odredio najviše prostih
brojeva.
Vaš zadatak je da napišete program koji će odrediti pobjednika.
41
Ulaz:
U prvom redu ulaza nalazi se cijeli broj N (N ≤ 10 000). Zatim u sljedećih N redova se nalaze brojevi k i (ki
≤ 2000) i mi za Ivicu, gdje je m i broj koji je odredio Ivica da je k i-ti prosti broj. Nakon toga slijedi još N
redova u kojima se nalaze brojevi ki i mi, ali ovaj put za Hasu.
Izlaz:
U jedini red izlaza potrebno je ispisati pobjednika, ukoliko postoji: „Ivica“ ili „Haso“ ili „Nerjeseno“.
Primjer ulaza:
3
20 71
40 173
500 3581
15 47
50 227
600 4327
Primjer izlaza:
Ivica
Pojašnjenje (Eratostenovo sito):
Eratostenovo sito je jednostavan i star algoritam za nalaženje svih prostih brojeva do unaprijed date
granice. To čini tako što iterativno označava kao složene brojeve umnoške prostih brojeva, počevši od
broja 2.
Višekratnici datog prostog broja se generišu počevši od tog prostog broja, kao aritmetički niz brojeva
kojem je prvi element i razlika upravo taj prosti broj.
Eratostenovo sito je jedan od najefikasnijih načina da se nađu svi manji prosti brojevi (ispod oko 10
miliona).
Postupak dobivanja prostih brojeva pomoću Eratostenovog sita:
- napišemo proizvoljan broj uzastopnih prirodnih brojeva počevši od 2
- zaokružimo najmanji neoznačeni broj
- precrtamo sve njegove višekratnike, koji nisu već označeni
- ponavljamo postupak od 2. koraka dok svi brojevi nisu označeni (zaokruženi ili precrtani)
Postupak završi u konačno mnogo koraka, jer na početku imamo konačno mnogo brojeva, a u svakom
koraku barem jedan broj označimo. Zaokruženi brojevi su prosti brojevi. Precrtani brojevi su složeni
brojevi.
Dobra grafička ilustracija algoritma se može naći na stranici:
http://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif
Algoritam, implementiran u programskom jeziku C :
for( i=2; i<=gornja_granica; i++)
{
if(!sito[i])
{
prosti_brojevi[velicina++] = i;
42
}
tmp = i+i;
while(tmp<=gornja_granica)
{
sito[tmp] = 1;
tmp+=i;
}
}
Programski kod (C):
#include <stdio.h>
#include <stdlib.h>
/*
funkcija prima kao argumente gornju granicu do koje ispitujemo postojanje
prostih brojeva te niz u koji cemo stavljati te proste brojeve
kao rezultat vraca velicinu niza
*/
int Eratostenovo_sito (int gornja_granica, int prosti_brojevi[])
{
int velicina=0;
int i;
int sito[18000];
memset(sito,0,sizeof sito);
int tmp;
for( i=2; i<=gornja_granica; i++)
{
if(!sito[i])
{
prosti_brojevi[velicina++] = i;
tmp = i+i;
while(tmp<=gornja_granica)
{
sito[tmp] = 1;
tmp+=i;
}
}
}
return velicina;
}
int main()
{
/*
Ideja zadatka jeste da na pocetku jednom pokrenemo funkciju Eratostenovo
sito i tako dobijemo niz prostih brojeva, tako da tokom
citanja ulaza mozemo direktno ispitivati
Nakon sto je implementirana funkcija Eratostenovo_sito, lagano se moze doci
do zakljucka da je dovoljan niz od 18000 elemenata
da bi sadrzao 2000 prostih brojeva
*/
int prosti_brojevi[2100];
43
int N;
int i, ivica=0, haso=0;
int k, m;
scanf("%d",&N);
Eratostenovo_sito(18000,prosti_brojevi);
velicini niza */
/*
Nije
nam
potreban
podatak
o
for(i=0;i<N;i++)
{
scanf("%d %d",&k,&m);
if(prosti_brojevi[k-1]==m) ivica++;
}
for(i=0;i<N;i++)
{
scanf("%d %d",&k,&m);
if(prosti_brojevi[k-1]==m) haso++;
}
if(ivica>haso)
printf("Ivica");
else if(haso>ivica)
printf("Haso");
else printf("Nerjeseno");
}
return 0;
Zadatak 3.4. Prosti blizanci
Težina: S
Prosti blizanci su parovi prostih brojeva koji su razdvojeni samo jednim parnim brojem npr. 11 i 13 su
prosti blizanci. Napisati program koji prima cijeli broj N i određuje broj parova prostih blizanaca koji su
manji od broja N.
Ulaz:
Cijeli broj N (N ≤ 2 000).
Izlaz:
Cijeli broj koji odgovara broju prostih blizanaca manjih od N.
Primjer ulaza:
100
Primjer izlaza:
8
44
Pojašnjenje:
Rješenje navedeno ispod generiše Erastotenovo sito (opisano u prethodnom zadatku), a zatim prolazi kroz
sve tako generisane brojeve i nalazi blizance.
Programski kod (C):
#include <stdio.h>
int Eratostenovo_sito (int gornja_granica, int prosti_brojevi[])
{
int velicina=0;
int i;
int sito[18000];
memset(sito,0,sizeof sito);
int tmp;
}
for( i=2; i<=gornja_granica; i++)
{
if(!sito[i])
{
prosti_brojevi[velicina++] = i;
tmp = i+i;
while(tmp<=gornja_granica)
{
sito[tmp] = 1;
tmp+=i;
}
}
}
return velicina;
int main()
{
int prosti_brojevi[2100];
int N, prostih, blizanaca;
scanf("%d",&N);
prostih = Eratostenovo_sito(N,prosti_brojevi);
blizanaca = 0;
for(i=1; i<prostih; i++)
{
if (prosti_brojevi[i] – prosti_brojevi[i-1] == 2) blizanaca++;
}
printf("%d", blizanaca);
}
return 0;
45
4. Iscrpna pretraga (exhaustive search, brute-force metod)
Nekada je jedino moguće rješenje određenog zadatka to da isprobamo sve dostupne varijante i nađemo
najpovoljniju. Ovakvo rješenje je naravno primjenjivo samo tamo gdje je broj mogućih rješenja ograničen.
U svakom slučaju vrijedi razmisliti o efikasnijem rješenju.
Zadatak 4.1. Konjićev skok
Težina: N
Odrediti koliko različitih 7-cifrenih brojeva je moguće formirati tako što se za svaku sljedeću cifru može
uzeti samo ona do čijeg se polja može doći pomijeranjem šahovske figure konjića od polja prethodne cifre,
pri čemu je tabla organizovana kao standardna telefonska tastatura.
Ulaz
Početna cifra broja; cijeli broj od 1 do 9.
Izlaz
Traženi broj 7-cifrenih brojeva koji su formirani na opisani način.
Primjeri:
Ulaz
3
Izlaz
104
Programski kod sa pojašnjenjem:
Napomena: u rješenju se koristi rekurzija koja će biti pojašnjena kasnije. Moguće je riješiti ovaj zadatak i
bez korištenja rekurzije.
/**
* Zadatak ce biti rijesen generisanjem svih mogucih brojeva koji
* zadovoljavaju date uslove i njihovim prebrojavanjem.
*/
#include <stdio.h>
// Matrica koja opisuje tastaturu.
char tastatura[][3] = { {'1',
{'4', '5',
{'7', '8',
{'*', '0',
'2', '3'},
'6'},
'9'},
'#'} };
// Matrica koja opisuje nacin kretanja konja na ploci.
// Svaka vrsta predstavlja jedan do mogucih poteza, pri cemu
// broj u prvoj koloni predstavlja pomak figure po redovima, a
46
// drugi pomak po kolonama u odnosu na trenutnu lokaciju.
int potezi[][2] = { {-1, -2},
{-1, +2},
{-2, -1},
{-2, +1},
{+1, -2},
{+1, +2},
{+2, -1},
{+2, +1} };
// Broj elemenata u nizu potezi
int const BR_POTEZA = 8;
// Niz koji ce sadrzavati broj koji se gradi
char broj[7];
int total = 0;
/**
* Funkcija prima koordinate (x, y) na kojoj se trenutno nalazi
* figura i parcijalno izgradjen broj.
* Depth predstavlja dubinu rekurzije, tj. cifru koju ce odrediti
* taj poziv funkcije.
*/
void rekurzija(int x, int y, int cifra) {
// Uslov za zaustavljanje rekurzije, tj. pretrazivanja.
// Ukoliko je broj cifara jednak 7, pronadjen je novi
// broj koji zadovoljava zadane uslove.
if (cifra == 7) {
++total;
return;
}
// Treba naci koja su to polja dostupna iz trenutne pozicije figure.
for (int i = 0; i < BR_POTEZA; ++i) {
// Red na koji se dolazi potezom i iz trentne pozicije
int const destX = x + potezi[i][0];
// Kolona na koju se dolazi potezom i iz trenutne pozicije
int const destY = y + potezi[i][1];
// Provjeriti da li je polje validno -- da li se nalazi unutar
// granica ploce.
if (destX < 0 || destX >= 4 || destY < 0 || destY >= 3) continue;
// Polja sa znakovima * i # se ne uzimaju u obzir...
if (destX == 3 && (destY == 0 || destY == 2)) continue;
// Kada je utvrdjeno da je novo polje validno, cifra koja se
// nalazi na njemu se dodaje u trenutni broj, te se funkcija
// poziva rekurzivno sa novom pozicijom figure na ploci.
broj[cifra] = tastatura[destX][destY];
rekurzija(destX, destY, cifra + 1);
}
}
int main() {
// Pocetna cifra!
int p_cifra;
scanf("%d", &p_cifra);
// Odrediti pocetni red i kolonu na osnovu unesenog broja.
int red= p_cifra / 3;
47
int kol = p_cifra % 3 - 1;
// Prvu cifru dodajemo u broj
broj[0] = tastatura[red][kol];
// Poziva se funkcija rekurzija s dubinom 1, jer je potrebno poceti
// od druge cifre trazenog broja (prva je zadata).
rekurzija(red, kol, 1);
printf("%d\n", total);
return 0;
}
48
5. Binarna pretraga
Binarna pretraga je metoda efikasnog pronalaska datog elementa u
nizu. Za razliku od iscrpne pretrage u kojoj moramo uporediti svaki
element u nizu sa traženom vrijednošću, što je definicija algoritamske
kompleksnosti O(n), binarna pretraga ima kompleksnost O(log n). No,
da bi se binarna pretraga mogla koristiti nužan preduslov je da je niz
sortiran (složen po veličini).
Binarna pretraga je učenicima poznatija pod imenom "metoda
Slika 2: Rekurzija
polovljenja intervala". Recimo da se pretražuje neki skup vrijednosti
na intervalu [P,Q]. Uzima se vrijednost u sredini tog intervala (P+Q)/2 te se ona poredi sa traženom
vrijednošću. Ako je tražena vrijednost veća od vrijednosti u sredini, novi interval je dat kao početak i
sredina prethodnog intervala, odnosno [P, (P+Q/2]. Ako je tražena vrijednost manja, novi interval je
sredina i kraj prethodnog intervala dakle [(P+Q)/2, Q]. Procedura se ponavlja sve dok se vrijednost ne
pronađe ili dok dužina intervala ne postane 1 u kojem slučaju zaključujemo da se vrijednost ne nalazi u
intervalu.
Zadatak 5.1. Pretraga brojeva
Težina: N
Dat je niz brojeva duzine n sortiran u rastućem redoslijedu. Treba odrediti redni broj prvog pojavljivanja
svakog od m sljedećih brojeva. Ukoliko se broj ne nalazi u nizu, ispisati poruku "Nije pronadjen".
Ulaz
Na prvoj liniji se nalazi prirodan broj n – dužina niza (maksimalno 100).
Zatim slijedi n cijelih brojeva sortiranih u rastućem redoslijedu.
U sljedećem redu je broj m (takođe maksimalno 100).
Slijedi m cijelih brojeva za koje treba dati redni broj prvog pojavljivanja u nizu.
Izlaz
Za svaki od datih m cijelih brojeva, traženi redni broj u nizu ili poruku “Nije pronadjen” (bez navodnika)
na posebnoj liniji.
Primjer ulaza:
5
1
1
3
4
5
3
3
8
49
1
Primjer izlaza:
3
Nije pronadjen
1
Programski kod (C):
/**
* Kao ulaz je dat sortiran niz brojeva duzine n.
* Zatim slijedi m brojeva za koje treba naci redni broj
* prvog pojavljivanja u nizu. Ukoliko se broj ne nalazi
* u nizu, ispisati poruku "Nije pronadjen".
*/
#include <stdio.h>
/**
* Binarna pretraga niza radi tako sto u svakom koraku
* polovi interval u kojem se moze naci trazeni broj.
* To se radi tako sto se trazeni broj uporedi sa
* brojem u sredini intervala, te ukoliko je trazeni broj
* veci pretraga nastavi u gornjoj polovini intervala, a ukoliko
* je manji u donjoj polovini.
* Ukoliko interval postane prazan, dati broj se ne nalazi u nizu.
*/
int binary_search(int niz[], int len, int broj) {
int lower = 0;
int upper = len - 1;
// Interval niza za koji u svakoj interaciji petlje vrijedi
// da se nalazi broj (ukoliko je u nizu) je [lower, upper]
while (lower <= upper) {
// Odredjujemo indeks elementa iz sredine intervala
int i = (lower + upper) / 2;
if (broj < niz[i]) {
// Ako je trazeni broj manji od srednjeg elementa intervala
// u sljedecoj iteraciji je interval ogranicen na donju polovinu
upper = i - 1;
} else if (broj > niz[i]) {
// Ako je trazeni broj veci od srednjeg elementa intervala
// u sljedecoj iteraciji je interval ogranicen na gornju polovinu
lower = i + 1;
} else if (lower != upper) {
// Srednji element intervala je jednak trazenom broju, ali u intervalu
// se nalazi vise od jednog elementa, pa se ne moze garantovati da je
// pronadjeno prvo pojavljivanje trazenog elementa. Interval je ogranicen
// u sljedecoj iteraciji na donju polovinu s ukljucenim srednjim elementom.
upper = i;
} else {
// U intervalu se nalazi samo jedan element koji je jednak trazenom.
return i;
}
}
// Interval je postao prazan u toku pretrage, sto znaci da dati element nije
50
// pronadjen u nizu.
return -1;
}
int main() {
int n;
scanf("%d", &n);
int niz[10000];
for (int i = 0; i < n; ++i) {
scanf("%d", &niz[i]);
}
int k;
scanf("%d", &k);
for (int i = 0; i < k; ++i) {
int broj;
scanf("%d", &broj);
// Funkcija binary_search vraca index niza na kojem
// se nalazi prvo pojavljivanje datog broja u datom nizu.
// Vraca -1 ukoliko se broj ne pojavljuje u nizu.
int index = binary_search(niz, n, broj);
if (index == -1) {
printf("Nije pronadjen\n");
} else {
printf("%d\n", index + 1);
}
}
return 0;
}
Slika 3: Opšti princip rješavanja problema Hanojske
kule
51
6. Rekurzija
Rekurzivna funkcija je funkcija koja poziva samu sebe. Najčešće u pitanju je neka funkcija f(x) koju
možemo izraziti preko f(x-1). Jedan primjer rekurzivne funkcije je faktorijel. Faktorijel broja n (piše se n!) se
definiše kao proizvod svih brojeva od 1 do n:
n !=1⋅2⋅3⋅...⋅(n−1 )⋅n
Odnosno možemo pisati:
n !=n⋅(n−1)!
Drugim riječima, faktorijel broja n je n što množi faktorijel broja n-1. Ovu funkciju možemo opisati
sljedećom C funkcijom:
int faktorijel(int n) {
return n * faktorijel(n-1);
}
Pascal:
function faktorijel(n : integer) : integer;
begin
faktorijel := n * faktorijel(n-1);
end;
Ono što je ovdje zbunjujuće za većinu učenika je da se, kada funkcija pozove samu sebe, stanje izvršenja
kao i vrijednosti svih promjenljivih sačuvaju u memoriji te se kreiraju nove instance svih tih varijabli, pri
čemu nova varijabla n ima vrijednost staro n minus 1.
Recimo da smo gore definisanu funkciju faktorijel pozvali sa brojem 4: faktorijel(4). Izvršenje programa
teče ovako:
•
najprije se kreira promjenljiva n čija je vrijednost 4;
•
zatim izvršenje programa dođe do izraza n*faktorijel(n-1) koji se ne može izračunati dok se ne
izračuna vrijednost faktorijel(n-1);
•
da bi se pozvala funkcija faktorijel(n-1) najprije treba izračunati koliko je n-1, a to je 3;
•
poziva se funkcija faktorijel sa parametrom 3; ovom prilikom se kompletno stanje izvršenja smješta
u memoriju (n=4);
•
itd. proces se ponavlja za 2, 1, 0 itd.
Ovdje uočavamo jednu od najvažnijih stvari u vezi rekurzije: svaka rekurzivna funkcija mora da terminira,
odnosno mora se postaviti granica do koje će rekurzija ići, u suprotnom rekurzija će se nastaviti sa -1, -2
itd. te će se program krahirati pošto postoji maksimalan broj puta koliko funkcija može pozvati samu sebe.
U matematici, faktorijel ima smisla samo za prirodne (pozitivne) brojeve, pa ćemo dodati da je faktorijel od
1 jednak 1, čime ćemo osigurati da se rekurzija završi na tom mjestu:
C:
int faktorijel(int n) {
if (n==1) return 1;
else return n * faktorijel(n-1);
52
}
Pascal:
function faktorijel(n : integer) : integer;
begin
if n=1 then
faktorijel := 1
else
faktorijel := n * faktorijel(n-1);
end;
Dakle, sada kompletan rekurzivni proces za poziv faktorijel(4) izgleda ovako:
• n=4
• n nije jednako 1, izvršava se else
• n*faktorijel(n-1) - moramo odrediti faktorijel(n-1)
• n-1 = 3
• pozivamo faktorijel(3)
◦ n=3
◦ n nije jednako 1, izvršava se else
◦ n*faktorijel(n-1) - moramo odrediti faktorijel(n-1)
◦ n-1 = 2
◦ pozivamo faktorijel(2)
▪ n=2
▪ n nije jednako 1, izvršava se else
▪ n*faktorijel(n-1) - moramo odrediti faktorijel(n-1)
▪ n-1 = 1
▪ pozivamo faktorijel(1)
• n=1
• pošto je n=1, vraća se vrijednost 1
▪ n*1 = 2*1 = 2
▪ vraća se vrijednost 2
◦ n*2 = 3*2 = 6
◦ vraća se vrijednost 6
• n*6 = 4*6 = 24
• vraća se vrijednost 24
4! = 24 što je tačan rezultat.
Svaki rekurzivni zadatak može se riješiti i bez rekurzije. Međutim, rekurzija nam omogućuje prirodniji način
rješavanja problema koji je često bliži matematičkoj definiciji. Ako imamo problem za n koji se može
izraziti preko neke funkcije od n-1, onda se takav problem može riješiti rekurzivno.
53
Zadatak 6.1. Hanojske kule
Težina: N
Postoji legenda da se negdje u Indiji nalazi hram 1 i u njemu tri velika stuba oko kojih su ovješena 64 velika
zlatna diska. Hinduski svećenici vrijedno premještaju diskove sa jednog stuba na drugi, ispunjavajući
drevno proročanstvo da će smak svijeta nastupiti onda kada svi diskovi budu premješteni sa prvog stuba na
drugi (koristeći treći kao pomoćni). Trik je u tome da je svaki disk različite veličine i da se veći disk nikada
ne smije nalaziti na manjem (jer bi u tom slučaju propao).
Ako pretpostavimo da svećeniku treba jedna sekunda da premjesti disk, kada će nastupiti smak svijeta?
Ovaj broj je poznat i iznosi 264-1 sekundi. Generalno, za n diskova potrebno je 2n-1 poteza da bi se igra
riješila.
Napišite program koji ispisuje sve poteze potrebne za rješavanje igre za zadati broj n diskova.
Ulaz
Prirodan broj n: broj diskova.
Izlaz
U svakom redu opisan je jedan korak u igri.
Tri stuba su označena slovima A, B i C, a diskovi su označeni brojevima 1, 2, ... n tako da je disk 1 najmanji
a disk n najveći.
Jedan potez je opisan brojem koji odgovara disku, nakon čega slijede dva slova koja odgovaraju stubu sa
kojeg je disk premješten i stubu na koji je premješten.
Npr. 8CB znači da je disk 8 prenesen sa stuba C na stub B.
Primjer ulaza:
3
Primjer izlaza:
1AC
2AB
1CB
3AC
1BA
2BC
1AC
Pojašnjenje:
Najprije probajte riješiti ovu interesantnu igru na papiru i to sa nekim manjim brojem diskova, recimo 4.
Ako razmišljate na ispravan način, trebali biste nadoći na opšti princip rješavanja zadatka za n diskova. Taj
princip glasi ovako:
Da bismo pomjerili n diskova sa stuba A na stub C, potrebno je pratiti sljedeće korake (pogledajte sliku 3):
•
korak 1: pomjerimo (n-1) diskova sa stuba A na pomoćni stub B, koristeći C kao pomoćni stub;
•
korak 2: pošto je sada na dnu stuba A ostao najveći disk, premjestimo ga direktno na stub C;
1 U drugoj varijanti legende hram se nalazi u Hanoju, Vijetnam, pa je pod tim imenom zadatak poznatiji.
54
•
korak 3: sada pomjerimo (n-1) diskova sa stuba B na stub C koristeći stub A kao pomoćni.
Ovo rješenje problema je rekurzivno, odnosno, rješenje problema za n smo opisali preko rješenja za n-1 koje
smo iskoristili u koracima 1 i 3.
Ovaj opis nam omogućuje da napišemo funkciju prebaci koja prebacuje n diskova sa stuba x na stub y
koristeći stub z kao pomoćni. U slučaju da je broj diskova n=1, treba samo ispisati na ekranu slova x i y. Na
ovaj način smo osigurali da se ova rekurzivna funkcija terminira.
Programski jezik C:
void prebaci(int n, char sa_stuba, char na_stub, char pomocni_stub)
{
/* Prebacujemo (n-1) diskova sa polaznog stuba na pomocni stub, koristeci
odredisni stub kao pomocni.
Ako je n=1 ovaj korak ne moramo raditi. */
if (n>1) prebaci(n-1, sa_stuba, pomocni_stub, na_stub);
/* Najveci disk n je ostao na dnu, njega mozemo direktno prebaciti. */
printf(“%d%c%c\n”, n, sa_stuba, na_stub);
/* Sada mozemo prebaciti (n-1) diskova sa pomocnog stuba na odredisni */
if (n>1) prebaci(n-1, pomocni_stub, na_stub, sa_stuba);
}
Pascal:
procedure prebaci(n : integer, sa_stuba, na_stub, pomocni_stub : char);
begin
{ Prebacujemo (n-1) diskova sa polaznog stuba na pomocni stub, koristeci
odredisni stub kao pomocni.
Ako je n=1 ovaj korak ne moramo raditi. }
if n>1 then prebaci(n-1, sa_stuba, pomocni_stub, na_stub);
{ Najveci disk n je ostao na dnu, njega mozemo direktno prebaciti. }
writeln(n, sa_stuba, na_stub);
{ Sada mozemo prebaciti (n-1) diskova sa pomocnog stuba na odredisni }
if n>1 prebaci(n-1, pomocni_stub, na_stub, sa_stuba);
end;
Preostaje još da napišemo glavni program koji poziva ovu funkciju.
Programski kod (C):
#include <stdio.h>
/* Funkcija prebaci obavlja sav bitan posao u ovom programu */
void prebaci(int n, char sa_stuba, char na_stub, char pomocni_stub)
{
/* Prebacujemo (n-1) diskova sa polaznog stuba na pomocni stub, koristeci
odredisni stub kao pomocni.
Ako je n=1 ovaj korak ne moramo raditi. */
if (n>1) prebaci(n-1, sa_stuba, pomocni_stub, na_stub);
/* Najveci disk n je ostao na dnu, njega mozemo direktno prebaciti. */
printf("%d%c%c\n", n, sa_stuba, na_stub);
55
/* Sada mozemo prebaciti (n-1) diskova sa pomocnog stuba na odredisni */
if (n>1) prebaci(n-1, pomocni_stub, na_stub, sa_stuba);
}
int main()
{
int n;
scanf("%d", &n);
prebaci(n, 'A', 'C', 'B');
return 0;
}
Programski kod (Pascal):
program Hanojske_kule;
var
broj_diskova : integer;
{ Procedura prebaci obavlja sav bitan posao u ovom programu }
procedure prebaci(n : integer, sa_stuba, na_stub, pomocni_stub : char);
begin
{ Prebacujemo (n-1) diskova sa polaznog stuba na pomocni stub, koristeci
odredisni stub kao pomocni.
Ako je n=1 ovaj korak ne moramo raditi. }
if n>1 then prebaci(n-1, sa_stuba, pomocni_stub, na_stub);
{ Najveci disk n je ostao na dnu, njega mozemo direktno prebaciti. }
writeln(n, sa_stuba, na_stub);
end;
{ Sada mozemo prebaciti (n-1) diskova sa pomocnog stuba na odredisni }
if n>1 prebaci(n-1, pomocni_stub, na_stub, sa_stuba);
begin
readln(broj_diskova);
prebaci(broj_diskova, 'A', 'C', 'B');
end.
Zadatak 6.2. Flood fill
Težina: N
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2010. godine
Bitmapa je tabela (matrica) sastavljena od nula i jedinica, koja može predstavljati recimo neku crno-bijelu
sliku. Napravite program koji učitava kvadratnu bitmapu (dimenzija NxN) i koordinate jedne tačke u toj
bitmapi.
Ako je na datim koordinatama vrijednost 1 program ne radi ništa. Ali ako se tu nalazi 0, program treba
zamijeniti vrijednost jedinicom, a zatim zamijeniti i sve susjedne tačke sve dok se ne dođe do ruba matrice
ili do znaka 1. Drugim riječima, program treba da se ponaša kao “flood fill” (punjenje) u grafičkim
aplikacijama: treba da popuni dio matrice sastavljen od nula omeđen rubovima matrice i jedinicama.
Prilikom popunjavanja kroz se matricu kreće samo u smjerovima gore, dolje, lijevo i desno, ali ne i po
dijagonali.
56
Ulaz:
Na ulazu se najprije nalazi broj N koji predstavlja dimenzije bitmape (ne veći od 100), a zatim odgovarajući
broj nula i jedinica, te konačno dva broja u opsegu 1-N koji predstavljaju koordinate tačke od koje treba
započeti popunjavanje. Koordinate (1,1) odgovaraju tački u gornjem lijevom uglu matrice.
Izlaz:
Na izlazu treba ispisati sadržaj matrice nakon popunjavanja, tako da su elementi matrice navedeni bez
razmaka a na kraju reda matrice ispisuje se znak za novi red.
Primjer ulaza:
10
0000000000
0000000000
0011111100
0010000100
0010000100
0010000100
0010000100
0011111100
0000000000
0000000000
5 5
Primjer izlaza:
0000000000
0000000000
0011111100
0011111100
0011111100
0011111100
0011111100
0011111100
0000000000
0000000000
Primjer ulaza:
10
0000100000
0001010000
0010001000
0100000100
1000000010
0100000100
0010001000
0001010000
0000100000
0000000000
5 5
Primjer izlaza:
0000100000
57
0001110000
0011111000
0111111100
1111111110
0111111100
0011111000
0001110000
0000100000
0000000000
Pojašnjenje:
U računarskoj grafici postoje brojni algoritmi za flood fill, sa više ili manje efikasnosti. No u rješenju ovog
zadatka nije se tražila posebna efikasnost, sve što se tražilo je korektno rješenje. Zato ovdje možemo
primijeniti rekurzivni algoritam koji najočiglednije slijedi iz postavke problema. Rekurzivni algoritam
rješavanja ovog problema je sljedeći: za koordinate x,y:
• ako je koordinata x ili y izvan opsega matrice, ne radi ništa
• u suprotnom, ako se na koordinatama (x,y) nalazi jedinica, ne radi ništa
• u suprotnom:
◦ na koordinate (x,y) postavi jedinicu
◦ zamijeni vrijednosti na koordinatama: (x-1,y), (x+1,y), (x,y-1), (x,y+1)
Pri prevođenju ovog pseudokoda u C ili Pascal trebamo voditi računa da indeksi niza veličine N u C-u su 0(N-1) a u Pascalu 1-N.
Programski kod (C):
#include <stdio.h>
int matrica[100][100];
/* Moramo proslijediti velicinu matrice funkciji posto je ona ucitana u main-u */
void floodfill(int x, int y, int velicina)
{
/* Ako su koordinate izvan opsega matrice, ne radimo nista (prekid
funkcije) */
if (x<0 || y<0 || x>=velicina || y>=velicina) return;
/* Ako se na koordinatama nalazi jedinica, ne radimo nista */
if (matrica[x][y] == 1) return;
/* Postavljamo 1 na koordinate */
matrica[x][y]=1;
}
/* Rekurzivno pozivamo funkciju za tacke iznad, ispod, lijevo i desno */
floodfill(x-1,y,velicina);
floodfill(x+1,y,velicina);
floodfill(x,y-1,velicina);
floodfill(x,y+1,velicina);
int main()
{
int i,j,velicina,x,y;
scanf("%d", &velicina);
58
/* Ucitavamo vrijednosti u matrici */
for (i=0; i<velicina; i++) {
for (j=0; j<velicina; j++) {
scanf("%d", &matrica[i][j]);
}
}
/* Ucitavamo polazne koordinate x i y */
scanf ("%d %d", &x, &y);
/* Poziv funkcije */
floodfill(x,y,velicina);
/* Ispis matrice */
for (i=0; i<velicina; i++) {
for (j=0; j<velicina; j++) {
printf("%d", matrica[i][j]);
}
/* Novi red na kraju reda matrice */
printf("\n");
}
return 0;
}
59
7. Sortiranje
Sortiranje je preuređenje nekog niza tako da su elementi poredani po veličini, bilo u rastućem ili u
opadajućem redoslijedu. Kod brojeva obično je intuitivno jasno šta predstavlja sortiranje. Kod tekstualnih
podataka (stringova) obično se podaci sortiraju po tzv. leksikografskom odnosno abecednom poretku:
AAA
AAB
AAC...
ABA
ABB...
BAA...
BAB...
ZAA...
ZZZ
Pored toga vi možete sortirati podatke proizvoljnog tipa (geometrijska tijela, učenike, predmete) po bilo
kojem kriteriju koji definišete (obim, površina, visina, ocjena).
Postoji više algoritama za sortiranje koji se razlikuju prije svega po performansama u različitim situacijama.
Npr. neki algoritmi imaju vrlo dobre performanse ako je niz već uglavnom sortiran (npr. za dodavanje
novog elementa u sortiran niz), dok neki daju optimalne performanse kod niza elemenata čija je raspodjela
u skupu mogućih vrijednosti slučajna. Međutim, algoritme sortiranja razlikujemo i po njihovoj
kompleksnosti odnosno jednostavnosti razumijevanja.
Na ovom nivou takmičenja odlučili smo se da vam prezentujemo osnovne algoritme tzv. kvadratične
kompleksnosti, odnosno kompleksnosti O(n2), a to su: Bubble sort, Selection sort i Insertion sort).
U nastavku imate programski kod ovih algoritama sa pojašnjenjima u formi komentara.
Programski kod sa pojašnjenjem:
/**
* Implementacije bubble, selection i insertion sorta.
*/
#include <stdio.h>
#define bool int
#define true 1
#define false 0
/**
* Bubble sort sortira niz tako sto u svakom prolazu kroz niz
* uporedjuje susjedne elemente i mijenja im mjesto ukoliko nisu
* u ispravnom odnosu. To znaci da ukoliko je potrebno sortirati
* niz u rastucem redoslijedu elementi trebaju zamijeniti poziciju
* ukoliko je onaj s manjim indeksom veci od svog sljedbenika.
* Algoritam zavrsava s izvrsavanjem kada u prolazu kroz niz se ne
* izvrsi ni jedna izmjena, sto znaci da su svi elementi u ispravnom
* medjusobnom odnosu, tj. da je niz sortiran.
*/
60
void bubble_sort(int niz[], int len) {
// "Beskonacna" petlja, ne znamo unaprijed koji je
// potrebni broj iteracija za sortiranje niza.
for (;;) {
// Varijabla koja prati da li je doslo do
// neke izmjene u nizu u ovom prolazu.
bool swapped = false;
for (int i = 0; i < len - 1; ++i) {
if (niz[i] > niz[i + 1]) {
// swap
int tmp = niz[i];
niz[i] = niz[i + 1];
niz[i + 1] = tmp;
swapped = true;
}
}
if (!swapped) break;
}
}
/**
* Selection sort sortira niz tako sto pronalazi najmanji element
* u nizu i stavlja ga na prvu poziciju. Zatim, u ostatku niza
* pronalazi najmanji element i stavlja ga na sljedecu poziciju.
* Ova procedura se ponavlja sve dok se odgovarajuci element ne
* stavi na zadnju poziciju niza.
*/
void selection_sort(int niz[], int len) {
// i je pozicija u nizu na koju treba staviti sljedeci najmanji
// element.
for (int i = 0; i < len; ++i) {
// Na kojem indeksu u nizu se nalazi trenutno najmanji
// pronadjeni element.
int min_index = i;
// Provjeravaju se samo elementi iza indeksa i, obzirom da je
// dio niza [0, i - 1] vec sortiran.
for (int j = i + 1; j < len; ++j) {
if (niz[j] < niz[min_index]) {
min_index = j;
}
}
// Kada je pronadjen najmanji element stavlja se na odgovarajucu
// poziciju u nizu.
int tmp = niz[i];
niz[i] = niz[min_index];
niz[min_index] = tmp;
}
}
/**
* Insertion sort radi tako sto u sortirani dio niza
* dodaje element da odgovarajucu poziciju pri cemu se
* svi elementi veci od njega pomijeraju prema desno.
* Na pocetku samo prvi element pripada sortiranom dijelu
* niza.
*/
void insertion_sort(int niz[], int len) {
for (int i = 1; i < len; ++i) {
// Sljedeci element koji se dodaje u sortirani
// dio niza [0, i - 1] je element na indeksu i.
61
int novi = niz[i];
int j;
for (j = i - 1; j >= 0; --j) {
// Sve elemente vece od elementa koji se dodaje pomijeriti
// prema desno.
if (niz[j] > novi) {
niz[j + 1] = niz[j];
} else {
// Indeks na koji treba staviti novi element je
// poslije elementa niz[j] obzirom da je niz[j] <= novi
break;
}
}
niz[j + 1] = novi;
}
}
int main() {
// Duzina niza
int n;
scanf("%d", &n);
int niz[1000];
for (int i = 0; i < n; ++i) {
scanf("%d", &niz[i]);
}
// Funkcijama za sortiranje je potrebno proslijediti i duzinu niza.
// bubble_sort(niz, n);
// selection_sort(niz, n);
insertion_sort(niz, n);
for (int i = 0; i < n; ++i) printf("%d ", niz[i]);
printf("\n");
return 0;
}
Zadatak 7.1. Elementarna nepogoda
Težina: S
Tokom nedavnih vremenskih nepogoda izmjerena je visina snijega u gradovima BiH te je napravljen spisak
oblika "Ime grada – visina snijega u cm". Potrebno je ispisati isti spisak u kojem su gradovi poredani po
visini snijega, tako da se na prvom mjestu nalazi grad sa najvećom visinom snijega, zatim drugi po visini,
treći itd. Ukoliko je u dva grada izmjerena ista visina snijega, potrebno je njihova imena ispisati abecednim
odnosno leksikografskim poretkom.
Ulaz
Na prvoj liniji se nalazi prirodan broj n – broj gradova (maksimalno 100).
U sljedećih n linija nalazi se ime grada (dužine maksimalno 20 znakova, bez razmaka), zatim razmak te cijeli
broj koji predstavlja visinu snijega u tom gradu.
Izlaz
Izlaz se sastoji od n linija istog oblika kao ulaz, dakle naziv grada, razmak te visina snijega, pri čemu je izlaz
sortiran kako je opisano u zadatku.
62
Primjer ulaza:
5
Sarajevo 150
Mostar 80
Tuzla 120
Zenica 110
Bihac 150
Primjer izlaza:
Bihac 150
Sarajevo 150
Tuzla 120
Zenica 110
Mostar 80
Programski kod (C):
#include <stdio.h>
#include <string.h>
struct snijeg {
char grad[20];
int visina;
};
int main() {
int n,i,j,max_index;
struct snijeg niz[100],tmp;
/* Ulaz podataka */
scanf("%d", &n);
for (i=0; i<n; i++) {
printf ("Grad %d: ",i);
scanf("%s %d", niz[i].grad, &niz[i].visina);
}
/* Primjenjujemo modifikovani selection sort u opadajucem redoslijedu */
for (i = 0; i < n; ++i) {
/* Na kojem indeksu u nizu se nalazi trenutno najveci
pronadjeni element. */
max_index = i;
/* Provjeravaju se samo elementi iza indeksa i, obzirom da je
dio niza [0, i - 1] vec sortiran. */
for (j = i + 1; j < n; ++j) {
/* Provjera visine snijega */
if (niz[j].visina > niz[max_index].visina) {
max_index = j;
}
/* Ako je visina snijega ista, poredimo imena gradova
63
leksikografski */
if (niz[j].visina == niz[max_index].visina) {
if (strcmp(niz[j].grad, niz[max_index].grad) < 0 ) {
max_index = j;
}
}
}
}
/* Kada je pronadjen najveci element stavlja se na odgovarajucu
poziciju u nizu. */
tmp = niz[i];
niz[i] = niz[max_index];
niz[max_index] = tmp;
/* Ispis */
for (i=0; i<n; i++)
printf("%s %d\n", niz[i].grad, niz[i].visina);
return 0;
}
64
8. Efikasno stepenovanje
Zadatak 8.1. Efikasno stepenovanje
Težina: N
Odrediti zadnjih 6 cifara broja xn.
Ulaz
Jedna linija sa dva cijela broja, x i n, razdvojena jednim razmakom.
0<= x <= 2147483647 i 0 <= n <= 2147483647
Izlaz
Zadnjih 6 cifara broja xn.
Primjer:
Ulaz
2345 2345
Izlaz
515625
Pojašnjenje:
Najprije je potrebno primijetiti da su ograničenja na veličinu ulaznih podataka zadatka prevelika da bi se
zadatak riješio naivnom metodom - računanje stepena pomoću funkcije pow, a zatim uzimanje zadnjih 6
cifara rezultata.
Jedna metoda za efikasno stepenovanje brojeva je sljedeća. Neka treba naći x n. Najprije napišemo broj n
kao sumu stepena dvojke
n = k1 + k2 + ... + km
Sada imamo da je:
xn = x(k1 + k2 + ... + km) = xk1 * xk2 * ... * xkm
Koristeći činjenicu da je svaki od elemenata k i stepen dvojke, xki možemo izračunati rekurzivnom
formulom:
xki = (x(ki / 2))2
Koristeći binarnu reprezentaciju eksponenta n, gornji proizvod možemo računati na sljedeći način. Idući
po bitima eksponenta s desna na lijevo (od najmanje značajnog bita, do najznačajnijeg bita) ukoliko je dati
bit 1, potrebno je da u konačni proizvod uđe faktor x k, gdje je k = 2i, a i je pozicija bita koji se razmatra (za
najmanje značajan bit i = 0, itd...).
Činjenica da se traži zadnjih 6 cifara znači da je potrebno naći rezultat x n po modulu 1000000.
65
Programski kod (C):
/**
* Odrediti zadnjih 6 cifara broja x^n.
*/
#include <stdio.h>
int power(int x, int n) {
long long sol = 1;
// Varijabla b ce predstavljati faktor koji ulazi u proizvod.
// Za najmanje znacajan bit to je x^(2^0) = x^1 = x
long long b = x;
while (n) {
if (n & 1 == 1) {
// Bit koji se razmatra je 1, pa treba konacno rjesenje
// pomnoziti odgovarajucom vrijednosti x^k
sol *= b;
// Rezultat po po modulu 1000000
sol %= 1000000;
}
// Binary shift udesno eksponenta -- razmatra se sljedeci bit
n >>= 1;
// Za taj bit faktor koji bi trebao uci u proizvod (ukoliko je bit 1)
// je x^(2*k) = x^k * x^k, s obzirom na gore navedenu rekurzivnu formulu.
b *= b;
b %= 1000000;
}
return sol;
}
int main() {
int x;
int n;
scanf("%d %d", &x, &n);
printf("%d\n", power(x, n));
return 0;
}
66
9. Pohlepni (greedy) algoritmi
Zadatak 9.1. Lopov
Težina: N
Lopov se našao u piljari gdje se nalazi više različitih gajbi voća i povrća. Svaka gajba ima poznatu težinu u
kg (w), kao i vrijednost svakog kg namirnice iz te gajbe. Lopov ne može nositi više od M kilograma. Nije
obavezno uzeti cijelu gajbu, već lopov može uzeti samo neki dio voća ili povrća iz nje.
Odrediti koliko najviše lopov može zaraditi u datoj situaciji.
Ulaz:
cijeli broj n - broj gajbi 0 <= 20000 <= n
n parova cijelih brojeva - ukupna težina i vrijednost kilograma svake od gajbi
cijeli broj M - maksimalna težina koju lopov moze ponijeti
Izlaz:
Najveća zarada lopova pod datim uslovima.
Primjer:
Ulaz
5
15 4
21
11
65
36
15
Izlaz
72
Pojašnjenje:
Može se primijetiti da će lopov najviše zaraditi ukoliko odabere gajbu koja ima najveću vrijednost po
jedinici težine i uzme najveći mogući broj kilograma tog proizvoda. Ukoliko je moguće uzeti cijelu gajbu,
onda prelazi na onu koja ima sljedeću najbolju vrijednost po jedinici težine. Ukoliko ne može uzeti cijelu
gajbu, uzima najveći dio koji može nositi i računa se odgovarajuća vrijednost.
Za svrhu implementacije je moguće prvo sortirati niz predmeta, te, zatim, redom uzimati elemente i
dodavati na ukupnu vrijednost.
Ovaj pristup rješavanju problema se zove pohlepni (greedy) algoritam iz razloga što se u svakom koraku
bira ona vrijednost koja najviše poboljsava rješenje tj. izbor ne zavisi ni od jednog od prethodnih izbora.
67
Programski kod (C):
#include <stdio.h>
#define bool int
#define true 1
#define false 0
typedef struct Predmet {
int weight;
int value;
} Predmet;
/**
* Funkcija vraca true ukoliko je vrijednost prvog predmeta po jedinici tezine
* veca od vrijednosti drugog predmeta po jedinici tezine.
*/
bool cmp(Predmet p1, Predmet p2) {
return p1.value > p2.value;
}
void insertion_sort(Predmet niz[], int len) {
for (int i = 1; i < len; ++i) {
// Sljedeci element koji se dodaje u sortirani
// dio niza [0, i - 1] je element na indeksu i.
Predmet novi = niz[i];
int j;
for (j = i - 1; j >= 0; --j) {
// Sve elemente vece od elementa koji se dodaje pomjeriti
// prema desno.
if (!cmp(niz[j], novi)) {
niz[j + 1] = niz[j];
} else {
// Indeks na koji treba staviti novi element je
// poslije elementa niz[j] obzirom da je niz[j] <= novi
break;
}
}
niz[j + 1] = novi;
}
}
int main() {
int n;
scanf("%d", &n);
Predmet predmeti[10000];
for (int i = 0; i < n; ++i) {
scanf("%d %d", &predmeti[i].weight, &predmeti[i].value);
}
int weight;
scanf("%d", &weight);
// Sortira predmete po opadajucoj vrijednosti po jedinici tezine.
insertion_sort(predmeti, n);
int value = 0;
68
}
for (int i = 0; i < n && weight != 0; ++i) {
if (weight >= predmeti[i].weight) {
// Lopov moze ponijeti cijelu gajbu.
weight -= predmeti[i].weight;
value += predmeti[i].value * predmeti[i].weight;
} else {
// Lopov ne moze ponijeti cijelu gajbu, odredjuje se vrijednost
// onog dijela koji je moguce ponijeti.
value += predmeti[i].value * weight;
weight = 0;
}
}
printf("%d\n", value);
return 0;
69
10. Osnovna geometrijska tijela (pravougaonici, kružnice)
Zadatak 10.1. Majansko prokletstvo
Težina: S
Naučnici su posmatrajući nebo otkrili veliku količinu meteora koji idu ka Zemlji i otkrili su da će pasti na
jedan od najbitnijih gradova na Zemlji i to na datum 21.12.2012. godine. Maje su ipak bile upravu! Naučnici
su otkrili tačne koordinate na koje će pasti meteori, a vaš je zadatak da odredite koliku će tačno štetu oni
napraviti.
Ulaz:
Prvi red ulaza će sadržati brojeve m, n, k, r (m,n,k <1000, r<10000) – koji redom predstavljaju broj kuća
oblika pravougaonika, kvadrata i kružnice (kvadratu i pravougaoniku su strane paralelne odabranom
koordinatnom sistemu). Sljedećih m redova će sadržati po četri broja: prvi i drugi broj su x i y koordinate
donje lijeve tačke pravougaonika, dok su treći i četvrti broj koordinate gornje desne tačke pravougaonika.
Zatim slijedi n redova koji sadrže tri broja: prvi i drugi broj su x i y koordinate donje lijeve tačke kvadrata
dok treći broj predstavlja dužinu njegovih strana. Nakon toga slijedi k redova sa po tri broja: prvi i drugi
broj su x i y koordinate centra kruga dok treći broj predstavlja dužinu njegovog poluprečnika. Nakon toga
slijedi r linija sa po dva broja – x i y koordinatom na koju će pasti pojedini meteor. Koordinate svih tačaka
su cjelobrojnog tipa, dužine stranica i poluprečnika ne moraju biti.
Izlaz:
U jedini red izaza potrebno je ispisati koliko će tačno kuća oštetiti meteori.
Primjer ulaza:
1115
0042
505
14 1 3
31
74
93
5 11
13 2
Primjer izlaza:
3
Pojašnjenje (Lociranje tačke u osnovnim geometrijskim oblicima: kružnica, pravougaonik,
kvadrat):
- Pravougaonik:
Pravougaonici su u potpunosti određeni koordinatama dvije naspramne tačke. Na slici su to A(x A,yY) i
B(xB,yB) . Iz tih koordinata možemo dobiti informacije o svim drugim osobonama pravougaonika, kao što
su dužine stranica, koordinate druge dvije tačke, površina, obim itd.
70
Sa slike je jasno da je:
xB = xC
yB = yA
xD = xA
yD = yC
a = xC - xA
b = yC - yA
(ove formule važe ukoliko su stranice pravougaonika paralelne koordinatnim osama, što je skoro uvijek i
slučaj)
Neka imamo tačku E(xE, yE). Da li ona pripada pravougaoniku ABCD određujemo iz uslova: x A ≤ xE ≤ xC i
yA ≤ yE ≤ yC.
U programskom jeziku C, to izgleda:
if( E.x>= A.x && E.x<=C.x && E.y>=A.y && E.y <= C.y)
{
//tacka pripada pravougaoniku ABCD
}
else
{
//tacka ne pripada pravougaoniku
}
-
Kvadrat
Kao što znamo, kvadrat predstavlja specijalni slučaj pravougaonika te ga je moguće zadati na potpuno isti
način. Drugi način na koji se zadaju kvadrati je pomoću koodinate jedne tačke (u našem slučaju to je tačka
A) i dužine jedne stranice, iz čega možemo dobiti sve ostale podatke. Ovaj način zadavanja je jedino moguć
ukoliko su stranice kvadrata paralelne koordinatnim osama (što je skoro uvijek i slučaj), inače bismo morali
zadati još jedan podatak o kvadratu.
xB = xA+ a
yB = yA
xD = xA
yD = yA + a
xC = xA + a
yX = yX + a
Ispitivanje pripadnosti tačke kvadratu se vrši analogno kao kod pravougaonika.
71
-
Kružnica
Kružnica je u potpunosti određen koordinatama centra i dužinom poluprečnika.
Određivanje da li tačka pripada krugu vršimo ispitivanjem udaljenosti te tačke od centra kruga. Ukolika je
ta udaljenost veća od poluprečnika, tačka ne pripada krugu. U suprotnom, pripada. Sa slike vidimo da je
d1 > r te tačka A ne pripada krugu, dok je d2≤r i pripada. Udaljenost dvije tačke računamo po formuli:
U programskom jeziku C, odgovarajući kod je:
d = sqrt ( (A.x-S.x)* (A.x-S.x) + (A.y-S.y)* (A.y-S.y) );
if (d>r)
{
// ne pripada
}
else
{
//pripada
}
Programski kod (C):
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<string.h>
<math.h>
struct tacka
{
int x;
int y;
};
struct pravougaonik
{
struct tacka donja_lijeva;
struct tacka gornja_desna;
};
int pripada_pravougaoniku(struct tacka t,struct pravougaonik p)
72
{
return (t.x>=p.donja_lijeva.x && t.y>=p.donja_lijeva.y &&t.x<=p.gornja_desna.x
&& t.y<=p.gornja_desna.y);
}
struct kvadrat
{
struct tacka donja_lijeva;
float a;
};
int pripada_kvadratu(struct tacka t,struct kvadrat k)
{
return
(t.x>=k.donja_lijeva.x
&&
t.y
t.x<=k.donja_lijeva.x+k.a && t.y <= k.donja_lijeva.y+k.a);
}
>=k.donja_lijeva.y
&&
struct krug
{
struct tacka s;
float r;
};
float udaljenost(struct tacka A, struct tacka B)
{
return sqrt ( (A.x-B.x)* (A.x-B.x) + (A.y-B.y)* (A.y-B.y) );
}
int pripada_krugu(struct tacka t, struct krug k)
{
float d = udaljenost(t,k.s);
return !(d >k.r);
}
int main()
{
int m,n,k,r;
struct pravougaonik pravougaonici[1000];
struct kvadrat kvadrati[1000];
struct krug krugovi[1000];
struct tacka tmp;
unisteni_pravougaonici[1000],unisteni_kvadrati[1000],unisteni_krugovi[1000];
int
/* nizovi za pracenje da li je objekat vec unisten - ne moze se dva puta
unistiti*/
memset(unisteni_pravougaonici,0,sizeof unisteni_pravougaonici);
memset(unisteni_kvadrati,0,sizeof unisteni_kvadrati);
memset(unisteni_krugovi,0,sizeof unisteni_krugovi);
int rj=0;
int i,j;
scanf("%d %d %d %d",&m,&n,&k,&r);
73
for(i=0;i<m;i++)
{
scanf("%d
%d
%d
%d",&pravougaonici[i].donja_lijeva.x,&pravougaonici[i].donja_lijeva.y,&pravougaonic
i[i].gornja_desna.x,&pravougaonici[i].gornja_desna.y);
}
for(i=0;i<n;i++)
{
scanf("%d
%d
%f",&kvadrati[i].donja_lijeva.x,&kvadrati[i].donja_lijeva.y,&kvadrati[i].a);
}
for(i=0;i<k;i++)
{
scanf("%d %d %f",&krugovi[i].s.x,&krugovi[i].s.y,&krugovi[i].r);
}
for(i=0;i<r;i++)
{
scanf("%d %d",&tmp.x,&tmp.y);
for(j=0;j<m;j++)
{
if(pripada_pravougaoniku(tmp,pravougaonici[j])
unisteni_pravougaonici[j])
{
unisteni_pravougaonici[j]=1;
rj++;
}
}
for(j=0;j<n;j++)
{
if(pripada_kvadratu(tmp,kvadrati[j]) && !unisteni_kvadrati[j])
{
unisteni_kvadrati[j]=1;
rj++;
}
}
}
for(j=0;j<k;j++)
{
if(pripada_krugu(tmp,krugovi[j]) && !unisteni_krugovi[j])
{
unisteni_krugovi[j] = 1;
rj++;
}
}
printf("%d",rj);
return 0;
}
74
&&
!
Zadatak 10.2. Obuhvatanje tačaka
Težina: S
Dat je niz tačaka u 2D prostoru zadatih preko koordinata x i y i broj R. Napisati program koji određuje
koordinate centra kružnice sa radijusom R takve da pokriva što veći broj tačaka.
Ulaz:
Prvi red ulaza će sadržati cijeli broj R, zatim slijedi broj n tačaka u nizu (maksimalno 100), nakon čega
slijedi niz od n parova cijelih brojeva koji predstavljaju x i y koordinate tačaka.
Izlaz:
Dva cijela broja koji predstavljaju koordinate centra kružnice sa radijusom R.
Primjer ulaza:
3
5
45
76
33
52
44
Primjer izlaza:
54
Pojašnjenje:
Ovdje ćemo primijeniti metod iscrpne pretrage (brute force) tako što ćemo definisati pravougaonik koji
obuhvata sve tačke u nizu i zatim isprobati sve koordinate unutar tog pravougaonika kao potencijalni centar
kružnice. Matematski se može dokazati da niti jedna kružnica sa centrom izvan tog pravougaonika ne može
obuhvatati više tačaka od neke od kružnica sa centrom unutar pravougaonika.
Slika 4: Pojašnjenje primjera ulaza i izlaza
75
U primjeru ulaza tražen je krug sa radijusom 3 koji obuhvata datih 5 tačaka: (4,5), (7,6), (3,3), (5,2), (4,4).
Kao što vidimo sa slike 4 tačno rješenje je (5,4) što je program i ispisao.
Programski kod (C):
#include <stdio.h>
#include <math.h>
struct tacka
{
int x;
int y;
};
struct krug
{
struct tacka s;
float r;
};
float udaljenost(struct tacka A, struct tacka B)
{
return sqrt ( (A.x-B.x)* (A.x-B.x) + (A.y-B.y)* (A.y-B.y) );
}
int pripada_krugu(struct tacka t, struct krug k)
{
float d = udaljenost(t,k.s);
return !(d >k.r);
}
int main()
{
int r,n,i,x,y,obuhvaceno,max_obuhvaceno;
struct tacka niz[100],dolje_lijevo,gore_desno,centar;
struct krug k;
scanf("%d", &r);
scanf("%d", &n);
for (i=0; i<n; i++)
scanf("%d %d", &niz[i].x, &niz[i].y);
/* Najprije odredjujemo pravougaonik koji obuhvata sve tacke u nizu,
preko tacaka dolje_lijevo i gore_desno */
dolje_lijevo=gore_desno=niz[0];
for (i=1; i<n; i++) {
if (dolje_lijevo.x > niz[i].x) dolje_lijevo.x = niz[i].x;
if (dolje_lijevo.y > niz[i].y) dolje_lijevo.y = niz[i].y;
if (gore_desno.x < niz[i].x) gore_desno.x = niz[i].x;
if (gore_desno.y < niz[i].y) gore_desno.y = niz[i].y;
}
/* Sada prolazimo kroz sve cjelobrojne vrijednosti u tom pravougaoniku */
max_obuhvaceno=0;
for (x=dolje_lijevo.x; x<=gore_desno.x; x++) {
76
for (y=dolje_lijevo.y; y<=gore_desno.y; y++) {
/* Provjeravamo koliko tacaka je obuhvaceno kruznicom sa
centrom u x,y */
obuhvaceno=0;
k.s.x=x;
k.s.y=y;
k.r=r;
for (i=0; i<n; i++) {
if (pripada_krugu(niz[i], k)) obuhvaceno++;
}
/* Da li je taj broj veci od do sada poznatog maksimuma?
*/
}
if (obuhvaceno>max_obuhvaceno) {
max_obuhvaceno = obuhvaceno;
/* Postavljamo trazeni rezultat na x,y */
centar.x = x;
centar.y = y;
}
}
}
/* Ispis rezultata */
printf ("%d %d\n", centar.x, centar.y);
return 0;
77
11. Rad sa stringovima
Zadatak 11.1. Cenzura
Težina: S
Na školskom forumu postoji spisak cenzurisanih riječi koje se ne smiju pojaviti u porukama koje pišu
učenici. Program treba potražiti svaku od riječi na spisku u poruci i, ako se riječ javlja u tekstu poruke,
zamijeniti je odgovarajućim brojem zvjezdica.
Ulaz:
Na ulazu se najprije nalazi jedan cijeli broj n (maksimalno 100) koji predstavlja broj riječi koje se cenzurišu.
Zatim slijede cenzurisane riječi u zasebnim redovima (dužina riječi je maksimalno 20 znakova, riječi ne
sadrže razmake).
Na kraju se nalazi tekst koji treba cenzurisati (maksimalne dužine 1000 znakova, može sadržavati razmake).
Tekst se završava znakom za novi red.
Izlaz:
Dati tekst pri čemu je svaka od cenzurisanih riječi zamijenjena odgovarajućim brojem zvjezdica.
Primjer ulaza:
3
cenzura
nema
direktor
U ovom tekstu nema cenzure. Mi ni ne znamo sta je cenzura. Ako ne vjerujete, pitajte direktora.
Primjer izlaza:
U ovom tekstu **** cenzure. Mi ni ne znamo sta je ******. Ako ne vjerujete, pitajte ********a.
Zadatak 11.2. Pravilan jezik
Težina: S-N
U dalekoj zemlji Prdimahovini govori se prdimahovinski jezik koji ima vrlo jednostavna leksička pravila:
- svaka riječ sastoji se od jednog ili više slogova
- svaki slog sastoji se od jednog suglasnika i jednog samoglasnika
- izuzetno, riječ može počinjati samoglasnikom nakon kojeg slijede regularni slogovi
- takođe postoje veznici a i o u.
Na osnovu ovih pravila napisati primitivan spellchecker za prdimahovinski jezik. Spellchecker treba ispisati
na izlazu sve nepravilne riječi i to onim redoslijedom kojim se javljaju u ulaznom tekstu.
Ulaz:
Tekst koji treba provjeriti. Tekst je maksimalne dužine 1000 znakova i može sadržavati razmake, takođe riječi
u tekstu mogu uključivati i velika i mala slova. Tekst se završava znakom za novi red. Sve znakove u tekstu
koji nisu slova treba posmatrati kao graničnike između riječi: npr. Ababa4pipi je ispravan tekst jer sadrži
78
riječi ababa i pipi razdvojene cifrom 4 koja nije slovo pa je prema tome graničnik izmeđju riječi.
Izlaz:
Spisak nepravilnih riječi u tekstu, pri čemu je svaka riječ ispisana u zasebnom redu.
Primjer ulaza:
Abababa cecece i dodod bababa. Ba abab cecece dodo. Gugugu-bebe dododo. Aaba dede: agge dudu
bibibi.
Primjer izlaza:
dodod
abab
Aaba
agge
Programski kod (C):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Standardna funkcija za unos stringa. vel = velicina stringa*/
void unesi(char* string, int vel)
{
int i=0;
do
{
*string++ = getchar();
i++;
if (i==vel) break;
} while (*(string-1) != '\n');
*(string-1) = '\0';
}
char ulaz[1001];
/*
sluzi za ispisivanje ulaznog stringa od indeksa i do indeksa j
*/
void ispisi(int i,int j)
{
while(i<j-1)
{
printf("%c",ulaz[i]);
i++;
}
printf("\n");
}
/*
ispituje da li je znak c slovo
*/
int slovo(char c)
79
{
return ((c>='a' && c<='z') || (c>='A' && c<='Z'));
}
/*
ispituje da li je znak c samoglasnik
*/
int samoglasnik(char c)
{
return (c=='a' || c=='A' || c=='e' || c=='E' || c=='i' || c=='I' || c=='o' ||
c=='O' || c=='u' || c=='U');
}
/*
ispituje da li je znak c veznik definisan u zadatku
*/
int veznik(char c)
{
return (samoglasnik(c) && c!='e' && c!='E');
}
/*
funkcija analizira rijec, ispisuje je ukoliko nije pravilna, vraca kao rezultat
indeks od kojeg pocinje sljedeca rijec
*/
int analiziraj_rijec(int i)
{
int j=i,k=i;
/*
petlja koja odredjuje do kojeg indeksa ulaza je trenutna rijec
*/
while(slovo(ulaz[j++]));
/*
ukoliko je rijec duga jedan znak, ispituje da li je taj znak veznik
*/
if(!slovo(ulaz[i+1]))
{
if(!veznik(ulaz[i]))
{
printf("%c\n",ulaz[i]);
return j;
}
}
/*
ukoliko je prvi znak samoglasnik, preskacemo ga
*/
if(samoglasnik(ulaz[i]))
{
k++;
}
/*
preostali broj znakova mora biti paran da bi rijec bila korektna
*/
if((j-k-1)%2!=0)
{
ispisi(i,j);
80
return j;
}
/*
ispituje da li ostatak rijeci ima pravilnu strukturu
*/
for(;k<j-1;k+=2)
{
if(samoglasnik(ulaz[k]) || !samoglasnik(ulaz[k+1]))
{
ispisi(i,j);
return j;
}
}
return j;
}
int main()
{
int i=0;
int len_ulaz;
unesi(ulaz,1000);
len_ulaz = strlen(ulaz);
i=0;
while(i<len_ulaz)
{
if(slovo(ulaz[i]))
i = analiziraj_rijec(i);
else i++;
}
return 0;
}
Zadatak 11.3. Prijemni ispit
Težina: S
Nakon prijemnog ispita na nekom fakultetu napravljena su dva spiska: kandidati koji su primljeni na
fakultet i kandidati koji nisu primljeni. Ali neposredno prije objavljivanja spiska otkriveno je da je
napravljena greška! Naime neki kandidati su se javili na oba spiska. Napišite program koji će pomoći
osoblju fakulteta da popravi ovu grešku tako što će ispisati imena svih takvih kandidata kako bi se
provjerilo da li su isti primljeni ili ne. Redoslijed ispisanih imena treba biti isti kao u prvom spisku.
Ulaz:
Cijeli broj koji označava broj primljenih kandidata.
Spisak primljenih kandidata u obliku "Ime Prezime", pri čemu je svaki kandidat u zasebnom redu.
Cijeli broj koji označava broj odbijenih kandidata.
Spisak odbijenih kandidata u obliku "Ime Prezime", pri čemu je svaki kandidat u zasebnom redu.
U riječima Ime i Prezime ne mogu se javiti razmaci. Maksimalna dužina imena i prezimena je po 20
81
znakova.
Izlaz:
Imena i prezimena kandidata koji se nalaze na oba spiska u obliku "Ime Prezime", pri čemu je svaki
kandidat u zasebnom redu.
Zadatak 11.4. Spellchecker
Težina: S-N
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2007. godine
Vjerovatno ste upoznati sa tzv. spell checker programima. Svaki od tih programa sadrži fajl sa određenim
brojem riječi koje zajedno čine rječnik. Služi se tim skupom riječi, program analizira dati tekst i u njemu
pronalazi riječi koje se ne nalaze u njegovom rječniku. Na taj način moguće je pronaći riječi koje nisu
pravilno spelovane.
Vaš zadatak je da napravite program koji će:
a)
koristeći dati rječnik analizirati dati tekst u određenom jeziku i nepoznate riječi ispisati na ekran.
Nakon toga treba ispisati i broj nepoznatih riječi.
b)
za nepoznate riječi pokušati utvrditi ispravnu riječ i ponuditi korisniku ispravku teksta
zamjenom pogrešno napisane riječi sa odabranom riječi. Lista mogućih ispravnih riječi je isti korijen
riječi u odnosu na nepoznatu riječ. Korijen riječi je dužina riječi minus jedan znak.
Ulaz:
Na ulazu se najprije nalazi cijeli broj N (maksimalno 1000) koji predstavlja broj riječi u rječniku.
Zatim su date riječi, po jedna u svakom redu (unutar riječi se ne nalazi razmak).
Nakon toga je dat tekst koji treba prekontrolisati u formi niza riječi zaključno sa novim redom (tekst može
sadržavati znak razmak).
Izlaz:
Na izlazu treba najprije ispisati broj riječi koje nisu prepoznate u rječniku, a zatim u svakom redu ispisati
nepoznatu riječ, znak razmak, a zatim eventualnu zamjensku riječ koja je pronađena u rječniku (ili ako nije
pronađena riječ ne treba ispisati ništa). Ako u rječniku postoji više riječi koje zadovoljavaju kriterij (sva
slova jednaka osim zadnjeg), treba kao zamjensku ponuditi onu koja je prva navedena u rječniku.
Zadatak 11.5. T9
Težina: S
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2013. godine
Napisati program koji pruža funkcionalnost telefonskog imenika za neki vrlo jednostavan mobilni telefon.
Program treba omogućiti unos brojeva u telefonski imenik, pri čemu su za svaki kontakt u imeniku
definisani podaci: ime, prezime i broj telefona.
Nakon toga treba omogućiti i pretragu imenika koristeći T9 sistem. Naime, ovaj telefon posjeduje samo
numeričke tipke 0-9. U ovakvim situacijama na mobitelima se koristi tzv. T9 kodiranje pri čemu svaka
82
numerička tipka može odgovarati većem broju slova. Značenja tipaka po T9 kodiranju su:
0 – cifra 0 i razmak
1 – cifra 1
2 – cifra 2, slova A, B i C
3 – cifra 3, slova D, E i F
4 – cifra 4, slova G, H i I
5 – cifra 5, slova J, K i L
6 – cifra 6, slova M, N i O
7 – cifra 7, slova P, Q, R i S
8 – cifra 8, slova T, U i V
9 – cifra 9, slova W, X, Y i Z
Prilikom pretrage treba pretraživati sva tri polja imenika, dakle ime, prezime i broj telefona.
Ulaz:
U prvom redu ulaza nalazi se broj N koji predstavlja broj zapisa (slogova) u telefonskom imeniku. Nakon
toga slijede zapisi imenika. Svaki zapis može biti u jednom od dva formata:
ime:broj
ime:prezime:broj
U prvom slučaju prezime nije definisano i potrebno je da ostane prazno, odnosno neće se pretraživati.
Elementi zapisa razdvojeni su znakom “dvotačka”. Polja ime i prezime mogu se sastojati od velikih i malih
slova, cifara, te znaka razmak. Maksimalna dužina imena i prezimena je po 50 znakova. Polje broj sastoji se
isključivo od cifara 0-9, i to maksimalno 15 cifara.
Nakon N zapisa, u posljednjem redu biće dat string pretrage. String se sastoji od proizvoljnog broja cifara
0-9 koje treba tumačiti po T9 kodiranju datom iznad. Logično da pretraga duža od 50 znakova nema smisla
pa ne treba biti ni dozvoljena. Pretražuju se sva tri polja imenika, dakle ime, prezime i broj, pri čemu se
traženi string može nalaziti i u sredini nekog od polja imenika.
Pretpostavite da je ulaz uvijek ispravno formatiran.
Izlaz:
Potrebno je ispisati sve zapise iz imenika koji odgovaraju datom stringu pretrage. Format zapisa treba biti
isti kao na ulazu, dakle ime:prezime:broj ili ime:broj. Zapisi trebaju biti ispisani istim redom kojim su i
uneseni. U slučaju da nijedan zapis ne odgovara datoj pretrazi, treba ispisati poruku “NEMA
REZULTATA”.
Primjer ulaza:
3
Sulejman:Mehic:123456789
Ana Marija:Peric:09876543210
Mijo:Mijic:10301932062
206
Primjer izlaza:
Ana Marija:Peric:09876543210
Mijo:Mijic:10301932062
Objašnjenje:
83
- Pretraga 206 nalazi se u imenu “Ana Marija” pri čemu broj 2 po T9 kodiranju odgovara slovu A, broj
0 razmaku, a broj 6 slovu M (“Ana Marija”)
- U drugom slučaju cifre 206 su dio telefonskog broja: “10301932062”.
84
12. Veliki broj
Zadaci na informatičkoj olimpijadi često zahtijevaju rad sa brojevima toliko velikim da se oni ne mogu
pohraniti u tipove podataka koje podržavaju programski jezici C, C++ i Pascal (barem ne precizno). Stoga
se često zahtijeva da razvijete posebnu programsku klasu ili biblioteku za rad sa ovakvim brojevima, pri
čemu je broj predstavljen nizom (cifara). Matematičke operacije nad ovakvim brojevima su takođe posebna
problematika.
Zadatak 12.1. Zbir i proizvod dva velika broja
Težina: N
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2010. godine
Na ulazu se nalaze se dva cijela broja dužine maksimalno 100 cifara. Drugim riječima, ovi brojevi mogu biti
dosta veći od najvećeg broja koji se može držati u cjelobrojnom tipu podataka nekog programskog jezika.
Svaki broj se nalazi u zasebnom redu.
Napisati program koji izračunava i ispisuje sumu i proizvod ova dva broja. Suma i proizvod također trebaju
biti u zasebnim redovima.
Ulaz:
Dvije linije koje predstavljaju dva proizvoljno velika cijela broja.
Izlaz:
Dvije linije koje predstavljaju sumu i proizvod ova dva broja.
Primjer ulaza:
10000000000000000000
20000000000000000000
Primjer izlaza:
30000000000000000000
200000000000000000000000000000000000000
Programski kod (C):
#include <stdio.h>
/* Sljedece funkcije rade tacno samo za pozitivne brojeve! */
/* Pomocna funkcija za ispis velikog broja na ekran */
void ispisiNiz(int niz[], int vel)
{
int i;
for (i=0; i<vel; i++)
printf("%d", niz[i]);
printf("\n");
}
/* Funkcija prima dva niza br1 i br2, njihove velicine velbr1 i velbr2, te
treci niz rez u koji treba upisati rezultat.
Vraca broj elemenata u nizu rez.
85
Pri tome br1 uvijek mora biti veci od br2! */
int sumaBrojeva(int br1[], int velbr1, int br2[], int velbr2, int rez[])
{
/* Racunanje zbira */
int velrez = velbr1+1;
int razlika_velicina = velbr1 - velbr2;
int prenos = 0;
int i;
for (i = velbr1-1; i>=0; i--) {
rez[i+1] = br1[i] + prenos;
if (i>=razlika_velicina) rez[i+1] += br2[i-razlika_velicina];
prenos = rez[i+1]/10;
rez[i+1] = rez[i+1]%10;
}
if (prenos==1) rezultat[0]=1;
else {
/* Brisemo vodecu nulu iz niza (iako ne smeta) */
for (i=0; i<velbr1; i++)
rez[i]=rez[i+1];
velrez--;
}
return velrez;
}
/* Funkcija prima niz, velicinu niza vel i neki broj,
racuna proizvod niza brojem i upisuje u niz rez.
Funkcija vraca velicinu niza rez. */
int proizvodNizaBrojem(int niz[], int vel, int broj, int rez[])
{
int velrez = vel+1;
int prenos = 0;
int i;
for (i=vel; i>0;
rez[i] =
prenos =
rez[i] =
}
}
i--) {
v[i-1]*broj + prenos;
rez[i]/10;
rez[i]%10;
if (prenos>0) rez[0]=prenos;
else {
/* Brisemo vodecu nulu iz niza (iako ne smeta) */
for (i=0; i<vel; i++)
rez[i]=rez[i+1];
velrez--;
}
return velrez;
/* Funkcija prima dva niza br1 i br2, njihove velicine velbr1 i velbr2, te
treci niz rez u koji treba upisati rezultat.
Vraca broj elemenata u nizu rez. */
int proizvodBrojeva(int br1[], int velbr1, int br2[], int velbr2, int rez[])
{
int medjurezultati[velbr2][velbr1+velbr2];
86
int i,j;
/* Izracunavamo medjurezultate */
for (i=0; i<velbr2; i++) {
/* Mnozimo br1 i-tim clanom niza br2 i smjestamo u niz medjurezultata
*/
int duzina = proizvodVektoraBrojem(br1, velbr1, br2[i],
medjurezultati[i]);
/* Pomjeramo medjurezultat za i mjesta udesno */
for (j=duzina-1; j>=0; j--)
medjurezultati[i][j+i] = medjurezultati[i][j];
/* Upraznjeni prostor na pocetku popunjavamo nulama */
for (j=0; j<i; j++)
medjurezultat[i][j] = 0;
}
/* Nuliramo niz rez */
for (i=0; i<velbr2; i++)
rez[i] = 0;
/* Sumiramo medjurezultate u niz rez */
int novaduzina = velbr2;
for (int i=0; i<velbr2; i++) {
/* Sabiramo i-ti medjurezultat sa nizom rez i smjestamo u tmpniz */
int tmpniz[novaduzina+1];
novaduzina = sumaBrojeva(rez, novaduzina, medjurezultati[i], velbr2,
tmpniz);
/* Vracamo iz niza tmpniz u niz rez */
for (int j=0; j<novaduzina; j++)
rez[j] = tmpniz[j];
}
}
return novaduzina;
int main()
{
/* Nizovi cifara */
int br1[100], br2[100], suma[200], proizvod[200];
/* Brojevi cifara u nizovima */
int velbr1, velbr2, velsuma, velproizvod;
int i;
/* Brojeve moramo citati znak po znak */
char znak;
/* Petlja za citanje prvog broja */
velbr1=0;
znak = getchar();
while (znak != '\n') {
br1[velbr1] = znak - '0';
velbr1++;
87
}
znak = getchar();
/* Petlja za citanje drugog broja */
velbr2=0;
znak = getchar();
while (znak != '\n') {
br2[velbr2] = znak - '0';
velbr2++;
znak = getchar();
}
/* Funkcije za sumu i prozivod */
velsuma = sumaBrojeva(br1, velbr1, br2, velbr2, suma);
velproizvod = proizvodBrojeva(br1, velbr1, br2, velbr2, proizvod);
/* Ispis sume i proizvoda */
ispisiNiz(suma, velsuma);
ispisiNiz(proizvod, velproizvod);
}
return 0;
Zadatak 12.2. Najveći broj
Težina: N
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2012. godine
Učesnik na kantonalnom takmičenju iz informatike je uspješno riješio prethodni zadatak, i taman kada se
spremao da pošalje rješenje – nestalo je struje! Struja je ubrzo ponovo došla, ali mnogi fajlovi na disku su
nepovratno oštećeni, uključujući i rješenje zadatka. Kada je otvorio zadatak, takmičar je shvatio da su na
slučajna mjesta u kodu ubačeni neki slučajni znakovi.
Ostatak koda se da rekonstruisati, no ono što je takmičaru bitno su deklaracije varijabli. Takmičar zna da
sve varijable u njegovom kodu imaju imena sastavljena od tačno tri mala slova engleskog alfabeta i da su
sve njihove vrijednosti cjelobrojne i pozitivne. Osim toga, program je rađen u nekom čudnom
programskom jeziku koji dozvoljava cjelobrojne varijable sa proizvoljnim brojem cifara.
Napišite program koji će pomoći takmičaru da pronađe varijablu čija je vrijednost najveća. Takmičaru je
jasno da je upisom slučajnih znakova na slučajna mjesta izgubljen određen broj varijabli a da su nekim od
njih promijenjene vrijednosti. Ipak, spreman je na taj rizik, i od vas očekuje da u obzir uzmete samo ono
što zaista izgleda kao varijabla. Konkretno, isključivo dijelovi koda oblika
CCC=NNNNN...NN;
pri čemu znak C predstavlja malo slovo engleskog alfabeta, a znak N predstavlja cifru, smatraju se
varijablom. Vodite računa da ovi blokovi mogu biti okruženi proizvoljnim znakovima (brojevima, slovima,
itd.) a mogu se nalaziti i na početku ili kraju koda. Posebno treba naglasiti da se na kraju deklaracija
varijable nalazi znak tačka-zarez, tj. ukoliko tog znaka nema u pitanju nije validna varijabla. Primijetite i da
se validnim varijablama smatraju samo one čiji se ime obavezno sastoji isključivo od tri mala slova
engleskog alfabeta.
88
Ulazni podaci
Ulaz se sastoji od jedne linije teksta proizvoljne dužine (maksimalno 20.000.000 znakova). Možete smatrati
da nijedna varijabla neće imati više od 10000 cifara.
Izlazni podaci
U slučaju da se u primljenom kodu ne nalazi niti jedna validna deklaracija varijable, program treba ispisati
poruku NEMA NIJEDNA VARIJABLA nakon koje treba ispisati znak za novi red.
U suprotnom, potrebno je ispisati deklaraciju one varijable čija je vrijednost najveća, u istom obliku kako je
navedena u kodu:
CCC=NNNNN...NN;
nakon čega treba ispisati znak za novi red. Ukoliko ima više varijabli sa istom (najvećom) vrijednošću,
potrebno je ispisati prvu.
Ograničenja
Vaš program ne smije raditi duže od 0.1s i ne smije koristiti više od 16 MiB memorije. 80% testnih primjera
neće koristiti ulazni string duži od 10000 znakova.
Primjer
Ulazni podaci
aaaaa=123123;bbb=45645;
Izlazni podaci
aaa=123123;
Programski kod (C):
#include <stdio.h>
int main() {
char varijabla[4], vrijednost[10000];
char najv_varijabla[4], najv_vrijednost[10000];
int varijabla_index=0, vrijednost_index=0, najv_vrijednost_index=0;
enum { U_VARIJABLI, U_VRIJEDNOSTI } status = U_VARIJABLI;
char c;
int i;
najv_varijabla[0] = '\0'; // Ne postoji trenutno najveca
varijabla[3] = '\0'; // Da se moze ispisati
while ((c = fgetc(stdin)) != '\n') {
if (status == U_VARIJABLI) {
if (c == '=') {
if (varijabla_index == 3) {
status = U_VRIJEDNOSTI;
varijabla_index = 0;
continue;
} else {
varijabla_index = 0;
}
89
}
else if (c >= 'a' && c<= 'z') {
if (varijabla_index < 3) {
varijabla[varijabla_index++] = c;
} else {
varijabla[0] = varijabla[1];
varijabla[1] = varijabla[2];
varijabla[2] = c;
varijabla_index = 3;
}
}
else
varijabla_index = 0;
}
if (status == U_VRIJEDNOSTI) {
if (c>='0' && c<='9') {
vrijednost[vrijednost_index++] = c;
} else if (c == ';' && vrijednost_index>0) {
// Da li je tekuca najveca do sada?
int ova_je_najveca = 1;
if (najv_varijabla[0] != '\0') {
if (vrijednost_index < najv_vrijednost_index)
ova_je_najveca = 0;
else if (vrijednost_index == najv_vrijednost_index) {
ova_je_najveca = 0;
for (i=0; i<vrijednost_index; i++) {
if (vrijednost[i]>najv_vrijednost[i]) {
ova_je_najveca = 1;
break;
}
if (vrijednost[i]<najv_vrijednost[i]) {
ova_je_najveca = 0;
break;
}
}
}
} // if (najv_varijabla[0])
// Kopiramo tekucu u najvecu
if (ova_je_najveca) {
najv_varijabla[0] = varijabla[0];
najv_varijabla[1] = varijabla[1];
najv_varijabla[2] = varijabla[2];
for (i=0; i<vrijednost_index; i++)
najv_vrijednost[i] = vrijednost[i];
najv_vrijednost_index = vrijednost_index;
}
vrijednost_index = 0;
status = U_VARIJABLI;
} // else if (c == ';' && vrijednost_index>0)
else {
vrijednost_index = 0;
status = U_VARIJABLI;
}
}
}
najv_varijabla[3]='\0';
90
if (najv_varijabla[0] == '\0')
printf("NEMA NIJEDNA VARIJABLA\n");
else
printf("%s=%s;\n", najv_varijabla, najv_vrijednost);
}
return 0;
91
13. Grafovi i stabla
Zadatak 13.1. Presjedanje
Težina: N
Junak ove priče N.N. često mora da putuje. Kao najudobniji vid transporta koristi isključivo avionski.
Obzirom da je upoznat sa podatkom da se većina nesreća dešava prilikom uzlijetanja i slijetanja on svoju
rutu uvijek bira isključivo prema broju presjedanja koja mora da napravi. Njemu nije bitno koliko dugo će
putovati, spreman je duže biti u avionu da bi izbjegao dodatna slijetanja i uzlijetanja. Ni cijena nije bitna,
troškove u svakom slučaju snosi firma. Obzirom da sve putničke agencije imaju softver koji nalazi najkraće
ili najjefitnije rute, N.N. je prisiljen da uvijek svoju rutu bira ručno. Pomozite mu i napišite program koji će
mu pomoći u izboru rute.
Ulazni podaci
U ulaznoj datoteci “presjedanje.in” na prvoj liniji nalaze se dva cijela broja N (0 < N ≤ 10.000) i M (0 < M
≤ 100.000). Broj N je ukupan broj gradova, dok je broj M ukupan broj letova koji postoje između ovih
gradova. Gradovi su numerisani brojevima od 0 do N-1. U svakoj od narednih M linija nalaze se po dva
cijela broja ai i bi (0 < ai, bi < N) međusobno razdvojena razmakom. Značenje je da postoje letovi između
gradova ai i bi i to u oba smjera. Na posljednjoj liniji ulazne datoteke nalaze se dva cijela broja x i y (0 < x, y
< N) međusobno razdvojena razmakom. N.N. želi da putuje iz grada x u grad y.
Izlazni podaci
U izlaznu dateteku “presjedanje.out” trebate na prvoj i jedinoj liniji trebate ispisati jedan cio broj koji
predstavlja dužinu rute sa najmanjim brojem presjedanja između od grada x do grada y. Ukoliko ne postoji
tražena ruta, ispišite tekst “Trazena ruta ne postoji.”.
Primjer 1
presjedanje.in
5
0
1
2
3
0
1
0
6
1
2
3
4
2
3
4
presjedanje.out
3
Primjer 2
presjedanje.in
4
0
2
0
2
1
3
2
presjedanje.out
Trazena ruta ne postoji.
92
Rješenje
Najprirodniji način rješavanja ovog i sličnih problema je primjenom grafova, odnosno algoritama za
grafove. Neformalno, pod grafom smatramo geometrijsku strukturu koja se sastoji od skupine objekata
koji se nazivaju čvorovi grafa i koji su međusobno povezani vezama koje se nazivaju grane grafa. U ovom
zadatku čvorovi grafa bi bili gradovi, a grane grafa bi bili letovi između gradova. Postoje usmjereni i
neusmjereni grafovi. Kod usmjerenih grafova moguće je da postoji grana od čvora a do čvora b, a da
istovremeno ne postoji grana od čvora b do čvora a. Postoje grafovi kod kojih se uz svaku granu veže i
određeni broj koji se naziva težina te grane. Obzirom da nama nije od interesa dužina niti cijena određenih
letova koji bi se mogli modelirati kao težine određenih grana, mi ćemo koristiti tzv. netežinski graf. Jedan
od načina predstavljanja grafova u memoriji je lista susjedstva. Najprije sve čvorove grafa numerišemo
brojevima od 0 do N-1, gdje je N ukupan broj čvorova u grafu. U ovom zadatku ovakva numeracije je
osigurana samom postavkom. Sada se uz svaki čvor x veže niz cijelih brojeva između 0 i N-1. Svaki element
tog niza yi označava da postoji grana između čvora x i čvora y i. Na primjer, sljedeći skup globalnih varijabli
u potpunosti opisuje graf koji ćemo koristiti u rješenju ovog zadatka.
const int max_n = 10000;
int lista_susjedstva[max_n][max_n];
int lista_susjedstva_duzina[max_n] = {0};
int n;
max_n je maksimalni mogući broj čvorova (dato postavkom zadatka). Koristimo nizove i matrice čije su
dimenzije unaprijed određene kako bismo izbjegli dinamičku alokaciju memorije. n je broj čvorova koji
stvarno postoje u grafu (n ≤ max_n). lista_susjedstva je matrica dimnezija max_n×max_n od koje se
ustvari koristi najviše n×n elemenata. Svaki od nizova lista_susjedstva[i] (0 ≤ i < n) predstavlja niz čvorova
koji su povezani sa čvorom i. Dužina ovih nizova upisana je u nizu lista_susjedstva_duzina, tako da je
lista_susjedstva_duzina[i] dužina niza lista_susjedstva[i]. U najgorem slučaju jedan čvor može imati granu
do svih ostalih čvorovima i do samog sobe (iako grana do samog sebe u ovom zadatku nema nekog smisla).
Prema tome, nijedan od ovih nizova ne može biti duži od n elemenata. Iz ovoga je jasno zašto su
maksimalne dimenzije matrice lista_susjedstva n×n odnosno max_n×max_n.
Dodavanje grane u graf je jednostavna operacija i sastoji se od dodavanja elemenata u listu susjedstva.
Obzirom da je postavka zadatka takva da nameće korištenje neusmjerenog grafa (letovi su uvijek u oba
smjera), to je prilikom dodavanja grane između čvorova a i b neophodno dati i granu između čvorova b i a.
void dodaj_granu(int a, int b)
{
lista_susjedstva[a][lista_susjedstva_duzina[a]] = b;
++lista_susjedstva_duzina[a];
lista_susjedstva[b][lista_susjedstva_duzina[b]] = a;
++lista_susjedstva_duzina[b];
}
Među osnovne algoritme teorije grafova spadaju algoritmi kretanja kroz graf. Postoje dva bazna algoritma
za kretanje kroz graf: pretraga grafa po širini (breadth-first search, BFS) i pretraga grafa po dubini (depthfirst search, DFS). Kod BFS pretrage, krećemo od proizvoljnog čvora grafa, koji označimo sa 0. Sve
njegove susjede označimo sa 1. U sljedećoj iteraciji, krećemo od čvorova koji su označeni sa 1, njihove
susjede koji nisu bili ranije označeni označimo sa 2. Ovaj postupak nastavljamo dalje tako da u i-toj iteraciji
sve susjede čvorova označenih sa i koji nisu bili ranije označeni označimo sa i+1. Postupak se prekida kada
dalje obilježavanje više nije moguće. Primijetimo da oznake dodijeljene pojedinim čvorovima ujedno
93
predstavljaju najkraće udaljenosti pojedinih čvorova od startnog čvora, tako da je ovaj postupak pogodan u
slučaju da treba naći najkraći put od početnog čvora do nekog zadanog čvora (za netežinski graf). Slijedi
jedna implementacija BFS pretrage u programskom jeziku C++. Napomenimo da postoje značajno
efikasnije implementacije ali one uključuju upotrebu strukture podataka koja se naziva red (engl. queue).
int najkraca_ruta(int pocetak, int kraj)
{
//Niz u kojem su upisane oznake čvorova za BFS pretragu
int oznaka[max_n];
//Inicijalno svi čvorovi imaju oznaku -1, tj. nisu označeni
for (int i = 0; i < n; ++i)
oznaka[i] = -1;
//Čvor od kojeg se počinje BFS pretraga ima oznaku 0
oznaka[pocetak] = 0;
//Varijabla za oznaku koja se trenutno obrađuje
int trenutna_oznaka = 0;
/*Varijabla koja signalizira da li je posjećen barem jedan novi čvor u
*prethodnoj iteraciji. Inicijalno je postavljena na true da bi se osigurao
*prvi ulazak u while petlju*/
bool posjecen_barem_jedan_cvor = true;
while (posjecen_barem_jedan_cvor)
{
posjecen_barem_jedan_cvor = false;
/*Nalazimo čvorove čija je trenutna oznaka ona koja se obrađuje u
*ovoj iteraciji*/
for (int trenutni_cvor = 0; trenutni_cvor < n; ++trenutni_cvor)
if (oznaka[trenutni_cvor] == trenutna_oznaka)
/*Sve prethodno neposjećene čvorove iz liste susjedstva ovog
*čvora označavamo sa sljedećom oznakom*/
for (int j = 0; j < lista_susjedstva_duzina[trenutni_cvor]; ++j)
{
int susjedni_cvor = lista_susjedstva[trenutni_cvor][j];
if (oznaka[susjedni_cvor] == -1)
{
oznaka[susjedni_cvor] = trenutna_oznaka + 1;
posjecen_barem_jedan_cvor = true;
}
}
//U narednoj iteraciji treba obraditi sljedeću oznaku
++trenutna_oznaka;
}
/*Kako je kod netežinskih grafova oznaka u BFS pretrazi ustvari najkrača
*udaljenost od početnog čvora vračamo oznaku čvora kraj. Ako je njegova
*oznaka -1, onda čvor nije bio nikako posjećen, pa ne postoji ruta od
*čvora pocetak do čvora kraj*/
return oznaka[kraj];
}
Implementacija ostatka programa koja slijedi sastoji se od učitavanja podataka iz ulazne datoteke u
specificiranom formatu, poziva napisane BFS pretrage i ispisa dobijenih rezultata u nevedenom formatu.
int main()
{
FILE* ulazna_datoteka = fopen("presjedanje.in", "r");
FILE* izlazna_datoteka = fopen("presjedanje.out", "w");
94
}
//Učitavanje podataka iz ulazne datoteke
int m;
fscanf(ulazna_datoteka, "%d %d", &n, &m);
for (int i = 0; i < m; ++i)
{
int a, b;
fscanf(ulazna_datoteka, "%d %d", &a, &b);
dodaj_granu(a, b);
}
int x, y;
fscanf(ulazna_datoteka, "%d %d", &x, &y);
//Najkrača ruta se određuje primjenom BFS pretrage
int udaljenost = najkraca_ruta(x, y);
//Ispis rezultata u izlaznu datoteku
if (udaljenost == -1)
fprintf(izlazna_datoteka, "Trazena ruta ne postoji.\n");
else
fprintf(izlazna_datoteka, "%d\n", udaljenost);
fclose(ulazna_datoteka);
fclose(izlazna_datoteka);
return 0;
Zadatak 13.2. BIHAMK
Težina: N
Uslijed obimnih sniježnih padavina BIHAMK-ov pozivni centar je bio zatrpan pozivima. Najčešće pitanje
je bilo da li su prohodni putevi između dva grada. Vaš zadatak je da napišete program koji će pomoći
operaterima u pozivnom centru da brzo i jednostavno odgovore na takve upite.
Ulazni podaci
U ulaznoj datoteci “bihamk.in” na prvoj liniji nalazi se dva cijela broja N (0 < N ≤ 1.000) i M (0 < M ≤
100.000). Broj N je ukupan broj gradova, dok je broj M ukupan puteva koje BIHAMK ima u svojoj bazi. U
svakoj od narednih M linija nalaze se po dva naziva gradova ai i bi međusobno razdvojena razmakom
između kojih postoji put. Možete smatrati da se nazivi gradova sastoje isključivo od malih i velikih slova i
znakova “_” koji se koriste onda kada bi se inače koristio razmak u nazivu grada – npr. Banja_Luka.
Naziv niti jednog grada neće biti duži od 30 karaktera. Slijedi najviše 10.000 linija. Svaka linija je u jednom
od sljedeća tri formata:
• “N Prvi_grad Drugi_grad” - Označava da put između gradova Prvi_grad i Drugi_grad
više nije prohodan uslijed sniježnih padavina;
• “P Prvi_grad Drugi_grad” - Označava da je put između gradova Prvi_grad i Drugi_grad
očišćen i opet prohodan;
• “? Prvi_grad Drugi_grad” - Predstavlja upit da li je put između gradova Prvi_grad i
Drugi_grad prohodan;
• “X” - Naredba za prekid rada programa.
Ove podatke trebate obrađivati sekvencijalno, odnosno odgovor na upit da li je put između dva grada
prohodan treba dati samo na osnovu podataka koji su u datoteci nalaze prije samog upita.
Izlazni podaci:
U izlaznu dateteku “bihamk.out” trebate ispisati po jednu liniju za svaki postavljeni upit iz ulazne
95
datoteke, a koja predstavlja odgovor na odgovarajući upit iz ulazne datoteke. Ukoliko je put prohodan,
treba ispisati tekst u obliku “Put izmedju gradova Prvi_grad i Drugi_grad je
prohodan.”. Analogno ukoliko put nije prohodan, treba ispisati tekst u obliku “Put izmedju
gradova Prvi_grad i Drugi_grad nije prohodan.”.
Primjer:
bihamk.in
6 6
Sarajevo Kiseljak
Sarajevo Visoko
Visoko Kakanj
Kakanj Zenica
Kiseljak Zenica
Zenica Travnik
? Sarajevo Travnik
N Visoko Kakanj
? Sarajevo Kakanj
N Kiseljak Zenica
? Sarajevo Travnik
P Visoko Kakanj
? Travnik Sarajevo
X
bihamk.out
Put
Put
Put
Put
izmedju
izmedju
izmedju
izmedju
gradova
gradova
gradova
gradova
Sarajevo i Travnik je prohodan.
Sarajevo i Kakanj je prohodan.
Sarajevo i Travnik nije prohodan.
Travnik i Sarajevo je prohodan.
Rješenje
Prirodan način modeliranja ovog problema je korištenjem grafova. Jedan od načina predstavljanja grafova u
memoriji je matrica susjedstva. Najprije sve čvorove grafa numerišimo brojevima od 0 do N-1, gdje je N
ukupan broj čvorova u grafu. Čvorovi u ovom zadatku označeni su nizovima karaktera (stringovima).
Slijedi implementacija funkcije koja osigurava jednoznačno preslikavanje naziva gradova na brojeve od 0 do
N-1.
int numerisi_grad(char naziv_grada[])
{
/*Statičke varijable (čuvaju vrijednost između poziva funckije) kojima
*memorišemo preslikavanje*/
static char numeracija_gradova[max_n][31];
static char numerisano_gradova = 0;
//Prvo pretražimo listu već numerisanih gradova.
int i;
for (i = 0; i < numerisano_gradova; ++i)
if (strcmp(naziv_grada, numeracija_gradova[i]) == 0)
/*Ukoliko grad sa traženim nazivom postoji varijabla i će imati
*vrijednost indeksa niza gdje je smješten naziv numerisanog niza*/
break;
/*Ukoliko grad sa traženim nazivom ne postoji, varijabla i će imati
*vrijednost ukupnog broja do tada memorisanih gradova (nakon što se završe
*sve iteracije gornje for petlje*/
if (i == numerisano_gradova)
{
//Ukoliko grad prije nije zapamćen, uradimo to sada
96
strcpy(numeracija_gradova[i], naziv_grada);
++numerisano_gradova;
}
/*Indeks niza gdje je memorisan naziv grada predstavlja jedinstveni cijeli
*broj na koji se taj naziv preslikava*/
return i;
}
Matrica susjedstva A je matrica koja se sastoji od jedinica i nula dimenzije N×N. Ako je element aij matrice
A jedinica, tada postoji grana od čvora i do čvora j. Suprotno, ako je element aij matrice A nula, tada ne
postoji grana od čvora i do čvora j. Skup sljedećih globalnih varijabli je dovoljan za potpuni opis korištenog
grafa.
const int max_n = 1000;
int n;
bool matrica_susjedstva[max_n][max_n];
Prednost predstavljanja grafa matricom susjedstva u odnosu na listu susjedstva ogleda se uglavnom u
efikasnom uklanjanju već postojećih grana i efikasnom provjerom da li postoji grana između dva čvora.
Ova prva osobina daje matrici susjedstva prednost za ovaj zadatak zbog potrebe da se određene grane grafa
brišu (kada odgovarajući put postane neprohodan). Sa druge strane, iz liste susjedstva može se efikasnije
dobiti lista svih čvorova sa kojim određeni čvor dijeli granu, što listu susjedstva čini efikasnijim izborom za
(između ostalog) algoritme kretanja kroz graf. Ovo listi susjedstva također daje prednost za ovaj zadatak
zbog potrebe korištenja nekog od algoritama za kretanja kroz graf da bi se provjerilo da moguće putovati
između dva grada. Optimalan izbor strukture za predstavljanje grafa u memoriji bi uključivao kombinaciju
liste susjedstva sa dodatnom oznakom da li je određeni put između dva grada prohodan. Ipak, zbog
jednostavnosti i za potrebe ilustracije korištenja matrice susjedstva u ovom rješenju je korištena matrice
susjedstva kao način predstavljanja grafova u memoriji.
Dodavanje grane u graf i brisanje grane iz grafa su sada vrlo jednostavne operacije. Slijede njihove
implementacije. Napomenimo da opet koristimo neusmjereni graf.
//Funkcija za dodavanje grane u neusmjereni graf
void dodaj_granu(int a, int b)
{
matrica_susjedstva[a][b] = true;
matrica_susjedstva[b][a] = true;
}
//Funkcija za brisanje grane
void obrisi_granu(int a, int
{
matrica_susjedstva[a][b]
matrica_susjedstva[b][a]
}
iz neusmjerenog grafa
b)
= false;
= false;
Algoritmi kretanja kroz graf se mogu koristiti da bi se provjerilo da li postoji način da se dođe od čvora a
do čvora b. Kod DFS pretrage krećemo od proizvoljnog čvora grafa, koji označavamo sa 0. Od tog čvora
se krećemo dalje ka proizvoljnom čvoru, koji označavamo oznakom 1. U svakom koraku, trudimo se da
nastavljamo dalje prema bilo kojem neoznačenom čvoru, pri čemu svaki čvor u koji smo došli obilježavamo
sa oznakom koja je za jedinicu veća od oznake čvora iz kojeg smo krenuli. Postupak nastavljamo sve dok ne
97
dođemo do čvora iz kojeg dalje ne vodi niti jedna grana ka nekom neoznačenom čvoru. U tom slučaju,
vraćamo se nazad duž puta kojim smo došli do prvog čvora iz kojeg postoji grana koja vodi ka nekom
neoznačenom čvoru. Tada nastavljamo duž te grane, dok ponovo ne dođemo do čvora iz kojeg nije
moguće dalje kretanje prema nekom neoznačenom čvoru. Postupak se ponavlja dok ne iscrpimo svaku
dalju mogućnost kretanja. Najkrača implementacija DFS pretrage je rekurzivna. U ovom zadatku oznaka
čvorova kao rezultat DFS pretrage nije od posebnog interesa pa ćemo samo koristi jedan globalni niz koji
označava da li je čvor posjećen ili nije.
bool dfs_posjecen_cvor[max_n];
Rekurzivna implementacija DFS pretrage bazirana je na činjenici da se za vrijeme DFS pretrage uvijek
pokušava posjetiti neki novi čvor koji nije prethodno posjećen (a to ustvari znači pokrenuti novu DFS
pretragu u čvoru koji želimo posjetiti), dok povratak nazad nastupa tek onda kada više nemamo izbora,
odnosno kada nema novih neposjećenih čvorova do kojih vodi neka grana iz trenutnog čvora. Povratak
nazad osiguran je otpetljavanjem rekurzije.
void dfs(int cvor)
{
dfs_posjecen_cvor[cvor] = true;
for (int i = 0; i < n; ++i)
if (matrica_susjedstva[cvor][i] && !dfs_posjecen_cvor[i])
dfs(i);
}
Sada se provjera da li se kretanjem kroz graf može doći od jednog do drugog čvora ustvari svodi na
pokretanje DFS pretrage u jednom čvoru i provjeri da li je prilikom te pretrage posjećen drugi čvor.
bool povezani_cvorovi(int prvi_cvor, int drugi_cvor)
{
for (int i = 0; i < n; ++i)
dfs_posjecen_cvor[i] = false;
dfs(prvi_cvor);
return dfs_posjecen_cvor[drugi_cvor];
}
Implementacija ostatka programa koja slijedi sastoji se od učitavanja podataka iz ulazne datoteke u
specificiranom formatu i nihovog tumačenja, korištenja objašnjenih funkcija i ispisa dobijenih rezultata u
navedenom formatu.
int main()
{
FILE* ulazna_datoteka = fopen("bihamk.in", "r");
FILE* izlazna_datoteka = fopen("bihamk.out", "w") ;
int m;
fscanf(ulazna_datoteka, "%d %d", &n, &m);
for (int i = 0; i < m; ++i)
{
char a[31], b[31];
fscanf(ulazna_datoteka, "%s %s", a, b);
dodaj_granu(numerisi_grad(a), numerisi_grad(b));
}
while (true)
98
{
char komanda[2], a[31], b[31];
fscanf(ulazna_datoteka, "%s", komanda);
if (komanda[0] == 'X')
break;
fscanf(ulazna_datoteka, "%s %s", a, b);
if (komanda[0] == 'P')
dodaj_granu(numerisi_grad(a), numerisi_grad(b));
else if (komanda[0] == 'N')
obrisi_granu(numerisi_grad(a), numerisi_grad(b));
else if (povezani_cvorovi(numerisi_grad(a), numerisi_grad(b)))
fprintf(izlazna_datoteka,
"Put izmedju gradova %s i %s je prohodan.\n", a, b);
else
fprintf(izlazna_datoteka,
"Put izmedju gradova %s i %s nije prohodan.\n", a, b);
}
fclose(ulazna_datoteka);
fclose(izlazna_datoteka);
return 0;
}
Zadatak 13.3. Nomenklatura
Težina: N
Biološka klasifikacija je način na koji biolozi grupiraju i kategoriziraju izumrle i živuće vrste i organizme.
Moderna klasifikacija vuče korijene iz sistema Carolusa Linnaeusa, koji je grupisao vrste prema njihovim
zajedničkim fizičkim osobinama. Nakon Linnaeusa, ove skupine su dorađene kako bi se dovele u bližu vezu
s Darwinovim načelom o zajedničkom pretku. Tako se, na primjer, čovjek klasificira na sljedeći način:
Vrsta razreda Razred
klasifikacije klasifikacije
Carstvo
Animalia
Pleme
Chordata
Klasa
Mammalia
Red
Primates
Porodica
Hominidae
Potporodica
Hominini
Rod
Homo
Vrsta
Sapiens
Binomna nomeklatura je naziv za sistem imenovanja vrsti živih bića u kojem se svakoj vrsti daje naziv koji
se sastoji od dva dijela (imena roda i imena vrste). Riječi ne moraju nužno biti latinskog porijekla ali se daju
imajući u vidu gramatička pravila latinskog jezika. Tako je, na primjer, naziv za čovjeka Homo sapiens.
99
Konvencijom je utvrđeno da je prvo slovo imena roda veliko slovo, dok je prvo slovo imena vrste malo
slovo.
Vaš zadatak je da napravite jednostavni program za klasifikaciju živih bića. Stablo klasifikacije je moguće
jednostavno definisati skupom uređenih parova riječi na način da je prva riječ viši razred klasifikacije a
druga riječ niži razred klasifikacije koji pripada prethodnom. Tako, na primjer, djelomično definisano stablo
klasifikacije neophodno za klasifikaciju čovjeka bi bilo određeno uređenim parovima riječi (Animalia,
Chordata), (Chordata, Mammalia), (Mammalia, Primates), (Primates, Hominidae), (Hominidae, Hominini),
(Hominini, Homo), (Homo, Sapiens).
Ulazni podaci
Na prvoj liniji ulazne datotetke “nomenklatura.in” nalazi se cio broj N (0 < N ≤ 100.000). U narednih N
linija ove datoteke nalaze se po dvije riječi odvojene razmakom. Sve riječi imaju veliko početno slovo i
ostala mala slova i ne sastoje se od više od 30 slova. Svaki od ovih redova opisuje jednu vezu u stablu
klasifikacije na način kako je to objašnjeno ranije. Možete smatrati da će se redovi u daoteteci pojavljivati u
onom redoslijedu koji garantuje da će se viši razred klasifikacije uvijek pojaviti prije nižeg. U onom slučaju
kada se određeni razred klasifikacije pojavi prvi put, možete smatrati da je u pitanju carstvo, odnosno da je
taj razred klasifikacije novi korijen stabla klasifikacije. Moguće je da bude više od jednog korjena.
U sljedećoj liniji ulazne datoteke nalazi se se broj M (0 < M < 10.000). U narednih M linija ove datoteke
nalaze se vrste za koje je potrebno izvršiti klasifikaciju. Prva riječ je ime roda, druga riječ je ime vrste, dok
su preostale riječi naziv vrste na našem jeziku. Naziv na našem jeziku (koji se može sastojati od malih i
velikih slova i razmaka) neće biti duži od 30 karaktera ukupno. Prva i treća riječ imaju početno veliko slovo
dok su sva ostala slova mala.
Izlazni podaci
U svaku od M linija izlazne datoteke “nomenklatura.out” treba ispisati po jednu od traženih klasifikacija u
onom redoslijedu u kojem su navedene u ulaznoj datoteci. Prvo treba ispisati naziv na našem jeziku, zatim
jednu dvotačku iza koje slijedi jedan razmak, a zatim treba pobrojati nazive svakog razreda klasifikacije
kojem pripada ta vrsta počevši od najvišeg redom do najnižeg međusobno razdvojene razmacima. Ukoliko
navedena vrsta nije u stablu klasifikacije, iza dvotačke i razmaka treba ispisati tekst “klasifikacija ne postoji”.
Primjer
nomenklatura.in
10
Plantae Angiosperms
Angiosperms Eudicots
Eudicots Rosids
Rosids Rosales
Rosales Rosaceae
Rosaceae Malus
Malus Domestica
Rosaceae Pyrus
Pyrus Communis
Pyrus Ussuriensis
4
Malus domestica Jabuka
Pyrus communis Evropska kruska
Pyrus ussuriensis Sibirska kruska
Malus sieversii Divlja jabuka
nomenklatura.out
Jabuka: Plantae Angiosperms Eudicots Rosids Rosales Rosaceae Malus Domestica
100
Evropska kruska: Plantae Angiosperms Eudicots Rosids Rosales Rosaceae Pyrus
Communis
Sibirska kruska: Plantae Angiosperms Eudicots Rosids Rosales Rosaceae Pyrus
Ussuriensis
Divlja jabuka: klasifikacija ne postoji
Rješenje
Stablo je struktura podataka koja modelira hijerarhijsku strukturu stabla ili drveta. U osnovi to je specijalan
slučaj usmjerenog grafa kod kojeg ne postoje ciklusi 2 i svaki čvor ima tačno jednog roditelja 3 osim jednog
čvora koji nema roditelja i naziva se korjenom stabla. Iz ovoga se nameće način za jednostavnu
implementaciju drveta. Koristimo strukturu podataka koja sadrži informacije koje čvor po prirodi
problema treba da sadrži, informaciju o tome koji je njegov roditelj ili informaciju da nema roditelja,
odnosno da je korijen drveta. Sve čvorove smještamo u niz, tako da je informacija o roditelju ustvari indeks
čvora koji je roditelj ili -1 ukoliko čvor nema roditelja. U ovom zadatku svaki čvor stabla ja ustvari jedan
razred klasifikacije, pa je pogodno koristiti sljedeću strukturu za predstavljanje jednog čvora u memoriji
struct Cvor
{
int roditelj;
char naziv[30];
};
Napomenimo da postoje implementacije stabla koje se baziraju na upotrebi pokazivača i dinamičke
alokacije memorije. Ove implementacije su efikasnije sa aspekta iskorištenja memorije onda kada unaprijed
nije poznata veličina stabla.
Operacije nad stablom koje će nam biti potrebne za rješenje zadatka su umetanje novog čvora u stablo ispod
određenog roditelja, pretraga čvorova stabla po nazivu i kretanje po stablu od trenutnog čvora do korjena stabla. Algoritam
koji rješava postavljeni zadatak koristeći ove operacije nad stablom je sljedeći:
1. Učitaj viši i niži razred klasifikacije sa trenutne linije iz datoteke.
2. Pronađi viši razred u stablu po imenu.
3. Ako razred nije pronađen, dodaj novi korijen stabla sa nazivom višeg razreda.
4. Dodaj novi čvor u stablo sa nazivom nižeg razreda i čvorom gdje je viši razred pronađen kao
roditeljom.
5. Vrati se nazad na korak 1 sve dok se ne unese N linija iz datoteke.
6. Učitaj ime roda, ime vrste i naziv na našem jeziku sa trenutne linije iz datoteke.
7. Pronađi čvor čiji je naziv jednak nazivu vrste i čiji roditelj ima naziv koji je jednak nazivu roda.
8. Krečući se uz stablo, tako da se svaki put prelazi na roditelja trenutnog čvora sve do korjena stabla
ispisati nazive svih čvorova koji se na ovaj način posjete, ali u obrnuton redoslijedu od redoslijeda
posjećivanja.
Opišimo još kako realizovati navedene operacije nad stablom. Niz u kojem su smješteni čvorovi kao i
varijabla koja čuva podatak o tome koliko je trenutno čvorova u stablu su globalne varijeble zbog
jednostavnosti implementacije. Obzirom da u datoteci može biti najviše 100.000 linija to znači da broj
2 U grafu ne postoji ciklus ako polazeći od proizvoljnog čvora slijedeći bilo koju ivicu proizvoljan broj puta, nije se moguće
vratiti ponovo u isti čvor.
3 Neka postoji usmjerena ivica između čvora a i čvora b. Tada se čvor a naziva roditeljom čvora b, a čvor b se naziva djetetom
čvora a.
101
različitih čvorova ne može biti veći od 200.000 (u slučaju kada su sve riječi različite).
int trenutno_cvorova = 0;
Cvor cvorovi[200000];
Operacija dodavanja čvora u stablo je jednostavna. Na kraj niza se doda novi čvor i poveća se broj čvorova
koji su trenutno u stablu. Operacija pretrage čvorova u stablu također nije komplikovana. Jednostavno se
prolaskom kroz niz traži onaj čvor čiji je naziv jednak traženom nazivu i vrača se indeks mjesta u nizu gdje
se on nalazi ili -1 ukoliko takav čvor nije pronađen. Slijede implementacije ovih funkcija.
//Funckija za dodavanje čvora u stablo sa određenim nazivom ispod roditelja
void dodaj_cvor(int roditelj, char naziv[])
{
cvorovi[trenutno_cvorova].roditelj = roditelj;
/*Stringove (odnosno nizove karaktera) treba dodjeljivati koristeći
*funckiju strcpy a ne operatorom '='*/
strcpy(cvorovi[trenutno_cvorova].naziv, naziv);
++trenutno_cvorova;
}
//Funckija koja vraća indeks u nizu čvora sa određenim nazivom
int pronadji_cvor(char naziv[])
{
for (int i = 0; i < trenutno_cvorova; ++i)
/*Stringove (odnosno nizove karaktera) treba porediti koristeći
*funckiju strcmp a ne operatorom '=='.*/
if (strcmp(cvorovi[i].naziv, naziv) == 0)
return i;
//Ukoliko čvor nije pronađen vračamo -1
return -1;
}
Operacije kretanja kroz stablo je nešto kompleksnija. Njena najednostavnija realizacija je rekurzivna. Slijedi
konkretna implementacija sa ispisom tražene klasifikacije.
//Funckija koja ispisuje klasifikaciju za čvor sa određenim indeksom
void ispisi_klasifikaciju_id(int id)
{
if (cvorovi[id].roditelj != -1)
ispisi_klasifikaciju_id(cvorovi[id].roditelj);
fprintf(izlazna_datoteka, " %s", cvorovi[id].naziv);
}
/*Funckija koja ispisuje klasifikaciju za čvor sa određenim nazivom i
*određenim nazivom svog roditelja*/
void ispisi_klasifikaciju(char naziv_rod[], char naziv_vrsta[])
{
//Prvo vršimo sekvencijalnu pretragu niza tražeći čvor sa navedenim uslovima
bool pronadjen = false;
for (int i = 0; i < trenutno_cvorova && !pronadjen; ++i)
if (cvorovi[i].roditelj != -1
&& strcmp(cvorovi[i].naziv, naziv_vrsta) == 0
&& strcmp(cvorovi[cvorovi[i].roditelj].naziv, naziv_rod) == 0)
{
//Ako je čvor pronađen, vršimo ispis klasifikacije
ispisi_klasifikaciju_id(i);
pronadjen = true;
102
}
}
//Ako je čvor nije pronađen, ispišemo odgovarajuću poruku
if (!pronadjen)
fprintf(izlazna_datoteka, " klasifikacija nije pronadjena.");
fprintf(izlazna_datoteka, "\n");
Ova funkcija poziva samu sebe u onim slučajevima kada postoji roditelj, a zatim ispisuje naziv trenutnog
čvora. Polazeći od čvora koji se nalazi na određenom mjestu u nizu funkcija će se rekurzivno pozvati nad
njegovim roditeljom. Taj novi poziv funkcije će uzrokovati novi rekurzivni poziv, i tako dalje sve do
trenutka kada trenutni čvor postane korijen stabla odnosno kada više ne postoji roditelj čvora. Tada će se
ispisati naziv krojena stabla, a zatim uslijed otpetljavanja rekurzije ispisat će se i nazivi ostalih čvorova koji
su posjećeni i to u suprotnom redoslijedu od onog u kojem su posjećeni. Da je bilo traženo da se postigne
ispis u redoslijedu koji odgovara redoslijedu posjete čvorova, to bi se postiglo tako što bi se ispis vršio prije
(eventualnog) rekurzivnog poziva funckije.
Slijedi i implementacija ostatka programa koji vrši operacije nad ulaznom i izlaznom datotekom i
implementira ranije opisani algoritam.
int main()
{
FILE* ulazna_datoteka = fopen("nomenklatura.in", "r");
izlazna_datoteka = fopen("nomenklatura.out", "w");
int n;
fscanf(ulazna_datoteka, "%d", &n);
for (int i = 0; i < n; ++i)
{
char naziv_roditelj[30], naziv_dijete[30];
fscanf(ulazna_datoteka, "%s %s", naziv_roditelj, naziv_dijete);
int roditelj = pronadji_cvor(naziv_roditelj);
if (roditelj == -1)
{
dodaj_cvor(-1, naziv_roditelj);
roditelj = trenutno_cvorova - 1;
}
dodaj_cvor(roditelj, naziv_dijete);
}
int k;
fscanf(ulazna_datoteka, "%d", &k);
for (int i = 0; i < k; ++i)
{
char naziv_rod[30], naziv_vrsta[30], naziv[31];
fscanf(ulazna_datoteka, "%s %s ", naziv_rod, naziv_vrsta, naziv);
/*Naziv vrste u ulaznoj datoteci je sa početnim malim slovom pa ga
*konvertujemo u odgovarajuće veliko slovo jer su čvorovi u stablu
*klasifikacije svi sa početnim velikim slovom*/
naziv_vrsta[0] = toupper(naziv_vrsta[0]);
fgets(naziv, 31, ulazna_datoteka);
/*fgets će unijeti i karakter '\n' koji je oznaka kraja reda a nije
*dio naziva na našem jeziku*/
naziv[strlen(naziv)-1] = '\0';
fprintf(izlazna_datoteka, "%s:", naziv);
ispisi_klasifikaciju(naziv_rod, naziv_vrsta);
}
fclose(ulazna_datoteka);
fclose(izlazna_datoteka);
return 0;
103
}
Zadatak 13.4. Posude
Težina: N
Ovaj zadatak je bio na Kantonalnom takmičenju iz informatike 2012. godine
Na raspolaganju imate tri posude za vodu. Te tri posude mogu biti različitog kapaciteta koji je izražen u
litrima (cjelobrojno). Na početku jedna posuda je puna vode dok su preostale dvije prazne. U svakom
trenutku možete presuti vodu iz jedne posude u drugu. Međutim, obzirom da nemate nikakav način da
mjerite koliko vode ste presuli, dopušteno je zaustaviti presipanje samo u jednom od dva slučaja. Kada se
posuda u koju se voda usipa napuni, treba stati sa presipanjem i ostatak vode (ako je nešto ostalo) ostaviti u
posudi iz koje se vode sipala. Kada se posuda iz koje se voda presipala ispraznila, treba stati sa presipanjem,
pa posuda u koju se voda sipala možda i nije do kraja puna.
Na primjer, neka su date tri posude kapaciteta 8, 5 i 4 litra i neka je inicijaljno voda samo u prvoj posudi
koja je puna, dok su preostale dvije posude prazne. Očigledno je moguće samo presipanje vode iz prve
posude u neku od druge dvije. Ako vodu presipamo u drugu posudu, presipanje ćemo zaustaviti kada se
druga posuda napuni, pa će nakon presipanja u prvoj posudi ostati 3 litra, a u drugoj će biti 5 litara. Slično,
da smo presipali sadržaj prve posude u treću što je također bilo dopušteno, tada bi sadržaj posuda sa
vodom nakon ovog koraka bio 4, 0 i 4 litra respektivno. Kada je količina vode u posudama 3, 5 i 0 litara,
moguće je presuti vodu iz prve u treću kantu. Presipanje ćemo zaustaviti kada se isprazni prva posuda, pa
je novo stanje posuda sa vodom: 0, 5 i 3 litra. Osim toga, bilo je moguće presuti vodu iz druge posude u
treću, tako da se dobiju 3, 1 i 4 litra. Nastavljajući od stanja sa 4, 0 i 4 litra, mogu se dobiti stanja 0, 4 i 4, te
4, 4 i 0 litra. Jasno je da se daljim presipanjima mogu dobiti razna stanja. Međutim, pokazuje se da postoje
određene kombinacije koje nisu moguće. Na primjer, bez obzira koliko poteza napravili, nije moguće dobiti
stanje sa 3, 4 i 1 litrom u prvoj, drugoj i trećoj posudi respektivno.
Vaš zadatak je da za zadane kapacitete posuda za vodu k1, k2, k3 i početnu količinu vode p1, p2, p3 u svakoj
posudi, odredite koja od n datih stanja vode u posudama se mogu dobiti proizvoljnim brojem presipanja u
skladu sa opisanim pravilima.
Ulazni podaci
Na prvoj liniji standardnog ulaza nalaze se tri prirodna broja (1 < k1, k2, k3 < 16.777.216 = 224 ) koji
predstavljaju kapacitete tri posude za vodu.
Na drugoj liniji standardnog ulaza nalaze se tri prirodna broja ( p1 ∈{0, k 1 }, p2 ∈{0, k 2 }, p3 ∈{0, k 3 } ) koji
predstavljaju koliko vode se nalazi u tri posude za vodu na početku. Tačno jedan od brojeva p 1, p2 i p3 će
biti različit od nula, dok će druga dva biti jednaki nula.
Na trećoj liniji standardnog ulaza nalazi se jedan prirodan broj (1 < n ≤ 10.000).
U (i+4)-oj liniji standardnog ulaza (0 ≤ i < n) nalaze se po tri prirodna broja (0 ≤ si1, si2, si3 < 16.777.216)
koji opisuju i-to stanje vode u posudama.
Izlazni podaci
Na (i+1)-u liniji standardnog izlaza (0 ≤ i < n) treba ispisati riječ DA (velikim slovima) ukoliko se i-to stanje
vode u posudama može dobiti nakon proizvoljnog broja presipanja u skladu sa opisanim pravilima. U
suprotnom, treba ispisati riječ NE.
104
Ograničenja
Vaš program ne smije raditi duže od 0.2s i ne smije koristiti više od 128MiB memorije.
Primjer
Ulazni podaci Izlazni podaci
8
8
2
3
3
5 4
0 0
DA
NE
1 4
4 1
Programski kod (C++):
#include <climits>
#include <cstdio<
#include <cstring>
using namespace std;
const unsigned int Kante_n = 3;
typedef unsigned int Kante[Kante_n];
Kante kante_kap;
unsigned int kante_suma;
unsigned int kante_moguce[16777216];
inline void bitNizPostavi(unsigned int* bit_niz, unsigned int i, bool vrijednost)
{
bit_niz[i/32] &= ~(1<<(i%32));
bit_niz[i/32] |= 1<<(i%32);
}
inline bool bitNizProvjeri(unsigned int* bit_niz, unsigned int i)
{
return bit_niz[i/32] & (1<<(i%32));
}
unsigned int kodiraj(Kante kante)
{
unsigned int kante_pune_flag = 0;
unsigned int kante_unutrasnje_vrijednost = 0;
unsigned int kante_unutrasnje_flag = 0;
for (unsigned int i = 0; i < Kante_n; ++i)
{
if (kante[i] != 0 && kante[i]!=kante_kap[i])
{
kante_unutrasnje_flag |= 1 << i;
kante_unutrasnje_vrijednost = kante[i];
}
else
{
kante_pune_flag <<= 1;
105
}
kante_pune_flag |= (kante[i]==kante_kap[i]);
}
if (kante_unutrasnje_flag == 0)
kante_pune_flag >>= 1;
return (kante_pune_flag << 27) | (kante_unutrasnje_flag << 24) |
kante_unutrasnje_vrijednost;
}
void kanteKopiraj(const Kante iz, Kante u)
{
for (int i = 0; i < Kante_n; ++i)
u[i] = iz[i];
}
int kantePrespi(Kante kante, unsigned int iz, unsigned int u, unsigned
maksimalno = UINT_MAX)
{
unsigned int ostalo_kap = kante_kap[u]-kante[u];
unsigned int presipa_se = (kante[iz] > maksimalno ? maksimalno :
kante[iz]);
if (presipa_se > ostalo_kap)
{
kante[u] = kante_kap[u];
kante[iz] -= ostalo_kap;
return ostalo_kap;
}
else
{
kante[u] += presipa_se;
kante[iz] -= presipa_se;
return presipa_se;
}
}
unsigned int dfs_cnt = 0;
void dfs(Kante kante)
{
unsigned int kante_kodirano;
kante_kodirano = kodiraj(kante);
if (bitNizProvjeri(kante_moguce, kante_kodirano))
return;
dfs_cnt++;
bitNizPostavi(kante_moguce, kante_kodirano, true);
for (int i = 0; i < Kante_n; i++)
if (kante[i] != 0)
for (int j = (i+1)%Kante_n; j != i; j=(j+1)%Kante_n)
{
unsigned int presuto = kantePrespi(kante, i, j);
dfs(kante);
kantePrespi(kante, j, i, presuto);
}
}
bool valid(Kante kante)
{
106
int
}
if (kante[0] + kante[1] + kante[2] != kante_suma)
return false;
int unutrasnje;
for (int i = 0; i < Kante_n; ++i)
if (kante[i] > kante_kap[i])
return false;
else if (kante[i] != 0 && kante[i] != kante_kap[i])
++unutrasnje;
return unutrasnje != 3;
int main()
{
scanf("%u %u %u", kante_kap, kante_kap+1, kante_kap+2);
Kante kante;
scanf("%u %u %u", kante, kante+1, kante+2);
kante_suma = kante[0] + kante[1] + kante[2];
memset(kante_moguce, 0, sizeof(kante_moguce));
dfs(kante);
int n;
scanf("%d", &n);
while(n--)
{
scanf("%u %u %u", kante, kante+1, kante+2);
if (valid(kante) && bitNizProvjeri(kante_moguce, kodiraj(kante)))
printf("DA\n");
else
printf("NE\n");
}
fclose(stdin);
}
Zadatak 13.5. Farma
Težina: I
Napomena: Odlukom komisije, na kantonalnom takmičenju iz informatike 2012. godine neće biti uvrštena oblast "najkraći
put". Ipak, radi kompletnosti ove zbirke, dajemo i jedan zadatak iz ove oblasti koji je bio dat na kantonalnom takmičenju
2007. godine.
Farmer posjeduje livadu na kojoj su posađene kruške. Farma je dimenzija NxN (1 < N < 10) i na svakom
polju te farme je posađeno stablo kruške. Farmer je zasadio 5 različitih sorti krušaka, tako da svaka daje
različit broj plodova dnevno. Prva sorta daje 1 plod dnevno, druga 2 ploda dnevno, treća daje 3 ploda
dnevno, četvrta 4 ploda dnevno i peta sorta daje 5 plodova dnevno. Zadatak je da napravite put farmera iz
polja najgornje lijeve kruške do polja najdonje desne kruške pri kojem će farmer sakupiti najveći broj
plodova krušaka. Farmer se može kretati samo u smjeru juga (dolje) i istoka (desno).
Ulaz:
Prva linija ulaza sadrži dimenziju polja N. Ostale linije predstavljaju redove tog polja koji sadrže sortu
krušaka (1-5).
Izlaz:
U prvoj liniji treba da sadrži maksimalan broj skupljenih krušaka, a u drugoj put kojim je farmer išao da bi
odabrao taj broj krušaka. Ukoliko farmer treba da ide desno oznaka je R a ukoliko treba da ide prema dolje
107
oznaka je D.
108
Literatura
1. Haris Šupić, Algoritmi i strukture podataka, Elektrotehnički fakultet Univerziteta u Sarajevu, Sarajevo
2010.
2. Željko Jurić, Diskretna matematika za studente tehničkih nauka, Elektrotehnički fakultet Univerziteta u
Sarajevu, Sarajevo 2011.
3. Željko Jurić, Tehnike programiranja, skripta.
109
Download

Kantonalno takmicenje iz informatike v6.0