[Решења задатака] 2024/2025 Изборно за јуниорске информатичке олимпијаде

Вредност подниза

Аутор, текст и тест примери: Љубомир Бановић

Тестирање и анализа решења: Душан Попадић, Огњен Тешић

k\leq \min(n,100)

У овом подзадатку је довољно грубом силом проћи кроз све могуће поднизове (њих је \mathcal{O}(nk)) и рачунати збир подниза користећи префиксне суме. Сложеност овог приступа је \mathcal{O}(nk).

n=k

Овај подзадатак се своди на познат проблем Maximum Subarray Sum, чије решење можете прочитати на следећем линку Maximum Subarray Sum - Kadane's Algorithm - GeeksforGeeks.

Главно решење

Да бисмо ограничили дужину подниза на највише k, у сваком тренутку чувамо префиксне суме на претходних k позиција у скупу или приоритетном реду. То нам омогућава да у логаритамском времену нађемо најмању префиксну суму међу претходних k позиција (јер ће она давати највећу разлику pref[i] - pref[j] и самим тим и највећу суму подниза. Решење је највећа вредност такве разлике кроз све позиције у низу. Сложеност овог приступа је \mathcal{O}(n\log k), што је довољно за максималан број поена.

Оптималније решење: Уместо да чувамо све префиксне суме претходних k индекса у сортираној структури, примећујемо да нас у сваком тренутку занима само најмања префиксна сума у интервалу [i - k, i].
Користимо структуру deque (ред са два краја) који нам омогућава да:

  • уклонимо елементе који више нису у интервалу дужине k
  • уклонимо све префиксне суме које су веће од тренутне (јер никада неће бити боље решење од ње)
  • за тренутни индекс i, најбоље решење се добија као разлика pref[i] - \min(pref[j]) за j \in [i-k, i]

Овим приступом обезбеђујемо да сваки елемент буде додат и уклоњен из deque-а највише једном, што даје укупну сложеност \mathcal{O}(n). Оваква употреба структуре deque се назива monotonic queue.

Графовски савез

Аутор, текст, тест примери и анализа решења: Михајло Марковић

Тестирање: Урош Стефановић

Решење за N, M \leq 5000

У овом подзадатку довољно је након сваког упита додати грану, и затим помоћу претраге у дубину или ширину проверити да ли је граф бипартитан. За сваку компоненту треба чувати број чворова који су обојени у једну и другу боју и по завршетку обиласка компоненте у резултат додати максималну од те две вредности. Уколико су све компоненте бипартитне, исписујемо резултат. У супротном, исписујемо -1 и бришемо грану коју смо покушали да додамо.

За сваки предлог осим првог, тачно једно острво из тог предлога се појавило и у неком претходном предлогу

У овом подзадатку формира се стабло и свака грана која се додаје спаја неки од чворова који већ припадају стаблу са новим чвором који ће у том тренутку постати лист тог стабла. Како је свако стабло бипартитно, увек ће бити могуће додати грану. Чворове на парним дубинама бојимо у једну боју, а на непарним дубинама у другу боју. Решење након сваког упита је збир броја чворова који још увек нису у стаблу и максимума од броја чворова на парним и броја чворова на непарним дубинама.

За сваки предлог осим првог, бар једно острво из тог предлога се појавило и у неком претходном предлогу

Овај подзадатак је сличан претходном. Ако су се оба чвора већ појавила у претходним предлозима, они већ припадају стаблу. Пошто је бојење стабла (и генерално било ког повезаног графа) тако да буде бипартитан јединствено, уколико предложена грана повезује чворове на дубинама различите парности можемо је додати и нећемо нарушити бипартитност. У супротном, предлог одбацујемо. Дакле, ове гране су небитне и ни на који начин не мењају бојење чворова. За гране које повезују један стари и један нови чвор, решење је претходни подзадатак.

За сваки предлог, бар једно острво из тог предлога се није појавило ни у једном претходном предлогу

У почетном тренутку, пре додавања грана сви чворови могу бити обојени у једну боју, па је почетни резултат N. Овај резултат ћемо памтити и ажурирати након додавања сваке нове гране. Имамо два случаја.

Први случај: повезујемо два нова чвора (тј. спајамо две компоненте дужине 1). Резултат се смањује за 1 пошто сада само један од ових чворова може бити у доминантној боји, а пре спајања су могла оба. Ови чворови формирају ново стабло на које ће се касније надовезивати нови чворови. Дакле, имаћемо скуп стабала, тј. шуму. За сваки чвор који се налази у неком стаблу можемо памтити да ли се налази на парној или непарној дубини и индекс стабла ком припада (тај индекс, на пример, може бити индекс корена тог стабла). За свако стабло памтимо број чворова на парним и број чворова на непарним дубинама.

Други случај: повезујемо нови чвор са старим. Прво за стари чвор утврдимо ком стаблу припада. Затим од тренутног резултата одузмемо резултат тог стабла (тј. максимум од броја чворова на парним и непарним дубинама). Сачувамо индекс стабла ком припада нови чвор, као и парност дубине. У резултат додајемо максимум од броја чворова на парним и непарним дубинама тог стабла након овог ажурирања.

Свако острво се појављује у највише два предлога

Граф који се добије након свих предлога се састоји од компоненти које су или ланци или прости циклуси. Сваки ланац је бипартитан. Ако је прост циклус парне дужине, он јесте бипартитан. Ако је прост циклус непарне дужине, он није бипартитан и бипартитност је нарушила последња грана која је додата (пре њеног додавања та компонента је била ланац). Према томе, можемо једноставно одредити резултат након свих предлога, а затим се враћати уназад и избацивати гране, чиме ће прости циклуси постајати ланци и ланци ће се делити на мање ланце.

Да бисмо ефикасно одредили ком ланцу или циклусу тренутно припадају чворови гране коју избацујемо, чворове ћемо распоредити у низ, тако да чворови који су суседни унутар једног ланца буду суседни и у том низу, док ћемо за циклусе то исто урадити након што им избацимо последњу додату грану, тј. када постану ланци. У структури сет ћемо чувати индексе низa на којима почињу чворови новог ланца. За сваки чвор чувамо његову позицију у том низу. Када желимо да избацимо грану ланца, бинарном претрагом у сету налазимо индексе из низа првог већег и првог мањег почетка (у односу на индекс неког од чворова гране која се избацује), а њихова разлика је управо дужина тренутног ланца. Горњи цео део половине те дужине је учествовао до сада у резултату, па га одузимамо из резултата. Избацивањем гране настаће два нова ланца, па ћемо у сет додати позицију почетка новог ланца из низа, тј. то ће бити максимална од позиција чворова те гране. Затим у резултат додајемо горње целе делове половина дужина два новодобијена ланца.

Опис главног решења

За сваки чвор можемо чувати компоненту којој припада и боју у коју је обојен у тој компоненти. За сваку компоненту можемо чувати број чворова у свакој боји. Ако се грана додаје између чворова који припадају истој компоненти, могуће је додати ту грану ако и само ако су чворови које она повезује обојени различитим бојама. Усвајање или неусвајање овог предлога ни на који начин не утиче на бојење. Ако се грана додаје између чворова који припадају различитим компонентама, тада треба извршити спајање тих компоненти, тј. потребно нам је да убудуће знамо за све те чворове да припадају истој компоненти и која је њихова боја. Ако су чворови између којих се грана додаје били у супротним бојама, онда се боје чворова не мењају. У супротном, потребно је проћи кроз једну од компоненти и сваком чвору променити боју. У оба случаја, треба проћи кроз једну од компоненти и за сваки чвор сачувати да сада припада оној другој компоненти.

Анализирајмо временску сложеност решења уколико се при сваком спајању одлучимо да ове измене вршимо у мањој компоненти. Посматрајмо један произвољан чвор и максималан број пута да он буде посећен приликом свих измена. У најгорем случају, компонента којој припада посматрани чвор се сваки пут спаја са компонентом једнаке или веће дужине. Нека се он први пут спаја са неком компонентом дужине 1 и они сада формирају компоненту дужине 2, затим се спајају са неком компонентом дужине бар 2 и настаје компонента дужине бар 4… Укупно, највише \log N пута ће бити посећен приликом измена у спајањима. Како ово својство важи за сваки од чворова, временска сложеност је O(N\log N). Описана техника се назива small to large merging.

Меморијска сложеност може бити O(N) или O(N\log N) у зависности од имплементације. Пошто нам је приликом спајања компоненти потребно да имамо све чворове који припадају тој компоненти, најједноставније је да за сваку компоненту то чувамо у неком вектору, који ћемо проширивати када ту компоненту спајамо са неком мањом. Меморијска сложеност такве имплементације би била O(N \log N), што је довољно добро за дата ограничења. Напреднији приступи би били да уместо вектора то буду листе које ћемо само надовезивати и тиме не заузимамо додатни простор, или уместо класичног small to large merging, примена структуре disjoint set union.

Имамо један низ у ком чувамо родитеља сваког чвора која нам говори да чвор и родитељ припадају истој компоненти. При спајању две компоненте, увек ћемо представника веће компоненте прогласити да буде родитељ представника мање компоненте. Представник компоненте је корен тог стабла, или другим речима, најстарији предак кад се крећемо кроз родитељске чворове. Два чвора припадају истој компоненти ако и само ако им је исти представник компоненте. Да бисмо одржавали и промене боја, можемо имати низ који нам говори да ли при спајању чвора и родитеља тог чвора долази до промене боје чворова из мање компоненте. Овде је важно нагласити да се не упоређују боје представника компоненти, него боје чворова између којих додајемо грану. Паралелно са налажењем представника компоненте неког чвора можемо да нађемо и боју тог чвора у компоненти у којој се тренутно налази, крећући се по истим индексима у низу промена као у низу родитеља. Ако је сума тих посећених промена парна, чвор је у боји 0, ако је непарна чвор је у боји 1. Временска сложеност је, у зависности од имплементације, O(N \log N) или O(N\alpha(N)), а меморијска O(N).

До нуле

Аутор, текст, тест примери и анализа решења: Михајло Марковић

Тестирање: Огњен Тешић, Душан Попадић

Први и други подзадатак, Q \leq 100 и X \leq 10^9

За решавање ових подзадатака довољно је приметити да ако је број дељив са 2, увек је једина оптимална операција поделити га са 2 (осим у случају када је X=2, тада је оптимална и операција одузимања). Можемо осмислити рекурзивну функцију која ће за дато X вратити пар (минималан број операција, број начина минималне дужине). Ако је број X паран, минимални број операција ће бити за један већи од минималног броја операција за број \frac{X}{2}, а број начина минималне дужине је једнак броју начина минималне дужине за број \frac{X}{2}%. Ако је број X непаран, позваћемо функцију за бројеве X-1 и X+1. Уколико је минималан број операција за оба броја једнак, онда ћемо њихов број начина сабрати. Ако није једнак, узећемо онај пар коме је минимални број операција мањи (и наравно, број операција повећавамо за један).

Трећи и четврти подзадатак, X \leq 10^6

Овај подзадатак можемо решити динамичким програмирањем. Пошто за сваки непаран број треба да знамо резултате за њему суседне парне бројеве, dp низ можемо попуњавати редом 2, 1, 4, 3, 6, 5, 8, 7... или рекурзивно као у прошлом подзадатку, али са мемоизацијом. Трећи приступ, који су и неки такмичари применили, јесте да за налажење минималног броја операција искористимо идеју из наредног подзадатка која је ефикаснија, а затим пуштамо рекурзију и из броја X-1 и из броја X+1 само када је минимални број операција да се од тих бројева стигне до нуле једнак. Иначе, пуштамо рекурзију само из оног броја за који је минимални број операција мањи.

Први, трећи и пети подзадатак, T=1 и X \leq 10^{18}

Из операција које имамо на располагању, природно се намеће идеја да посматрамо бинарни запис броја X. Прво можемо приметити да ће нам бити неопходно бар онолико операција колико цифара има број X у бинарном запису. Поред тога, за сваки сегмент узастопних јединица биће нам потребно још неколико операција. Кренимо од позиција најмање тежине у бинарном запису. Ако је тренутни број дељив са 2, само ћемо га поделити са 2 (након тога тренутни број у бинарном запису остаје без те цифре 0 на најмањој тежини). Ако су последње две цифре бинарног записа 01 (другим речима, тренутни број даје остатак 1 при дељењу са 4) оптимално је одзуети 1 и на тај начин се решити те јединице јер је она изолована од осталих. Ако су последње две цифре бинарног записа 11 (тренутни број даје остатак 3 при дељењу са 4) оптимално је додати 1, јер ће се овиме цео сегмент јединица (а он је дужине бар 2) претворити у нуле, па касније можемо ту једну додатну јединицу елиминисати у још једној операцији. На овај начин смо за сегмент јединица дужине 2 или више искористили највише две додатне операције (може се десити и да то буде само једна операција, ако се јединица добијена из преноса након сабирања споји са наредним сегментом јединица, тј. уколико смо између та два сегмента јединица имали само једну нулу и на место те нуле је дошла јединица из преноса). Специјалан случај је када је тренутни број једнак 3 и тада треба одузети 1. Ово није једино оптимално решење, али у овом делу не разматрамо број начина.

Други, четврти и шести подзадатак, T=2 и X \leq 10^{18}

Из мало детаљнијег разматрања претходног, можемо закључити да до неке позиције у бинарном запису можемо доћи различитим комбинацијама стања, од чега нас нека доводе у тренутно стање са послатим преносом 1 од сабирања, а нека нам не шаљу тај пренос. Дакле, можемо кренути од цифре најмање тежине и за сваку тежину i одредити минималан број операција и број начина да до ње дођемо без преноса и са преносом од сабирања (другим речима, да дођемо до броја \left\lfloor \frac{X}{2^i} \right\rfloor и броја \left\lfloor \frac{X}{2^i} \right\rfloor +1). Нека су dp0[i] и dp1[i] ти парови {минималан број операција, број начина минималне дужине} за случај без преноса и са преносом.

На основу тога, ако је на позицији i у бинарном запису 0, пренос на позицији i+1 можемо добити само ако смо у стање i стигли са преносом и одлучимо да тренутном броју додамо 1 (и поделимо га са 2), па је dp1[i+1]={dp1[i].first+2,dp1[i].second}. Без преноса на позицију i+1 можемо стићи или тако што смо дошли из стања i са преносом и операцијом одузимања за 1 (па затим дељења са 2) или тако што смо дошли из стања i без преноса и само операцијом дељења са 2. Према томе, ако обе путање дају једнак број операција, тј. ако је dp1[i].first+2==dp0[i].first+1, онда је dp0[i+1]={dp0[i].first+1,dp1[i].second+dp0[i].second}. Ако број операција није једнак, за број операција ћемо узети минимум од dp0[i].first+1 и dp1[i].first+2, а број начина ћемо само узети од оног пара од ког смо узели и минималан број операција.

Ако је на позицији i у бинарном запису 1, примењујемо сличну анализу.

Други начин је да групишемо нуле и јединице у бинарном запису и да разматрамо случајеве када имамо једну, две, три или више узастопних нула/јединица, али овај начин није нарочито занимљив за анализу и имплементацију.

Седми и осми подзадатак

Потребно је да велики број X представимо у бинарном бројном систему. То можемо једноставно урадити симулирањем алгоритма за дељење са 2. Све док је број већи од нуле, крећемо од цифре највеће тежине и пролазимо кроз тренутни број тако што сваку цифру са урачунатим преносом од претходне позиције делимо са два. Остаци при дељењу са 2 након сваке итерације нам дају цифре у бинарном систему од најмање до највеће тежине. Након тога се решење своди на претходне подзадатке.

Анирин задатак

Аутор, текст, тест примери и анализа решења: Немања Мајски

Тестирање: Благој Рујаноски Стојановски (MKD)

Основна запажања

Ради прегледности, више тврђења је у овом решењу наведено без доказивања. Детаљан доказ можете наћи овде.

Прво запажање је да ће Анири увек на картицу записати већи од бројева a+b и a \cdot b, пошто ће у каснијим операцијама то да доведе то већих бројева.

Решење када је Q =1 и a_i \ge 2

У овом подзадатку ће сви бројеви који икад буду у ранцу бити \ge 2. А како за било која два броја a, b \ge 2 важи a \cdot b \ge a+b, значи да је одговор на упит производ свих бројева у ранцу, односно његов остатак при дељењу са M.

Решење када је N \le 8, Q=1 и a_i \le 100

У овом подзадатку је могуће рекурзивно пролазити кроз све могуће изборе бројева у сваком кораку. Како резултат не прелази 10^{16}, он стаје у 64-битни тип података.

Решење када је N \le 100, Q \le 1000 и a_i=1

Направићемо dp низ тако да је dp_i највећи број који може да се добије ако имамо i јединица. Почетни услови су dp_1 = 1, dp_2 = 2, dp_3 = 3.

Сада је проблем како наћи вредност dp_n за n > 3. Користимо чињеницу да је могуће прво извршити сва сабирања, па затим помножити све бројеве у ранцу. То значи да чиниоце можемо да правимо један по један, а онда на крају да их све помножимо. Рецимо да смо фиксирали да ће нам први чинилац бити x. Онда од преосталих n - x јединица треба да направимо што већи број, који ћемо помножити са x. Тај број смо претходно већ израчунали и он је dp_{n-x}.

Тако да је вредност dp_n максимум од x \cdot dp_{n-x} за свако 1 \le x < n.

Сложеност је O(N^2 + Q).

Решење када је N, Q \le 1000 и a_i=1

Сложеност претходног подзадатка је довољно брза да временски одради и овај. Али проблем настаје у томе што је dp_{1000} веома велики број и не стаје у 64-битни тип података. Тако да не можемо радити поређење да бисмо нашли максимум, морамо наћи формулу за тај број.

Ако математички запишемо наш резултат, он ће изгледати као производ више заграда, у којој се налази збир неколико јединица. Пример за dp_{10} = 36:

$$(1+1+1) \cdot (1+1+1) \cdot (1+1) \cdot (1+1) = 36$$

Приметимо да ће у свакој загради бити две или три јединице, пошто за n \ge 4 важи \lfloor \frac{n}{2} \rfloor \cdot \lceil \frac{n}{2} \rceil \ge n, а имати једну јединицу у загради нема смисла.

Како је (1+1) \cdot (1+1) \cdot (1+1) < (1+1+1) \cdot (1+1+1), то значи да ће највише две заграде ће имати две јединице. Број заграда са две јединице одређен остатком дељења n са три. Из тога следи формула dp_n = 3 \cdot dp_{n-3} за n \ge 5.

Решење када је N,Q \le 1000 и a_i \le 2

Нека је број јединица у ранцу a, а број двојки b. Уколико је a \ge b, онда је решење dp_{a+2\cdot b}, пошто је могуће све двојке претворити у тројке, па ће крајна конструкција бити иста као што би била да су двојке заправо две јединице. Сада посматрамо случај када је b > a.

Приметимо да никада нећемо да имамо чинилац већи од три у нашем производу. Тако да ћемо ми неке двојке повећати за један, а остале јединице ћемо комбиновати као у претходном подзадатку.

Претпоставимо да смо одлучили да неке две јединице групишемо у загради. Гарантује се да постоје барем две двојке којима нисмо додали јединицу (због b > a). Како је (1+1) \cdot 2 \cdot 2 < (2+1) \cdot (2+1), оптимално је да ми те две јединице ипак додамо на двојке, а не да их групишемо у заграду. Аналогно (1+1+1) \cdot 2 \cdot 2 \cdot 2 < (2+1) \cdot (2+1) \cdot (2+1).

Следи да је у овом случају оптимално додати све јединице на двојке, тако да је одговор 2^{b-a} \cdot 3^a.

Решење када је N,Q \le 1000

У овом подзадатку се појављују и бројеви већи од два. Само јединице ће се додавати на неке бројеве, или комбиновати. Остали бројеви се само множе. Да би се дошло до решења, требају редом да се примењују следећа правила.

Ако постоји само једна јединица у ранцу, она ће бити додата на други најмањи број у ранцу (најмањи сем ње).

Ако не постоје двојке у ранцу, све јединице ће бити груписане саме са собом, а резултат ће бити помножен са производом осталих бројева. Ово важи пошто dp_{n+1} \ge \frac{4}{3} \cdot dp_n за n \ge 2.

Ако постоје двојке у ранцу, оне се комбинују са јединицама као у претходном подзадатку. Одонсно, додају се на двојке све док има и јединица и двојки. За остатак јединица се примењују претходна два правила.

Главно решење

За рачунање одговора на упит у претходном подзадатку нам је било потребно да нађемо:

  • Број јединица у ранцу;
  • Број двојки у ранцу;
  • Најмањи број у ранцу већи од два (ако постоји);
  • Производ свих осталих бројева.

Све ово можемо да нађемо у сложености O(\log N) по упиту користећи сегментно стабло. Након тога само израчунавање вредности може да се одради у сложености O(1) уз прекалкулације.

Сложеност алгоритма је O(N + Q \log N).

Мајстор Боб

Аутор, текст, тест примери и анализа решења: Љубомир Бановић

Тестирање: Душан Попадић

n \leq 20

У овом подзадатку, потребно је набројати сваки подскуп послова и за сваки такав подскуп проверити да ли је могуће узети сваки посао у том подскупу. Да би се проверило да ли је могуће обавити овако одабране послове без преклапања, потребно је да их сортирамо по времену почетка, а затим проверавамо редом да ли је сваки следећи могуће узети. Од свих валидних подскупова, исписати онај који доноси највећу зараду.

Временска сложеност: \mathcal{O}(n \cdot 2^n)

c_{i} = 1

Како сви послови вреде исто, овај подзадатак од нас захтева да одредимо највећи број послова који можемо да узмемо, а да се не преклапају. Ово је познат задатак, који се решава тако што послове сортирамо по времену краја, те редом похлепно узимамо сваки следећи посао.

Временска сложеност: \mathcal{O}(n \log n)

n \leq 10^3

Приметимо да нама редослед уноса послова заправо није битан, те није лоша идеја сортирати послове. Наиме, сортирајмо послове као у прошлом задатку, по времену завршетка. Овако сортиран низ нам омогућава да знамо да приликом обраде i-тог посла, сви послови пре њега су се завршили пре или у исто време као посао i.

Да бисмо решили овај подзадатак, потребно је да применимо технику динамичког програмирања. Нека dp[i] представља највећу зараду, ако је последњи посао који смо узели посао i. Потребно је да за свако i, проверимо све претходне послове које можемо да узмемо заједно са послом i, те поставимо вредност dp[i] на \max(dp[i], dp[j] + c_{i}).

Временска сложеност: \mathcal{O}(n^2)

1 \leq l_{i} \leq r_{i} \leq 10^5

У овом подзадатку, потребно је оптимизовати решење претходног подзадатка. Променимо мало дефиницију dp[i]. Нека она означава највећу могућу зараду, међу свим пословима који су се завршили до времена i (укључујући и време i). Приликом израчунавања вредности dp[i], разматрамо све послове који су се завршили на време i и проверавамо да ли ако узмемо неке од тих послова и комбинујемо са dp[l_{k} - 1] (где је l_{k} време почетка текућег посла), можемо да зарадимо више од dp[i].

Временска сложеност: \mathcal{O}(n \log n + r_{max})

Главно решење

У овом подзадатку, потребно је компресовати почетке и крајеве послова. На овако компресоване послове, применити алгоритам употребљен у претходном подзадатку.

Временска сложеност: \mathcal{O}(n \log n)

Сви прости

Аутор, текст, тест примери и анализа решења: Огњен Тешић

Тестирање: Урош Стефановић

N\leq 6

Овде је довољно проверити све могуће бројеве који се могу добити као подстрингови унетог броја. Проверу да ли је број прост радимо у линеарној сложености.

N\leq 10^4

Овде је довољно проверити све могуће бројеве који се могу добити као подстрингови унетог броја. Проверу да ли је број прост радимо у коренској сложености.

x садржи све исте цифре

Приметимо да је сваки број који се може добити као подстринг облика 111\ldots 1\cdot x, где јединица има најмање једна, а највише седам. Да би овакав број био прост, мора један од бројева 111\ldots 1 и x да буде једнак 1 (иначе може да се запише као производ два природна броја већа од 1, па није прост). Ако је први чинилац једнак 1, онда је за x\in \{2,3,5,7\} одговор 1. Ако је x=1, тада је такође одговор 1 (само број 11, за остале се може лако проверити на локалном рачунару). У супротном је одговор 0.

Главно решење

Ератостеновим ситом ћемо генерисати све просте бројеве не веће од 10^7. Након тога ћемо пролазити кроз сваки могући подстринг и у сложености \mathcal{O}(1) проверавати да ли је тај број прост. Уколико јесте, треба у помоћном низу да маркирамо да смо га избројали како не бисмо бројали понављања. Резултат је број маркираних елемената помоћног низа.