W zadaniu musimy obsłużyć trzy typy zapytań:
Początkowo $\forall_{1 \leq x \leq n}$ istnieje zbiór $X = { x }$.
W celu rozwiązania zadania, zbudujemy zmodyfikowaną struturę DSU. Przez $rep[v]$ oznaczymy krawędź wychodzącą z wierzchołka $v$, a przez $siz[v]$ rozmiar spójnej, której korzeniem jest $v$.
Ponieważ musimy umieć wykonywać operację ”$(2)$ $c$”, możemy zrezygnować z wykorzystania kompresji ścieżek - i tak niewiele by nam dała, gdyż potencjalnie ciągle byśmy musieli ją wycofywać. Możemy natomiast użyć często spotykanej techniki łączenia “mniejszy do większzego”. Dzięki temu uzyskamy strukturę o głębokości $O(log(n))$.
Cofnięcie pojedynczej operacji możemy wykonać w złożoności $O(1)$ - wystarczy utrzymywać stos wykonanych operacji $(1)$, utrzymujący parę wartości $( v, s)$, oznaczającą iż w wyniku danej operacji zmieniła się krawędź wychodząca z wierzchołka $v$, a rozmiar zbioru $X$, do którego należał $v$ przed tą operacją wynosił $s$. W takim razie cofnięcie realizujemy następująco:
Cofnięcie $c$ operacji możemy realizować jako $c$ pojedynczych cofnięć - każda operacja może być cofnięta co najwyżej raz, więc wykonamy łącznie co najwyżej $O(m)$ operacji.
Gdy łączymy dotychczas rozłączne zbiory $(A, B)$, tworzymy krawędź miedzy ich dotychczasowymi “korzeniami” (powiedzmy $a$ oraz $b$). Załóżmy b.s.o., że krawędź jest poprowadzona z $a$ do $b$. W takim razie, dla każdej pary ${ x, y }$ $x \in A, y \in B$, aktualny moment jest pierwszym momentem, w którym $x$ jest w tym samym zbiorze co $y$. Dla par $(x \in A, y \in A)$ (analogicznie dla $B$) nic się natomiast nie zmienia.
Oznacza to, że zmieniło się coś jedynie dla elementów, dla par wierzchołków $(x, y)$, dla których $lca(x, y) = b$ Możemy więc umieścić na dodawanej właśnie krawędzi informację o tym, w którym momencie została dodana. (przez $lca(x, y)$ rozumiemy pierwszy wierzchołek, który jest osiągalny zarówno z $x$ jak i z $y$).
Będziemy więc podczas łączenia ustawiać wartości na krawędziach w naszym DSU - waga będzie oznaczała momemt dodania. $time[v] = t$, oznacza, iż krawędź wychodząca z wierzchołka $v$ została dodana w czasie $t$.
Zapytanie typu $(3)$ o parę $(x, y)$ możemy teraz zrealizować znajdując krawędź, którą $x$ osiąga $lca(x, y)$ - nazwijmy ją $e_1$ oraz krawędź, którą $y$ osiąga $lca(x, y)$ - nazwijmy ją $e_2$. Powiedzmy, że aktualny moment (czyli liczba niecofniętych operacji typu $(1)$) to $t$. Wtedy naszym wynikiem jest: $(t - max(time[e_1], time(e_2)))$
Nie przypadkowo w zadaniu jest dość ciasny limiit pamięci - ma to zmusić do staranności. Trzeba choć trochę uważać, by go nie przekroczyć - spore benefity, zarówno czasowe, jak i (przede wszystkim) pamięciowe, powoduje użycie funkcji .reserve(max_rozmiar)
w przypadku wykorzystania wektorów ze standardowej biblioteki c++. Jeżeli tego nie zrobimy, vector
może (aby zapewnić szybką złożoność zamoryzowaną działania funkcji .push_back
), zaalokować 200%
pamięci, której faktycznie używa. Jak widać w omówieniu, do rozwiązani wystarczy jedynie kilka tablic / vector
-ów - nie ma sensu trzymać tablic, które nie wnoszą żadnej nowej informacji - a jeżeli już coś takiego robimy, zachęcam do upewnienia się, czy na pewno nie zbliżamy się zbytnio do limitu pamięci.
/usr/bin/time ./program < dane_wejsciowe > wynik_programu
- mówi ile czasu działał program i ile pamięci zużył (w kB
).
./oiejq.sh ./program < dane_wejsciowe > wynik_programu
- to samo, ale mierzy jak na olimpiadzie - skrypt ten można pobrać na stronie oi.edu.pl
.
jeżeli używasz windowsa
, zacznij używać linuxa
(najprostszym na to sposobem jest wsl
- warto podczytać o tym na internecie) (;