Розбір
Автор та розробник: Данііл Смельський
Пироголяндія "— це дерево, вершинами якого є пекарні, а ребрами є дороги. Тоді кожна вершина містить якесь невід'ємне число (значення вершини), а кожне ребро має один з двох станів: заблоковане чи розблоковане. Будемо вважати, що дерево має корінь у вершині .
Блоки 1-5
:
Запит типу : збережемо масив що відповідає за стан кожного ребра. При виклику запиту будемо змінювати стан ребра в цьому масиві.
Запит типу : запустимо із вершини та відвідаємо всі вершини, які знаходяться в компоненті цієї вершини. При цьому не будемо переходити по заблокованих ребрах. Змінимо значення відвіданих вершин.
Запит типу : можна викликати спочатку запит типу до вершини , щоб дізнатись її значення після застосування запиту типу . Після цього викликаємо запит типу , щоб змінити значення кожної вершини з компоненти вершини на нуль. Замінюємо значення вершини на підраховану суму.
Запит типу : повертаємо значення вершини .
Запит типу : запустимо із вершини та відвідаємо всі вершини, які знаходяться в компоненті цієї вершини. При цьому не будемо переходити по заблокованих ребрах. Додамо до відповіді значення відвіданих вершин.
Запит типу : запустимо із вершини та відвідаємо усі вершини які знаходяться у компоненті цієї вершини. При цьому не будемо переходити по заблокованих ребрах. Змінимо значення відвіданих вершин на нуль.
Запит типу : візьмемо будь-яку вершину з ненульовим значенням. Якщо такої не існує, то очевидно, що відповідь нуль. Інакше запустимо з неї , що буде повертати , якщо у піддереві вершини, з якої ми виходимо, є хоча б одна ненульова вершина, та інакше. Тоді, якщо ми переходимо до наступної вершини через заблоковане ребро та із неї повертає , то існує пара вершин (вершина, з якої ми запустили на початку та певна вершина з цього піддерева) така, що шлях між ними проходить через це заблоковане ребро. Отже, до відповіді слід додати .
Блок 6
:
під час ініціалізації розіб'ємо дерево на компоненти, що обмежені заблокованими ребрами, та дамо кожній компоненті свій номер. Оскільки запити типу відсутні, то множина вершин кожної компоненти змінюватися не буде. Також будемо зберігати значення кожної вершини не зовсім чесно: будемо зберігати її старе значення, а справжнє ми зможемо дізнатися, застосувавши певні операції.
Для кожної компоненти будемо зберігати певні значення:
integer sum "— сума значень вершин компоненти.
integer toAdd "— значення, яке треба додати до кожної вершини цієї компоненти, щоб воно було рівним справжньому значенню вершини.
integer lastVertex "— остання вершина компоненти, до якої було застосовано запит типу , або нуль, якщо до вершин цієї компоненти ще не застосовувалися запити типу .
clear "— булева змінна, що рівна , якщо до компоненти вже застосовували запит типу або запит типу , та нуль інакше.
cnt "— кількість вершин у цій компоненті.
Запит типу : візьмемо номер компоненти, у якій знаходиться вершина з номером , та додамо до значення .
Запит типу : можна викликати спочатку запит типу до вершини , щоб дізнатись її значення після застосування запиту типу . Після цього викликаємо запит типу , щоб змінити значення кожної вершини з компоненти вершини на нуль. Замінюємо значення вершини на підраховану суму. Змінимо значення .
Запит типу : оскільки кожна вершина зберігає своє старе значення, то треба до цього значення додати значення параметру з її компоненти.
Запит типу : оскільки значення вершини рівне + (де рівне значенню параметра компоненти, у якій знаходиться вершина з номером ), тоді суму значень вершин у компоненті можна визначити за формулою + .
Запит типу : розглянемо два випадки залежно від значення параметру .
: зробимо всі операції чесно: пройдемося по кожній вершині, замінимо її значення на нуль. Значення та також замінимо на нуль, а значення на .
: у нас є лише одна вершина, нечесне значення якої відмінне від нуля, і це вершина з номером . Отже, можна зробити те ж саме що у випадку , але замінити лише значення вершини з номером .
Блок 7
:
щоб розв'язати цей блок, можна модифікувати розв'язок до попереднього блоку. З'являється лише проблема із тим, що кількість та склад компонент можуть змінюватись.
Додамо до компонент два параметри:
"— вектор, що зберігає множину вершин, що знаходяться в цій компоненті.
"— вектор, що зберігає множину вершин, що знаходяться в цій компоненті та мають нечесне значення відмінне від нуля.
Через запит типу треба вміти поєднувати певні компоненти. Для цього застосуємо структуру даних (система неперетинних множин).
Розглянемо операцію , застосовану до компонент із номерами та у нашій . Вона повинна поєднати дві компоненти. Будемо вважати, що це лідери відповідних компонент у та вони різні. Ми будемо підвішувати компоненту з номером до компоненти з номером . Тоді нам треба якось поєднати їх параметри. Для цього спочатку очистимо список , а потім пройдемося по вершинах компоненти (список ), якщо значення , тоді спочатку замінимо значення вершини на нуль, а потім замінимо значення кожної з них на , додамо кожну з них до списку , а до додамо . Далі перекинемо вершини зі списку до списку , а також вершини зі списку до списку . Додамо до значення , а до значення . Підвісимо вершину до вершини у .
Які зміни відбулися у запитах?
Запит типу : змін немає, лише треба брати номер компоненти з .
Запит типу : після виклику запиту типу наш список повинен містити лише вершини , але він є порожнім, тому треба додати до нього вершину з номером , якщо її значення після цього запиту є ненульовим.
Запити типу та : відповіді можна знайти так само як у блоці .
Запит типу : у розв'язку до шостого блоку ми фактично проходилися по списку , але помітимо, що можна проходитись лише по списку та замінювати лише їх значення на нуль. Після цього слід видалити всі вершини зі списку .
Асимптотика такого розв'язку не буде вкладатися в обмеження, бо може перевищувати . Проте можна застосувати техніку -- (від меншого до більшого). Перш ніж оброблювати запит типу , давайте упорядкуємо та так, щоб було не меншим за . Тоді асимптотика такого рішення вже буде , що повинно проходити цей блок.
Блок 8
:
оскільки компоненти не будуть змінюватись у складі, можна побудувати додаткове дерево. Для цього давайте умовно видалимо з нашого дерева всі заблоковані ребра та стиснемо кожну компоненту, що утворилася в одну вершину в новому дереві. Усі нові вершини ми якось пронумеруємо, а також для кожної вершини початкового дерева запам'ятаємо номер вершини, до якої вона увійшла в новому дереві. Тепер додамо до цього дерева заблоковані ребра, що ми видалили на початку його побудови. Підвісимо це дерево за будь-яку вершину. Будемо вважати, що вершина в новому дереві має чорний колір, якщо є хоча б одна вершина з ненульовим значенням у початковому дереві, що належить цій вершині в новому дереві. Переформулюємо запит сьомого типу: треба знайти мінімальну кількість ребер у новому дереві, які потрібно розблокувати, щоб на шляху між кожною парою чорних вершин усі ребра були розблоковані.
Розглянемо будь-яке ребро з нового дерева. За якої умови його треба розблокувати? Якщо у піддереві нижньої вершини (більш віддаленої від кореня) цього ребра є хоча б одна вершина чорного кольору та ззовні піддерева верхньої вершини є хоча б одна вершина чорного кольору, то це ребро треба розблокувати. Інакше не існує пари вершин чорного кольору, на шляху між якими лежить це ребро, отже, його розблоковувати не потрібно.
Запустимо з кореня нового дерева та випишемо вершини чорного кольору у порядку часу входження до них. Нехай це послідовність довжини . Уведемо нову функцію "— кількість ребер на шляху між парою вершин та у новому дереві. Звідси отримуємо формулу для визначення відповіді на запит сьомого типу: / . Чому це правильно? Знову розглянемо будь-яке ребро з нового дерева. Якщо існує вершина чорного кольору у піддереві нижньої вершини цього ребра та вершина чорного кольору ззовні верхньої вершини цього ребра, то це ребро увійде до суми рівно два рази, бо воно увійде в це піддерево та вийде з нього. Інакше воно жодного разу не увійде до цієї суми.
Отже, для розв'язання сьомого типу запитів треба вміти підтримувати цю суму. Оскільки відсутні запити першого типу, склад нового дерева змінюватися не буде, але вершини можуть змінювати колір. Давайте зберігати таку суму: Тоді нас цікавлять лише зміни, що відбуваються у послідовності . У нас може або додаватися новий елемент до послідовності, можливо, десь у середину, або видалятися.
Випишемо в окремий масив усі вершини нового дерева у порядку часу входження до них при виклику з кореня. Тобто коли входить у нову вершину, поточний час збільшується на . Звідси "— час входження до вершини , а "— час виходу з вершини . Особливість цього порядку в тому, що всі вершини з піддерева вершини зустрічаються на відрізку у цій послідовності. Для кожної вершини запам'ятаємо позицію, на якій вона зустрічається у цьому масиві. Якщо вершина має чорний колір, то в додатковому масиві на її позицію поставимо одиничку, інакше нуль. Як треба оброблювати зміни у новому дереві?
Зміна кольору вершини з чорного на білий. Припустимо, що ця вершина зустрічалась на позиції в послідовності . Тоді у формулу для знаходження значення вона могла увійти максимум у двох доданках.
:
:
Тоді нам треба перевірити можливі випадки та вміти знаходити відстань між двома вершинами, а потім віднімати їх від суми. Для цього можна скористатися додатковими структурами даних.
Зміна кольору вершини з білого на чорний аналогічна, але тепер треба додавати числа до нашої суми.
щоб дізнатися відповідь на запит сьомого типу, до нашої суми треба додати значення , а потім поділити суму на два. Щоб знайти першу та останню чорну вершини, звернемося до нашого масиву з нуликів та одиничок. Можемо на цьому масиві побудувати дерево відрізків та швидко знаходити наші вершини (можна зробити , але дерево відрізків нам знадобиться для вирішення наступних блоків).
Блок 9
:
Випишемо всі вершини нашого дерева у порядку часу входження в них у , викликаного з кореня дерева. Тепер для кожної вершини у додатковому масиві, на позиціях де вони зустрічаються у масиві часу входження, будемо зберігати кількість заблокованих ребер на шляху від кореня до цієї вершини. Тоді всі вершини з піддерева вершини , що ще й знаходяться в компоненті вершини , мають те ж саме значення у додатковому масиві, що й значення вершини . Згадаємо, що вершини піддерева вершини знаходяться на відрізку у масиві обходу . Важливий факт, який треба помітити, "— значення вершини на цьому відрізку в додатковому масиві мінімальне. А отже, усі вершини, що мають мінімальне значення на цьому відрізку, знаходяться в компоненті вершини . Тобто якщо визначити в кожній компоненті її корінь (найближчу вершину до кореня всього дерева), тоді ця компонента може бути визначена як множина вершин, що лежать на відрізку , та мають на ньому мінімальне значення.
Побудуємо на додатковому масиві дерево відрізків. Кожен лист цього дерева буде відповідати за певну вершинку початкового дерева, визначену за номером вершини у масиві обходу . У кожному листі дерева відрізків будемо зберігати пару параметрів:
значення вершини
значення вершини у додатковому масиві (кількість заблокованих ребер на шляху від кореня дерева до цієї вершини)
А для інших вершинок дерева відрізків, що відповідають за певні проміжки, будемо зберігати наступні параметри:
сума значень усіх вершин з цього проміжку
мінімальне значення у додатковому масиві усіх вершин з цього проміжку
кількість вершин із мінімальним значенням на цьому проміжку
додаткові параметри, які умовно будемо називати обіцянками, щоб оброблювати певні операції на цьому дереві.
Перейдемо до розв'язку запитів:
Запити типу : подивимося на піддерево нижньої вершини цього ребра. Оскільки це ребро заблоковане, то воно додає до значення кожної вершини у додатковому масиві з цього проміжку. Тобто у дереві відрізків треба відняти від додаткового значення кожної вершини з цього проміжку, а це можна зробити використовуючи обіцянку "— число, на яке треба змінити додаткове значення кожної вершини з цього проміжку.
Запити типу : спочатку знайдемо корінь компоненти, у якій знаходиться ця вершина (як це зробити, дивіться після розв'язку блоку ). Нехай це вершина . Тоді до значення всіх вершин з компоненти вершини треба додати число . Оскільки ці вершини знаходяться на відрізку та мають мінімальне значення на ньому, то можна використати наше дерево відрізків, а саме ще один параметр для обіцянок "— число, на яке треба змінити значення кожної вершини з цього проміжку.
Запити типу : викличемо запит типу та . Запит типу лише замінив значення кожної вершини нашої компоненти на нуль, але значення додаткового параметру не змінилось. Отже, ми можемо додати до значення нашої вершини суму, підраховану в запиті типу , використавши наше дерево відрізків для проміжку .
Запити типу : використовуючи обіцянки, ми можемо дізнатися значення окремої вершинки, поступово спускаючись до неї по дереву відрізків та перекидаючи обіцянки до синів поточної вершини.
Запити типу : щоб дізнатися відповідь на цей запит, треба взяти суму значень усіх мінімумів на відрізку , де "— корінь компоненти вершини . Наше дерево відрізків уже зберігає цю суму для часткових проміжків, тому треба обрати головні з них та комбінувати.
Запити типу : будемо використовувати ще один параметр обіцянки в дереві відрізків "— чи було очищення кожної вершинки із мінімальним значенням на проміжку цієї вершинки дерева відрізків.
Слід зауважити, що на відміну від перекидування обіцянок у звичайному дереві відрізків (наприклад, звичайне додавання на відрізку), у цьому дереві відрізків ми будемо передавати обіцянку лише до синів із мінімальним значенням. Це стосується всіх обіцянок, окрім , яку слід передавати так само, як у звичайному дереві відрізків.
Отже, асимптотика такого рішення буде .
Як ефективно знаходити корінь кожної компоненти (із можливістю застосовувати запити першого типу):
знову випишемо всі вершинки у порядку обходу , а також повернемося до параметрів та . Заблокованими будемо називати ті вершинки, ребро з яких до предка у дереві заблоковане. Отже, зробимо ще один додатковий масив. Якщо вершина заблокована, або це вершинка (корінь дерева), то на її позиції будемо зберігати значення , а інакше будемо на її позиції зберігати нуль.
Як знаходити корінь компоненти: по-перше, усі предки нашої вершини будуть знаходитись на позиціях із номерами, меншими ніж , тобто на відрізку . А також, за властивістю та , значення повинно бути більшим або рівним за , якщо предок . Якщо є дві вершини, що є кандидатами на корінь нашої компоненти, то серед них треба обрати останню. Тобто лідер компоненти вершини це остання вершинка на відрізку , на позиції якої стоїть значення, що не менше за .
Блок 11
:
Єдине, що ми ще не розглянули, це як одночасно підтримувати запити першого та сьомого типу. Тепер у нас може змінюватися структура нашого нового дерева, що було побудоване на стиснутих компонентах, тому будувати нове дерево може виявитися занадто незручно. Зробимо копію нашого початкового дерева. І тепер чорними будуть вершини, що є коренями компонент, що містять хоча б одну вершину з ненульовим значенням. Тоді змінилась функція "— кількість заблокованих ребер на шляху між вершинами та . Проте ми можемо дізнатися її значення, використовуючи значення наших додаткових параметрів у дереві відрізків, що ми застосовували для вирішення перших типів запитів.
Тепер при виклику запиту типу треба вміти поєднувати дві компоненти та ділити одну компоненту на дві. Це ми можемо зробити певною послідовністю фарбувань вершин з білого кольору на чорний та навпаки, залежно від складу компонент.
Проте нам треба зауважити, що це ребро могло входити до значення . Давайте спочатку змінимо його стан, перерахувавши значення , а потім будемо поєднувати або ділити компоненти.
Зміна стану ребра з розблокованого до заблокованого: треба дізнатися, скільки разів це ребро буде входити до суми. Згадаємо про масив, де ми зберігаємо на позиціях чорних вершин та на позиціях білих. Тоді, якщо на відрізку зустрічається хоча б одна одиничка та на відрізку також зустрічається хоча б одна одиничка, тоді існує доданок , що має входити до значення . Тобто до цього значення слід додати . Так само якщо на відрізку трапляється хоча б одна одиничка та на відрізку також трапляється хоча б одна одиничка, то є доданок , що має входити до значення , тобто це значення треба знову збільшити на .
Зміна стану ребра із заблокованого на розблоковане: аналогічно до першого випадку, проте треба віднімати від значення .
Асимптотика рішення .