Mopadulo

Dzisiaj zajmiemy się rozwiązaniem zadania mopadulo z potyczek algorytmicznych 2021. W zadaniu tym pytają się nas na ile sposobów można podzielić ciąg na spójne fragmenty tak aby suma każdego fragmentu modulo p była parzysta. Dla przykładu dla ciągu [10, 1, 5, 8] i p = 11 są trzy takie podziały:

10   1   5   8
10   1 | 5   8
10 | 1   5 | 8

Zacznijmy od przestawienia algorytmu dynamicznego działającego w czasie O(n2). Niech D[i] oznacza liczbę sposobów na jaką możemy podzielić ciąg a[1 .. i]. Wtedy D[i] można policzyć w następujący sposób:

D[0] = 1
D[i] = sum D[j] po 0 <= j < i dla których suma elementów a[j+1 .. i] modulo p jest parzysta

W jaki sposób sprawdzić czy suma elementów a[j+1 .. i] modulo p jest parzysta? Można po prostu tę sumę policzyć i sprawdzić. Spróbujmy jednak zrobić to inaczej. Policzmy sumę prafiksową modulo p. Niech S[i] oznacza sumę elementów a[1 .. i] modulo p. Teraz zastanówmy się kiedy suma elementów a[j .. i] modulo p jest parzysta. Jak się domyślamy, musimy popatrzeć na S[j] oraz S[i]. Gdyby wartości nie były liczone modulo p, to suma elementów a[j+1 .. i] byłaby parzysta, gdyby S[j] oraz S[i] miały taką samą parzystość. Jednak ponieważ sumy liczymy modulo p, możemy mieć dziwną sytuację w której S[j] > S[i]. Można zauważyć że w takiej sytuacji parzystość musi być inna. Dla przykładu niech S[j] = 9, S[i] = 4 i niech p = 11. Wtedy suma a[j+1 .. i] modulo p musi być równa 6. Bo 9 + 6 (mod 11) = 4. A skoro jest równa 6, to znaczy że jest parzysta.

Zatem chcielibyśmy znaleźć sumę D[j] dla 0 <= j < i dla których S[j+1] ma taką samą parzystość co S[i] i dla których S[j] <= S[i] lub dla których S[j+1] ma inną parzystość niż S[i] i dla których S[j] > S[i]. Zapomnijmy na chwilę o parzystości i zastanówmy się jaka struktura danych umożliwia dodanie pary (S[i], D[i]) do struktury oraz którą można odpytać o sumę D[i] dla par w których S[i] <= x dla zadanego x. Taką strukturą danych jest na przykład zbalansowane drzewo poszukiwań binarnych w której dodatkowo pamiętamy sumę wartości D[i] w poddrzewie dla każdego wierzchołka (Cormen, rozdział “Wzbogacanie struktur danych” do powtórki). Ta struktura umożliwia zarówno wstawianie jak i odpytanie w czasie logarytmicznym. Inną strukturą, która umożliwia podobne operacje (i w dodatku jest prostsze do implementacji) jest drzewo przedziałowe. Teraz pozostaje jedynie stworzenie dwóch takich struktur (dla elementów parzystych i nieparzystych) i cieszenie się z algorytmu O(n lg n).

Back To Top