Przeskocz do treści

Delta mi!

Informatyczny kącik olimpijski

Ciąg arytmetyczny i Kamyczki

Bartosz Łukasiewicz

o artykule ...

  • Publikacja w Delcie: luty 2018
  • Publikacja elektroniczna: 1 lutego 2018
  • Wersja do druku [application/pdf]: (50 KB)

Tym razem omówimy dwa zadania z jubileuszowej X Olimpiady Informatycznej Gimnazjalistów w roku szkolnym 2015/2016.

Zadanie (Ciąg arytmetyczny). Dany jest ciąg |n liczb naturalnych a1,a2,...,an. Naszym zadaniem jest odpowiedzieć na q zapytań postaci: czy podsłowo |ai,ai+1,...,aj −1,a j jest ciągiem arytmetycznym?

Zacznijmy od przypomnienia definicji ciągu arytmetycznego. Otóż, |ai,ai+1,...,aj −1,a j jest ciągiem arytmetycznym, jeśli różnice między kolejnymi parami wyrazów są równe, czyli:

a − a = a − a = ... = a − a . i+1 i i+2 i+1 j j−1

Rozwiązanie ( O(q ⋅n) ). Najprostsze rozwiązanie polega na sprawdzeniu dla każdego zapytania, wprost z definicji, czy dany ciąg jest arytmetyczny. W tym celu wyznaczamy różnicę ciągu jako |r = a −a . i+1 i Następnie sprawdzamy, czy różnica między każdymi dwoma sąsiednimi wyrazami jest równa r. Powyższe rozwiązanie działa w czasie O(q ⋅n).

Rozwiązanie ( O(n + q ⋅log(n)) ). Niech r będzie ciągiem różnic między kolejnymi wyrazami:

a2 − a1,a3 − a2,a4− a3,...,an− an−1 r1 r2 r3 rn 1

Ciąg ai,ai+1,...,a j−1,a j jest arytmetyczny, jeśli odpowiadający mu ciąg różnic ri,ri+1,...,r j−1 jest stały (wszystkie wyrazy są równe). Niech zatem |x będzie najmniejszą liczbą w ciągu, zaś |y największą. Wówczas wszystkie różnice znajdują się w przedziale |[x; y]. Stąd, jeśli x = y, to ciąg różnic jest stały, czyli pierwotny ciąg jest arytmetyczny. Do wyznaczenia najmniejszego i największego elementu w przedziale można wykorzystać drzewo przedziałowe, które pozwala zrealizować tę operację w czasie O(log(n)). Całe rozwiązanie działa w czasie O(n + q ⋅log(n)).

Rozwiązanie ( O(n + q) ). Okazuje się, że istnieje jeszcze szybsze rozwiązanie. Zauważmy, że ciąg różnic możemy podzielić na fragmenty o tych samych wartościach:

 1 2 k r1,r2,r3,r4,r5,...,rn−2,rn−1

Wówczas fragment ri,ri+1,...,r j−1 jest stały, jeśli wszystkie jego wyrazy należą do jednej grupy. Przejdźmy teraz do opisu generowania grup. Ustalmy, że pierwsza różnica tworzy pierwszą grupę. Pozostałe różnice będziemy przeglądali od lewej do prawej. Jeśli rozpatrywana różnica jest równa poprzedniej, to rozszerzamy poprzednią grupę, w przeciwnym przypadku tworzymy nową grupę. Rozwiązanie działa w czasie O(n + q).

Zadanie (Kamyczki). Danych jest n stosów kamyczków. Wysokości kolejnych stosów to a1,a2,...,an. Celem Małgosi, bohaterki zadania, jest opróżnienie wszystkich stosów. Dziewczyna w jednym ruchu może opróżnić jeden dowolnie wybrany stos (nazwijmy ten ruch A) lub zabrać po jednym kamieniu z wszystkich niepustych stosów (nazwijmy ten ruch B). Ile minimalnie ruchów musi wykonać dziewczynka, aby opróżnić wszystkie stosy?

Obserwacja. Istnieje optymalne rozwiązanie, w którym najpierw wykonujemy ruchy typu A, a następnie ruchy typu B.

Dowód. Weźmy dowolne optymalne rozwiązanie. Jeśli w tym rozwiązaniu istnieje taki ruch |x typu A, który występuje po jakimś ruchu typu B, to ruch x nie ma wpływu na pozostałe stosy oraz wcześniejsze ruchy typu B nie są konieczne do wykonania ruchu x. Stąd ruch x może zostać wykonany przed wszystkimi ruchami typu B.


Na mocy powyższej obserwacji w optymalnym rozwiązaniu najpierw wykonujemy ruchy typu A, a następnie ruchy typu B. Zauważmy, że maksymalna liczba ruchów typu A to n (jest co najwyżej n stosów do opróżnienia). Dla każdej możliwej liczby ruchów A (od 0 do n ) obliczmy minimalną liczbę ruchów B, która pozwoli opróżnić wszystkie stosy.

Zastanówmy się teraz, jak dla ustalonej liczby k, oznaczającej liczbę ruchów A, wybrać |k stosów do usunięcia tak, aby zminimalizować liczbę ruchów B. Zauważmy wcześniej, że po wykonaniu k ruchów typu A należy wykonać tyle ruchów B, ile wynosi wysokość najwyższego z pozostałych stosów. Stąd, aby zminimalizować liczbę ruchów typu B, należy wybrać |k najwyższych stosów, co minimalizuje wysokość najwyższego z pozostałych stosów.

Przejdźmy teraz do opisu sposobu znajdowania k najwyższych stosów. Kolejność stosów nie ma znaczenia, dlatego zacznijmy od posortowania ciągu |a rosnąco oraz ustalenia, że a0 = 0. Wówczas k najwyższych stosów to: |an−k+1,an−k+2,...,an. Zatem wynik dla ustalonego k to k + an−k (wykonujemy |k ruchów typu A, usuwając k najwyższych stosów, oraz |an−k ruchów typu B, opróżniając wszystkie pozostałe stosy, z których najwyższy ma wysokość an−k ).

Najmniejszą liczbą ruchów, potrzebną do opróżnienia wszystkich stosów, jest minimum z wyników dla każdego k od 0 do |n. Rozwiązanie działa w czasie O(n ⋅log(n)).