Василије мрзи геометрију
Аутор: Василије Новаковић
Текст и тест примери: Василије Новаковић
Анализа решења: Василије Новаковић
Тестирање: Милош Милутиновић
Решење кад p = [1, 2, 3, \dots, N] или p = [N, N-1, \dots, 1], N \leq 200, Q = 20000
Овај подзадатак може бити решен користећи један позив функције Pitaj.
Пошто је обилазак три тачке само смер у ком бисмо обилазили описани круг троугла који оне формирају, веома је јасно да важи да ако обрнемо редослед тачака којим обилазимо, и смер ће се обрнути.
Ово значи да би било која уређена тројка тачака које би у пермутацији p = [1, 2, 3, \dots, N] давала позитиван смер обиласка, давала негативан смер обиласка у пермутацији p = [N, N-1, \dots, 1], и обратно.
Дакле за овај подзадатак довољно је да међу тачкама имамо бар једну тројку која није колинеарна, и да знамо који би био њихов обилазак неким редоследом. Затим питамо за смер обиласка ове три тачке у том истом редоследу, и ако се поклапа са нашим, одговор је p = [1, 2, 3, \dots, N], а ако се не поклапа онда је p = [N, N-1, \dots, 1].
Анализа малих случајева
Да бисмо наставили даље са решењем, потребно је да пажљиво изаберемо тачке које ћемо узети.
Упит за оријентацију 3 тачке је поприлично апстрактан, па можемо изградити осећај за добру конструкцију размишљањем о малим случајевима:
Зашто је у задатку N \geq 4?
Узмимо N = 3:
-
Ако узмемо колинеарне тачке, било који упит нам враћа 0, што нам не даје никакве информације о пермутацији.
-
Ако узмемо три тачке које нису колинеарне, за сваку тројку (i, j, k), све ротације било које пермутације дају нам идентичне одговоре на упит, па их не можемо никако разликовати, што значи да је задатак нерешив за N = 3.
Први случај се проширује даље и за већи број тачака: ако су све тачке колинеарне, не можемо никако разликовати ниједне две пермутације.
Међутим, други случај нам налаже, да за већи број тачака морамо имати неке колинеарне тачке како бисмо могли да разликујемо између пермутација које су ротације једна друге.
Сад узмимо N = 4
Размишљајући о колинеарним тачкама, можемо закључити да постоје 3 различите конструкције:
- Све 4 тачке леже на једној правој.
- Не постоје колинеарне тројке.
- Постоји тачно једна колинеарна тројка, а четврта тачка не лежи на истој правој као и оне.
Разматрањем N = 3 одмах можемо одбацити случајеве 1 и 2, што нам оставља случај где једна тачка одступа од праве.
Даље решавање имајући ову конструкцију написаћемо у следећем подзадатку, јер је суштина ове анализе била да нађемо добру конструкцију.
Решење кад p_1 = 1, N \leq 200, Q = 25000
Размотримо генерализацију конструкције коју смо смислили у анализи малих случајева: ставићемо N - 1 тачку на исту праву, док ћемо прву тачку ставити ван те праве. Ради једноставности, можемо ставити све тачке на x-осу, где ће i-та тачка T_i = (i, 0), док ћемо нашу специјалну тачку ставити било где на y=1 (изнад осталих тачака)
Пошто важи p_1 = 1, ми одмах знамо која тачка нам је специјална, јер је само можемо ставити као прву тачку.
Можемо сад приметити да, пошто се специјална тачка налази изнад свих осталих, Pitaj(specijalna, x, y) ће вратити одговор 1 ако се T_{p_x} налази лево у односу на праву коју чине прве две тачке, што значи ако се T_{p_y} налази десно на x-оси у односу на T_{p_x}. Важи и супротно за одговор -1.
На овај начин можемо видети да ли је p_x < p_y, јер смо исписали тачке тако да лева тачка има мањи индекс од десне.
Ово нам дозвољава да нађемо засебно свако x, где се он налази у пермутацији, што нам самим тим једнозначно одређује и саму пермутацију.
Пошто овај начин тражења поретка тачака захтева n^2 / 2 упита, ово се комотно уклапа у ограничења подзадатка.
Решење кад N \leq 50, Q = 150000
У овом подзадатку важи Q > N^3 што нам дозвољава да урадимо сваки могући упит.
Посматрамо исту конструкцију као и прошли подзадатак. Разлика је што у овом подзадатку имамо више упита, али не знамо специјалну тачку од почетка.
Како би смо ми нашли која је то специјална тачка, можемо приметити да ће одговор на упит за ову конструкцију бити 0 ако и само ако специјална тачка није део тог упита.
У овом подзадатку имамо могућност да пробамо сваки могући упит, па можемо проћи кроз све упите и наћи тачку која се налази искључиво у упитима где одговор није 0.
Можемо на исти начин као и у претходном подзадатку наћи скривену пермутацију.
Решење кад N \leq 200, Q = 25000
Овај подзадатак захтева од нас да брже нађемо специјалну тачку.
Како би смо то урадили, можемо упитивати редом тачке i, i+1, i+2: ако је одговор на упит 1 или -1, то значи да је једна од ове 3 тачке специјална, након што смо свели број могућих специјалних тачака на 3 у O(N) упита, лако можемо закључити која од те 3 је заправо специјална, тако што питамо сваки пар две од те 3 тачке са неком споредном тачком (која је сигурно на правој), и ако је одговор 0, онда је тачка коју смо изоставили из упита специјална.
Користећи овај начин тражења успешно налазимо специјалну тачку након највише N + 1 упита.
Налазак тражене пермутације решава се на исти начин као и у претходна два подзадатка.
Решење кад Q = 24000
За овај подзадатак морамо у мањем броју упита да нађемо пермутацију. У суштини, ми не морамо директно за свако x засебно да налазимо колико тачака има лево од Т_{p_x}, већ тачке можемо сортирати све у исто време користећи било који сортинг алгоритам са поређењем, где свако поређење унутар сортирања кошта један упит. За овај подзадатак можемо искористити скоро било који алгоритам сортирања који користи O(N \cdot log_2 N) поређења.
Након што смо сортирали тачке можемо проћи кроз свако x како би нашли колико има тачака лево од p_x. Иако ово може да се уради и у линеарној сложености није потребно за овај задатак.
За једноставну имплементацију можемо направити нашу функцију поређења која користи Pitaj како би знала како да упореди две тачке, и ову функцију можемо проследити std::sort функцији чиме постижемо да не морамо да имплементирамо ниједан специфичан сортинг алгоритам за овај подзадатак.
Решење кад Q = 9400
За N = 1000, log_2 N је мало испод 10, што значи да би N \cdot log_2 N било око 10000 упита. Тренутни алгоритам преко овога има и тражење специјалне тачке које захтева O(N) упита, доводећи нас до око 11000 упита.
Како би смо овај број упита смањили на испод 9400, потребно је, прво, да нађемо алгоритам сортирања у O(N \cdot log_2 N) са довољно малим, гарантованим бројем поређења.
Посматрајмо како функционише алгоритам merge sort. Кад спајамо два дела, величина x и y, овај алгоритам ће направити x+y-1 поређење у најгорем случају, где је најгори случај се оба дела празне док не дођу до стања где оба имају по један елемент, где ће након поређења оба та елемента моћи да се убаце у спојени, сортирани низ.
Ово је најгори случај зато што смо за сваки елемент осим последњег направили по једно поређење. Али опет, постоји тачно \lceil log_2 N \rceil нивоа merge sort-а, и сваки елемент ће бити упоређен на сваком нивоу највише једном, што merge sort алгоритму даје релативно мали број поређења, који смо управо ограничили на N \cdot \lceil log_2 N \rceil поређења.
Међутим, број поређења је још нижи, због тога што претходна граница рачуна да је број поређења x + y за спајање, а не x + y - 1.
На најнижем нивоу, алгоритам спаја N делова величине 1, у N/2 делова величине 2. Ово значи да на том нивоу ми не правимо N поређења, него N - N / 2 поређења. Сличном логиком можемо за претпоследњи ниво добити N - N / 4, и тако даље.
Кад просумирамо ово за све нивое, добијамо укупно: N \cdot \lceil log_2 N \rceil - N + 1, и овим смо ефективно смањили број упита за N, што нас доводи до око 10000 упита.
Како бисмо даље спустили број упита испод 9400, можемо приметити да специјалну тачку можемо пронаћи у највише N/3 + 4 упита, уместо N + 1, тако што ако за i, i+1, i+2 добијемо да су колинеарне, не морамо да проверавамо ни за i+1, i+2, i+3 нити за i+2, i+3, i+4, већ можемо одмах прећи на i+3, i+4, i+5. Једини изузетак овде јесте да морамо ручно проверити N-2, N-1, N јер се може десити да нам је специјална тачка на месту N а да смо добили одговор 0 на N-4, N-3, N-2. Овом оптимизацијом смо смањили број упита за још 2 \cdot N / 3, што је довољно да уз merge sort успешно одреди било коју пермутацију у мање од 9300 упита.