6 - Pirinač
Vremensko ograničenje | Memorijsko ograničenje |
---|---|
1000ms | 64MB |
Mladi tajvanac Teo Lin se sprema da dočeka takmičare, profesore i goste koji će doći ove godine na IOI. Kao dobar domaćin, on će svakom takmičaru pokloniti po kutiju pirinča kada stigne. Kako je poznato da će doći \(N\) ljudi, on je već spremio \(N\) (praznih) kutija i postavio ih u niz, jednu do druge.
Svakog dana kada se vrati sa svog polja, on izabere nekih uzastopnih \(2^k\) kutija, a zatim u svaku ubaci \(2^v\) zrnca pirinča (Teo Lin mnogo voli "okrugle" brojeve, tj. stepene dvojke).
Nekad, kada mu je dosadno, on malo izmeša kutije. To radi tako što izabere grupu od \(2^k\) uzastopnih kutija, a zatim zameni njihov redosled tako sto uzme prvih pola \((2^{k-1})\) kutija iz te grupe, i stavi ih na mesto drugih pola kutija, i obrnuto. Drugim rečima, ukoliko imamo \(7\) kutija, redom obeležene brojevima \([1,2,3,4,5,6,7]\), a onda Teo izabere grupu od \(2^2\) kutije: \([3,4,5,6]\), redosled kutija nakon mešanja će biti: \([1,2,5,6,3,4,7]\). Ovaj postupak je prikazan i na slici:
Pre nego što ubacuje pirinač u kutije, Tea zanima koliko zapravo trenutno ima ukupno pirinča u izabranih \(2^k\) kutija. Zbog previše mešanja, on je i zaboravio gde ih je koliko ubacivao, a kako nema vremena da izbroji, zamolio je vas da mu pomognete, i odgovorite na neka pitanja koje vas bude pitao - Koliko ima ukupno zrnaca pirinča u \(2^k\) uzastopnih kutija koje je izabrao.
Opisi funkcija
Potrebno je da implementirate sledeće \(4\) funkcije:
Init(N, subtask)
– ova funkcija se poziva samo jednom na početku programa i označava da ima tačno \(N\) kutija, i da je taj test primer iz podzadatka \(subtask\). Možete je iskoristiti da postavite početne vrednosti svojih globalnih promenljivih/nizova.Ubaci(pos, k, v)
– ova funkcija označava da se u \(2^k\) uzastopnih kutija, počevši od pozicije \(pos\) ubacuje \(2^v\) zrnca pirinča. Ova funkcija se poziva više puta u toku programa sa potencijalno različitim parametrima.Izmesaj(pos, k)
– ova funkcija označava da će Teo Lin izmešati \(2^k\) uzastopnih kutija, počevši od pozicije \(pos\) kao što je opisano u zadatku. Ova funkcija se poziva više puta u toku programa sa potencijalno različitim parametrima.Prebroji(pos, k)
– ova funkcija predstavlja pitanje “Koliko ima ukupno zrnaca pirinča u \(2^k\) uzastopnih kutija, počevši od pozicije \(pos\)”; potrebno je odgovoriti na pitanje, tj. Odgovor vratiti kao vrednost funkcije. Ova funkcija se poziva više puta u toku programa sa potencijalno različitim parametrima.
Pozicije kutija su indeksirane od \(1\).
Primer 1
U sledećoj listi je prikazan mogući redosled poziva vaših funkcija:
Init(10, 1)
- Dobijate informaciju (samo jednom na početku) da je Teo pripremio \(10\) kutija, i da test primer ispunjava ograničenja iz prvog podzadatka. Na početku je broj zrnaca pirinča po kutijama: \([0,0,0,0,0,0,0,0,0,0]\).Ubaci(3, 2, 5)
- Dodajemo po \(32\) (\(2^5\)) zrnca pirinča u \(4\) (\(2^2\)) kutije počev od pozicije \(3\). Broj zrnaca pirinča po kutijama je sada: \([0,0,32,32,32,32,0,0,0,0]\).Ubaci(6, 1, 7)
- Dodajemo po \(128\) (\(2^7\)) zrnca pirinča u \(2\) (\(2^1\)) kutije počev od pozicije \(6\). Broj zrnaca pirinča po kutijama je sada: \([0,0,32,32,32,160,128,0,0,0]\).Prebroji(5, 2)
- Potrebno je vratiti \(320\) \((32 + 160 + 128 + 0)\), jer toliko zrnaca ima ukupno u \(4\) (\(2^2\)) kutije počev od pozicije \(5\).Izmesaj(4, 2)
- Izmešamo \(4\) kutije, koje počinju od pozicije \(4\). Broj zrnaca pirinča po kutijama je sada: \([0,0,32,160,128,32,32,0,0,0]\).Prebroji(5, 2)
- Potrebno je vratiti \(192\) \((128 + 32 + 32 + 0)\), jer toliko zrnaca ima ukupno u \(4\) (\(2^2\)) kutije počev od pozicije \(5\).Izmesaj(1, 3)
- Izmešamo \(8\) kutije, koje počinju od pozicije \(1\). Broj zrnaca pirinča po kutijama je sada: \([128,32,32,0, 0,0,32,160,0,0]\)Prebroji(4, 2)
- Potrebno je vratiti \(32\) \((0 + 0 + 0 + 32)\), jer toliko zrnaca ima ukupno u \(4\) (\(2^2\)) kutije počev od pozicije \(4\).Prebroji(3, 3)
- Potrebno je vratiti \(224\) \((32 + 0 + 0 + 0 + 32 + 160 + 0 + 0)\), jer toliko zrnaca ima ukupno u \(8\) (\(2^3\)) kutije počev od pozicije \(3\).
Ograničenja
- Funkcija
Init(N, subtask)
se poziva tačno jednom (na početku) i važi \(1\leq N\leq 100.000\) i \(1\leq subtask\leq 5\). - Funkcije
Ubaci
,Izmesaj
iPrebroji
se ukupno pozivaju ne više od \(300.000\) puta. - Prilikom svakog poziva
Ubaci(pos, k, v)
važi \(k\geq 0\), \(1\leq pos\leq pos+2^k-1\leq N\), i \(0\leq v\leq 30\). - Prilikom svakog poziva
Izmesaj(pos, k)
važi \(k\geq 1\), \(1\leq pos\leq pos+2^k-1\leq N\). - Prilikom svakog poziva
Prebroji(pos, k)
važi \(k\geq 0\), \(1\leq pos\leq pos+2^k-1\leq N\).
Označimo sa \(numUbaci\) broj poziva funkcije Ubaci
, sa \(numIzmesaj\) broj poziva funkcije Izmesaj
, sa \(numPrebroji\) broj poziva funkcije Prebroji
, i sa \(Q = numUbaci+numIzmesaj+numPrebroji\):
- PODZADATAK \(1\) [\(7\) POENA]: \(N\leq 1024\), \(Q\leq 1024\).
- PODZADATAK \(2\) [\(17\) POENA]: Prilikom svakog poziva funkcije
Ubaci
će važiti \(k=0\) i prilikom svakog poziva funkcijeIzmesaj
će važiti \(k=1\). - PODZADATAK \(3\) [\(18\) POENA]: \(numIzmesaj=0\).
- PODZADATAK \(4\) [\(25\) POENA]: \(N\leq 16384\).
- PODZADATAK \(5\) [\(33\) POENA]: Nema dodatnih ograničenja.
Detalji implementacije
Potrebno je da pošaljete tačno jedan fajl, pod nazivom pirinac.c
, pirinac.cpp
ili pirinac.pas
, koji implementira gore pomenutu funkciju. Osim tražene funkcije, vaš fajl može sadržati i dodatne globalne promenljive, pomoćne funkcije i dodatne biblioteke.
Zavisno od programskog jezika koji koristite, vaša funkcija/procedura mora biti sledećeg oblika:
C/C++:
void Init(int N, int subtask);
void Ubaci(int pos, int k, int v);
void Izmesaj(int pos, int k);
long long Prebroji(int pos, int k);
Pascal:
procedure Init(N, subtask : longint);
procedure Ubaci (pos, k, v : longint);
procedure Izmesaj (pos, k : longint);
function Prebroji(pos, k : longint) : int64;
Testiranje i eksperimentisanje
Uz zadatak, obezbeđeni su vam “template” fajlovi (pirinac.c
, pirinac.cpp
, pirinac.pas
) koje možete koristiti i menjati po potrebi. Takođe su vam obezbeđeni programi (grader.c
, grader.cpp
, grader.pas
) koji služe da lakše testirate kodove. Ovi programi učitavaju sa standardnog ulaza sledeće podatke:
- U prvom redu brojeve \(N\) i \(Q\) i \(subtask\), razdvojene razmakom; \(N\) je broj kutija, \(Q\) ukupni broj upita (ne računajući
Init
), a subtask je redni broj podzadatka; - U narednih \(Q\) redova nalazi se prvo broj \(t\); ukoliko je \(t = 1\), tada trenutni red predstavlja upit
Ubaci
i u nastavku reda se učitavaju još \(3\) broja (\(pos\), \(k\), \(v\)) sa kojima se poziva funkcija; ukoliko je \(t = 2\), tada trenutni red predstavlja upitIzmesaj
i u nastavku reda se učitavaju još \(2\) broja (\(pos\), \(k\)) sa kojima se poziva funkcija; ukoliko \(t = 3\), tada trenutni red predstavlja upitPrebroji
i u nastavku reda se učitavaju još \(2\) broja (\(pos\), \(k\)) sa kojima se poziva funkcija.
Na početku se poziva vaša funkcija Init
sa parametrima \(N\), \(subtask\), a zatim se za svaki učitani upit poziva vaša odgovarajuća funkcija iz odgovarajućeg fajla (pirinac.c
, pirinac.cpp
, pirinac.pas
) sa učitanim parametrima. Posle svakog poziva vašoj funkciji Prebroji
, rezultat koji ona vraća se ispisuje na standardni izlaz. Kodove ovih programa možete menjati po potrebi.
Autor | Tekst i test primeri | Analiza rеšenja | Testiranje |
---|---|---|---|
Dušan Zdravković | Dušan Zdravković | Aleksandar Višnjić | Dimitrije Dimić |
Prvi podzadatak
Na upite odgovaramo naivnim prolaskom kroz niz jednom petljom, zamenjujući ili dodavanjem na elemente niza direktno. Složenost je \(O(N \cdot Q)\).
Drugi podzadatak
Funkcija Ubaci povećava jedan element niza, a funkcija Izmesaj zamenjuje dva susedna njegova elementa (odnosno jedan povećava, a drugi smanjuje). Ovim imamo linearan broj upita povećavanja i imamo upite zbira na segmentu, što nas navodi da zadatak rešavamo segmentnim stablom u složenosti \(O(N+QlogN)\).
Treći podzadatak
Kako ne pozivamo funkciju Izmesaj, zadatak se svodi na upite raspona povećanja i sume. Segmentno stablo sa lenjom propagacijom rešava ovaj podzadatak u \(O(N+QlogN)\).
Četvrti podzadatak
Niz se može podeliti u listu blokova veličine \(\sqrt N\); svaki blok čuva listu celih brojeva koji označavaju same elemente niza i još jedan ceo broj koji označava za koliko još dodatno treba uvećati svaki element te liste. Ubaci i Prebroji nije teško implementirati u \(O(\sqrt N)\). Što se tiče funkcije Izmesaj, ona će prvo po potrebi podeliti blokove u kojima se nalaze krajevi njenog raspona na dva bloka (kako radimo sa listama to je efikasno, takođe uvek nastaje najviše 4 nova bloka). Zatim treba da "izmešamo" određene cele blokove, ali to je opet samo izbacivanje iz i dodavanje u listu (koja sadrži liste). Napomenimo samo da se nakon \(\sqrt N\) poziva funkcije Izmesaj blokovi moraju napraviti iznova u \(O(N)\) kako bi njihov broj ostao korenskog reda. Ukupna složenost je \(O(N+Q\sqrt N)\).
Glavno rešenje
Napravimo segmentno stablo, ali u kom će veličina svakog čvora biti stepen dvojke. Takođe uradimo to iterativno: za najmanje \(k\) takvo da \(2^k\geq N\) najniži nivo će obuhvatati segmente \([0,0],[1,1],[2,2],...[2^k-1,2^k-1]\), sledeći će \([0,1],[2,3],..,[2^k-2,2^k-1]\), ... koren će obuhvatati jedan segment \([0,2^k-1]\). Upite uvećanja i zbira radimo isto kao u trećem podzadatku, a za mešanje moramo uočiti jednu lepu stvar kod rastavljanja segmenta na čvorove u ovakvom stablu. Najpre primetimo da svi segmenti u upitu imaju veličinu stepena dvojke, kao i njihove polovine. To čini da rastavljanje leve i desne polovine upita na čvorove topološki imaju istu strukturu. Formalnije, ako se leva polovina, redom, rastavlja na čvorove \(x_1,x_2,...,x_p\), a desna na \(y_1,y_2,...,y_q\), važiće \(p = q\) i
gde \(size(t)\) predstavlja veličinu segmenta koji taj čvor predstavlja. Ovo čini da se svaki čvor može lako zameniti sa njegovim odgovarajućim, potrebno je samo stablo implementirati implicitno. Vremenska složenost je \(O(N+QlogN)\).
06_pirinac.cpp | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
|