#include using namespace std; int n = 1; /* Řešení využívá upravenou implementaci treapu z kuchařky (http://ksp.mff.cuni.cz/kucharky/treapy), která je rozšířená o ukazatel na rodiče vrcholu (*parent), dále o hodnotu ve vrcholu (value) a součet podstromu s kořenem v konkrétním vrcholu (sum). Budeme chtít reprezentovat posloupnost, jejíž součet od nultého do i-tého prvku nám řekne vzdálenost z kořene do vrcholu v (předpokládejme, že víme, které i pro konkrétní v použít). Kdybychom ji měli vytvořit staticky z existujícího stromu, postupovali bychom pro vrchol v s hranou délky e následovně: 1. Při vstupu do vrcholu přidáme e na konec posloupnosti a poznamenáme si pozici, na kterou jsme přidávali (nazvěme ji i). 2. Rekurzivně se zavoláme a obdobně zpracujeme všechny naše potomky (pokud existují). 3. Na konec posloupnosti přidáme -e. Když se následně zeptáme na konkrétní součet pro vrchol v, všimneme si, že všechny vrcholy, které nejsou jeho předchůdci (tj. ty, které neleží na nejkratší cestě mezi ním a kořenem) jsme: a) ještě nenavštívili (protože se jedná o jeho potomky, ale ty voláme až po přidání e na konec posloupnosti), b) navštívili někdy předtím, ale zároveň jsou již uzavřené, neboť vrchol v se nenachází v jejich podstromu, tím pádem se pro ně dávno zavolal 3. krok výše popsané funkce. Tak nebo onak, vrcholy, které neleží na cestě mezi vrcholem a kořenem, mají v součtu od nultého do i-tého prvku vynulovanou (nezapočítanou) hranu. Díky tomu se nám podaří převést původní statický strom na reprezentaci posloupnosti. Na té si následně stačí spočítat prefixové součty a můžeme odpovídat na dotazy na vzdálenosti v konstatním čase. Co když ale budeme mít rostoucí strom, jako je ten ze zadání? Problém budeme řešit v základu pořád stejně: chceme, aby součet od 0 do nějakého i vracel vzdálenost vrcholu v od kořene. Jelikož se ale posloupnost může na různých místech rozšiřovat a měnit, má smysl ji opět reprezentovat pomocí stromu, tentokrát ale vyváženého, aby přepočítávání hodnot netrvalo příliš dlouho. My využijeme randomizovaný binární vyhledávací strom - treap. Ten si díky náhodě dokáže udržovat *průměrně* logaritmickou hloubku vzhledem k počtu prvků. Jeho podrobnější principy zde ale nebudeme vysvětlovat, více se o nich můžete dozvědět v naší kuchařce. Nyní k implementaci. Klíče treapu jako takové tady vůbec nevyužijeme, hodláme totiž třídit implicitně. Hodnoty uzlů v treapu budou reprezentovat buď kladnou, nebo zápornou hranu (e nebo -e). Pro přístup k vrcholu s konkrétním ID si budeme pamatovat ukazatele na uzly treapu v pomocném poli: na pozici 2*v bude uložen ukazatel na treapový uzel vrcholu s ID = v a hranou dlouhou e, na pozici 2*v+1 bude uložen ukazatel na uzel s ID = v a hranou dlouhou -e. Tato reprezentace nám umožní jednoduše prodlužovat konkrétní hrany: prostě na dvou pozicích přičteme/odečteme požadované hodnoty a následně je propagujeme v rámci treapu až do kořene. Nyní předpokládejme, že umíme treap rozdělit (=provést operaci split) na dva podstromy podle určitého klíče (ID). V prvním podstromu budou všechny vrcholy, jejichž ID by skončilo před nebo na pozici zadaného ID v posloupnostní reprezentaci. Ve druhém podstromu pak skončí zbytek. Následně ke spočítání součtu: pokud dokážeme udržet hodnotu součtu podstromů ve vrcholech treapu aktuální, můžeme z treapu jednoduše dostat podstrom s vrcholy s ID před nebo na pozici zadaného ID (vrcholu v) a potom vrátit součet tohoto podstromu. Tento podstrom totiž bude obsahovat pouze nultý až i-tý prvek, takže celý jeho součet musí dát rovněž součet úseku 0..i, o kterém víme, že se rovná vzdálenosti od vrcholu v do kořene. Nakonec k věšení nových vrcholů (s ID=n): treap si rozdělíme podle ID vrcholu, za který chceme vkládat. Následně sloučíme vzniklý levý podstrom s treapovými vrcholy (ID=n, hodnota=1) a (ID=n, hodnota=-1). Tento podstrom potom sloučíme s pravým podstromem a máme treap rozšířený o tento nový vrchol! */ struct Treap { Treap *left = nullptr, *right = nullptr, *parent = nullptr; int priority, value, sum; Treap(const int& v) { priority = rand(); value = v; sum = v; } }; // Počáteční strom - samotný kořen. // Vede do něj pomyslná hrana délky 0. Treap *root = new Treap(0); // Odkazy na jednotlivé vrcholy v treapu. vector nodeRef = {root, root}; void updateNode(Treap *in){ // Aktualizace součtu jediného uzlu v treapu. if(!in) return; in->sum = in->value; if(in->left) in->sum += in->left->sum; if(in->right) in->sum += in->right->sum; } void updateToRoot(Treap* in){ // Aktualizace součtu jediného uzlu až do kořene treapu. updateNode(in); if(in->parent != nullptr) updateToRoot(in->parent); } Treap* merge(Treap *left, Treap *right){ // Slučování dvou treapů rozšířené o aktualizaci ukazatelů na rodiče. if(!left || !right) return left ? left : right; if(left->priority < right->priority){ Treap *tmp = merge(left->right, right); left->right = tmp; tmp->parent = left; updateNode(left); return left; } else{ Treap *tmp = merge(left, right->left); right->left = tmp; tmp->parent = right; updateNode(right); return right; } } bool isLeftChild(Treap *c, Treap *p){ return p->left == c; } pair split(Treap* pivot){ // Rozdělení na vrcholy s ID, které se nachází dříve nebo na pozici ID pivot (left) a zbytek (right) Treap *left = pivot, *right = pivot->right; pivot->right = nullptr; updateNode(pivot); // odtrhneme pravý podstrom od levého, pokud existuje if(right != nullptr) right->parent = nullptr; while(pivot->parent != nullptr){ // postupujeme směrem ke kořeni Treap *parent = pivot->parent; if(isLeftChild(pivot, parent)){ // Pokud je rodič umístěn v posloupnosti až za pivotem (/aktuálním prvkem), // patří do pravého podstromu - stává se jeho novým kořenem. // Jelikož je aktuální prvek levým potomkem rodiče, znamená to, že všechny hodnoty (myšleno IDčka) // v podstromu aktuálního prvku jsou menší než hodnota tohoto rodiče. // Tím pádem původní right patří do levého podstromu nově vzniklého rightu. Treap *orig = right; right = parent; // Nezapomeneme správně přepojit ukazatele na rodiče. right->left = orig; if(orig != nullptr) orig->parent = right; } else{ // Symetricky v opačné situaci. Treap *orig = left; left = parent; left->right = orig; orig->parent = left; } updateNode(parent); pivot = parent; } return {left, right}; } void spread_edge(int a, int b) { // Hranu vedoucí do vrcholu a natáhneme o b. // Tedy na jedné pozici přičteme hodnotu b... nodeRef[2*a]->value += b; // Propagujeme (přepíšeme součty podstromů v relevantních vrcholech až do kořene) updateToRoot(nodeRef[2*a]); // ... a na druhé pozici ji zase odečteme. nodeRef[2*a+1]->value -= b; // opět propagujeme updateToRoot(nodeRef[2*a+1]); } void add_node(int a) { // Pod uzel s ID = a přidáme vrchol s (implicitním) ID = n a délkou hrany 1. Treap *b = new Treap(1); Treap *nb = new Treap(-1); // Vytvoříme dva nové odkazy na treapové vrcholy nodeRef.push_back(b); nodeRef.push_back(nb); Treap *leqA, *gtA; // Izolujeme podstromy leqA (ID <= a) a gtA (ID > a). tie(leqA, gtA) = split(nodeRef[2*a]); // Za podstrom leqA vložíme dva nově vzniklé vrcholy a sloučíme zpátky do celého treapu. root = merge(merge(merge(leqA, b), nb), gtA); } long long ask_len(int a) { // Vrátíme vzdálenost vrcholu a od kořene. Treap *leqA, *gtA; tie(leqA, gtA) = split(nodeRef[2*a]); long long res = leqA->sum; root = merge(leqA, gtA); return res; } unsigned int read_u32() { // Přečte 32-bitové little-endian číslo. unsigned char x[4]; fread(x, 1, sizeof(x), stdin); return x[0] | (x[1] << 8) | (x[2] << 16) | (x[3] << 24); } unsigned int read_u24() { // Přečte 24-bitové little-endian číslo. unsigned char x[3]; fread(x, 1, sizeof(x), stdin); return x[0] | (x[1] << 8) | (x[2] << 16); } int main() { srand(42); int q = read_u32(); long long int o = 0; for (int i = 0; i < q; i++) { int a = read_u24(); int b = getchar(); int x = (a + o) % (3 * n); if (x < n) { add_node(x); n++; } else if (x < 2*n) { spread_edge(x - n, b); } else { o = ask_len(x - 2*n); } } printf("%lld\n", o); return 0; }