Processing math: 0%

wtorek, 6 sierpnia 2013

11123. Fibonacci [MWP4_2A]

Zadanie:
https://pl.spoj.com/problems/MWP4_2A

Skrócony opis problemu:
Mając daną liczbę x należy znaleźć 2 pierwsze liczby ciągu Fibonacciego, który kończy się właśnie liczbą x, ale jest możliwie najdłuższy. Jeśli jest wiele ciągów o tej samej długości, to należy wybrać ten o najmniejszym pierwszym elemencie.



Rozwiązanie naiwne:
Dla każdej pary liczb mniejszych od x sprawdzić czy ciąg Fibonacciego zaczynający się od nich kończy się liczbą x. Sprawdzamy to obliczając kolejne liczby ciągu i jeśli któraś liczba przekroczy x to znaczy, że dana para początkowych liczb ciągu nie jest tą właściwą. Jeśli natomiast któraś liczba będzie równa x to sprawdzamy długość tego ciągu (liczymy ją na bieżąco) i jeśli jest większa niż maksymalna, którą mieliśmy to aktualizujemy ową maksymalną i zapamiętujemy te pierwsze 2 wyrazy. Jeśli jest długości są równe, to porównujemy też wielkość pierwszych wyrazów i wybieramy parę z mniejszym pierwszym wyrazem.

Złożoność: O(x^2 \log_{\varphi} x) (przy \varphi = \frac{1+\sqrt{5}}{2}). Wynika ona z tego, że par liczb z przedziału \left<0;x\right> jest x^2, a dla każdej pary obliczamy kolejne liczby ciągu aż dojdziemy do x. Ich z kolei jest n. Największą długość ma oryginalny ciąg Fibonacciego (zaczynający się od 0 i 1), a wzór na n-ty wyraz ciągu to (ze wzoru Bineta):
F_n \approx \frac{1}{\sqrt{5}} \left(\frac{1+\sqrt{5}}{2}\right)^n
Nasze x to właśnie F_n, więc wychodzi nam x = \frac{1}{\sqrt{5}} \varphi^n. Po pominięciu stałych wychodzi: x = \varphi^n. Otrzymujemy więc: n = \log_{\varphi} x. Po przemnożeniu wychodzi x^2 \cdot \log_{\varphi} x.

Rozwiązanie sprytne:
Aby to szybciej obliczyć możemy skorzystać z wcześniej wspomnianej liczby phi (\varphi), czyli złotego podziału. Dzięki wzorowi Bineta możemy obliczyć n-tą liczbę Fibonacciego w czasie stałym (zakładając oczywiście, że potęgowanie jest wykonywane w czasie stałym). W nim potęgujemy liczbę \varphi. Możemy więc otrzymać następną liczbę Fibonacciego mnożąc poprzednią przez \varphi. Okazuje się, że działa to nie tylko dla oryginalnego ciągu Fibonacciego, ale też dla zaczynających się od dowolnej pary liczb. Adam umieścił dowód w komentarzu. My chcemy jednak otrzymać liczbę, która była przed x. Dzielimy więc x przez \varphi i patrzymy co mamy.
Np. dla x = 10 otrzymujemy: \frac{10}{\varphi} \approx 6.18. Po zaokrągleniu otrzymamy 6. Idąc od tyłu otrzymujemy: 10, 6, 4, 2, 2, 0. Jak się okazuje jest to najdłuższy taki ciąg z najmniejszym pierwszym elementem. Wynik dla tego przykładu to zatem: "0 2".

Złożoność: O(\log_{\varphi} x).

Ostateczny algorytm:
int main()
{
    int x, y;
    get(x);
    y = round(x/1.618033989);
    while(y <= x)
        y = x-y,
        x -= y;
    print(x, x+y);
}

1 komentarz:

  1. Warto napisać parę słów o tym, czemu to działa dla każdego ciągu o takiej konstrukcji jak Fibonacciego. Otóż weźmy taki ciąg: F_0=A;F_1=B;F_n=F_{n-1}+F_{n-2} i spróbujmy tą rekurencję rozwiązać za pomocą funkcji tworzących: F(x)=\sum_{n\ge 0} F_nx^n = \sum_{n\ge 0} F_{n-1}x^n + \sum_{n\ge 0} F_{n-2}x^n + Bx + A = \\ = x\sum_{n\ge 0} F_{n-1}x^{n-1} + x^2\sum_{n\ge 0} F_{n-2}x^{n-2} + Bx + A = \\ = xF(x)+x^2F(x)+Bx+A=\frac{A+Bx}{1-x-x^2}=\frac{A+Bx}{(1-\varphi x)(1-\tilde{\varphi}x)} Co po rozłożeniu na ułamki proste i rozwinięciu w szereg daje nam wzór zwarty: F_n=C\varphi^n + D\tilde{\varphi}^n gdzie C,D to stałe do wyliczenia z warunków brzegowych rekurencji. Drugi składnik ekstremalnie szybko zbiega do zera więc F_n to w przybliżeniu pierwszy i stąd ta metoda działa. Pozostaje kwestia dokładności, ale tutaj już nie czuję się na siłach żeby coś powiedzieć.

    OdpowiedzUsuń