Metoda gąsienicy

Zapewne każdy wie, jak wygląda gąsienica. Jest to typ larwy u motyli i błonówek z podrzędu rośliniarek, jedno ze stadiów rozwoju. Charakteryzuje się miękkim ciałem o robakowatym kształcie i metamerii homonomicznej oraz obecnością nieczłonowanych przydatków odwłokowych nazywanych posuwkami.

Co ma gąsienica do rozwiązywania problemów informatycznych? Otóż posiada mocarną umiejętność – pełzanie.

Kiedy stosować metodę gąsienicy? Intersujące przedziały

W bardzo wielu zadaniach napotykamy się na potrzebę przetwarzania informacji dla przedziałów, które spełniają dany warunek. Nazwijmy je interesującymi. W tym artykule będą nas obchodzić problemy, w których jeżeli warunek zachodzi dla fragmentu [a,b+1][a, b + 1] lub [a1,b],[a - 1, b], to zachodzi również dla [a,b][a,b]

Oznacza to, że dla każdego początku aa albo żaden fragment nie jest interesujący, albo istnieje taki koniec b,b, że wszystkie interesujące przedziały zawierają się w przedziale [a,b].[a, b].

Pełzanie

W celu rozwiązywania tego typu problemów możemy dla ii „pełzać” po kolejnych jj dopóki fragment [i,j+1][i, j + 1] spełnia dany warunek. Wówczas przetwarzamy informacje dla znalezionych przedziałów i "skracamy się", po czym kontynuujemy poszukiwania dla i+1.i + 1. Zauważmy, że skoro [i+1,j][i + 1, j] jest interesujące, to możemy wznowić „pełzanie” dalszym końcem od jj zamiast od i+1.i + 1. Ta optymalizacja zmniejsza koszt czasowy przeglądnięcia wszystkich interecujących przedziałów z O(n2)O(n ^ 2) do O(n).O(n).

Dlaczego?

pełzanie

Zastanówmy się, od czego zależy złożoność gąsienicy. Przy założeniach, że przetwarzanie informacji dla znalezionych przedziałów umiemy wykonywać w czasie stałym, liczba wykonanych operacji jest proporcjonalna do sumarycznej liczby "przesunięć" początków i końców. Skoro za każdym razem przesuwamy początek lub koniec o jeden w przód, łącznie każdy z nich przesunie się conajwyżej nn razy. Łączna liczba przesunięć wyniesie co najwyżej 2n,2n, co daje nam maksymalnie O(n)O(n) operacji. Gdybyśmy chcieli sprawdzać oddzielnie każdy przedział, w sumie musielibyśmy sprawdzić O(n2)O(n^2) możliwych przedziałów (dla nn możliwych początków i nn możliwych końców).

gąsienica, pełznąca po przedziałach

Zadanie - szukanie najdłuższego ciągu o niedużej sumie

Mając dane KK i ciąg nn liczb naturalnych aia_i podaj długość najdłuższego przedziału [p,k][p, k] takiego, że ap+ap+1+ap+2++ak1+ak<K.a_p + a_{p+1} + a_{p+2} + \dots + a_{k-1} + a_k < K.

Rozwiązanie metodą gąsienicy

Niech S(a,b)S(a,b) będzie równe sumie liczb na przedziale [a,b].[a,b]. Dla każdej pozycji ii szukamy najdłuższego fragmentu zaczynającego się na i,i, spełniającego podaną nierówność. Będziemy iterować się po kolejnych jj tak długo, aż S(i,j+1)<K.S(i, j + 1) < K.

Wówczas możemy zaktualizować wynik i poszukać najdłuższego fragmentu spełniającego podaną nierówność i zaczynającego się na i+1.i + 1. Jako, że S(i+1,j)<S(i,j)<K,S(i + 1, j) < S(i, j) < K, zaczniemy iterację od wcześniej wyliczonego j.j. Rozwiązanie znajdziemy w czasie O(n).O(n).

Pełznięcie w zadaniu

int najdluzszy_przedzial(int k, int a[]) { int j = 1, suma = a[1], wynik = 0; for (int i = 1; i <= n; i ++) { if (j < i) { j = i; suma = a[i]; } while (j + 1 <= n && suma + a[j + 1] < K) { suma += a[j + 1]; j ++; } wynik = max(wynik, j - i + 1); suma -= a[i]; } return wynik; }

Zadania