前回は、Project Euler で紹介されている問題25を解いてみました。前回は、フィボナッチ数列の定義に従い計算をしましたが、今回は、一般項を求めて計算します。
再掲載 Project Euler 問題25(1000桁のフィボナッチ数)
フィボナッチ数列は、次の漸化式で定義される。
$$F_n = F_{n-1} + F_{n-2}, \; F_1 = 1,\; F_2 = 1.$$
したがって、最初の12項は次となる。
$$\begin{align*}
F_1 &= 1 \\
F_2 &= 1 \\
F_3 &= 2 \\
F_4 &= 3 \\
F_5 &= 5 \\
F_6 &= 8 \\
F_7 &= 13 \\
F_8 &= 21 \\
F_9 &= 34 \\
F_{10} &= 55 \\
F_{11} &= 89 \\
F_{12} &= 144
\end{align*}$$
第12項 $F_{12}$ は、3桁になる最初のフィボナッチ数列の項となる。
フィボナッチ数列は、何項目で1000桁になるか求めよ。
考察
フィボナッチ数列の漸化式は、以下となります。
$$a_{n + 2} = a_{n + 1} + a_n,\; a_1 = 1,\; a_2 = 1$$
フィボナッチ数列の一般項 $a_n$ は、以下の式となります。
$$a_n = \frac{1}{\sqrt{5}}\left\{
\left(\frac{1 + \sqrt{5}}{2}\right)^n – \,
\left(\frac{1 – \sqrt{5}}{2}\right)^n \right\}$$
一般項の2番目の項は、$(1-\sqrt{5})/2 = -0.61803 \dots$ なので、$n$ 乗していけば、小さくなっていきます。例えば $10$ 乗すると、$((1-\sqrt{5})/2)^{10} = 0.00813 \dots$ となりほとんど無視できる値となります。つまり以下となります。
$$a_n \fallingdotseq \frac{1}{\sqrt{5}}
\left(\frac{1 + \sqrt{5}}{2}\right)^n$$
近似式のように見えますが、右辺の式を整数単位で切り上げれば正確な値になります。
$a_n$ が $10^{999}$ 以上となれば、1000桁になります。
$$a_n \fallingdotseq \frac{1}{\sqrt{5}}
\left(\frac{1 + \sqrt{5}}{2}\right)^n \geqq 10^{999}$$
この式の両辺の $\log_{10}$ を取ると以下のように計算できます。
$$\log_{10} \left( \frac{1}{\sqrt{5}}
\left(\frac{1 + \sqrt{5}}{2}\right)^n \right) \geqq \log_{10} 10^{999}$$
一般的に $\log (a/b) = \log a – \log b$、$\log a^n = n \log a$ が成り立ちます。また $\log$ の定義から $log_{10} 10 = 1$ となります。よって、以下のように式を変形することができます。
$$n \log_{10} \frac{1 +\sqrt{5}}{2}\, -\, \log_{10} \sqrt{5} \geqq 999$$
$$n \geqq \frac{999 + \log_{10} \sqrt{5}}
{\log_{10} \frac{1 +\sqrt{5}}{2}}$$
$n$ を切り上げて整数にすれば、求める解答となります。複雑ですが、この式は $O(1)$ で求めることができます。関数電卓があれば、手でも計算できます。
解答例
一般項から、直接 $n$ を計算することができました。求めた式を Python で計算してみましょう。プログラムは以下となります。
"""Project Euler Problem025"""
import math
N = 999
sqrt5 = math.sqrt(5.0)
answer = math.ceil((N + math.log10(sqrt5))/math.log10((1.0 + sqrt5)/2.0))
print("Problem025: " + str(answer))
2行目で桁数を変更できます。$n = 999$ でも、$9999$ でも、$99999$ でも計算結果はすぐに出力されます。
C では、以下となります。値を大きくできるように関係する変数は long long int 型にしました。math.h を使っているため、コンパイル時(リンク時)に、「-lm」オプションが必要となります。またオンライン実行環境では、コンパイルできない環境があるかもしれません(過去に紹介した paiza.iO では動作しました)。
#include <stdio.h>
#include <math.h>
int main(void)
{
long long int n = 999;
long long int term;
double sqrt5;
sqrt5 = sqrt(5.0);
term = (long long int)ceil(((double)n + log10(sqrt5))/log10((1.0 + sqrt5)/2.0));
printf("Problem025: %lld\n", term);
return 0;
}
C 版も6行目で桁数を変更できます。
結果を振り返る
前回は、問題の1000桁を増やしていきました。100万桁を超えると計算量が大きくなり計算をあきらめました。今回は、計算量が一定です。追加で結果を記します。
X 桁 | X 桁になる最初の項 |
10000(104) | 47847 |
100000(105) | 478495 |
1000000(106) | 4784969 |
10000000(107) | 47849717 |
100000000(108) | 478497194 |
1000000000(109) | 4784971964 |
求めた式を近似値で記すと以下となります。
$$n \geqq \frac{(X – 1) + 0.8047 \dots}{0.4812 \dots}$$
不等式の係数は複雑ですが一次式です。そのため、X が10倍になれば、それを超えるフィボナッチ数列の項も10倍になります。
倍精度の浮動小数点は、10進数で15桁ほどの精度を持ちます。その程度までは、この式で正確な項数を求めることができます。
最後に
フィボナッチ数列の一般項を求めるのは、高校数学の履修範囲となっています(数学B「数列」)。この知識を使って、問題の計算量を $O(N)$ から $O(1)$ にすることができました。
今回で、当初の目標であった Project Euler の最初の25問を解くことができました。この25問の難易度を振り返ります。