情報科学シケスラ fibonacci
DESCRIPTION
情報科学TRANSCRIPT
Information Science
Fibonacci Sequence is Magic
My Little
2014/01/28 tozangezan
はじめに• シケプリではありません ( 特に「プリ」が
違う )• コラムが延々と続く感じです• Ruby 書きたくないのでまともなソース
コードは載せません• 今回は漸化式で表される数列の計算をし
ます。
扱う漸化式• 以下の式を扱います
• このうちの k と求める項の番号 n が与えられるので、 an を答える方法を考えます。
• k=2 としたものが Fibonacci 数列です。• やり方はたくさんあります。• ( 以下、漸化式の係数をいじればすこし一般化できます )
関数の再帰を使うF(n){ if n < K then 1 else F(n-1)+F(n-2)+…+F(n-k)}
( 注 ) どの言語でもないので実行できません
• 再帰関数というのは定義した関数の中に同じ関数を呼び出す機構があるものだと思っておけばよいと思います。
• 漸化式をそのままコードに書いたらこんな感じになります。
遅い!• よく考えると、さっきのコードは少なくと
も「 1 を返す回数」だけ関数が呼ばれています
• これは答えの数と一致
• 例えば、 50 番目のフィボナッチ数は12586269025 で、このプログラムだと数分はかかりそう
• 計算量はだいたい O(αn) α は何かの定数• (Fibonacci 数列なら黄金数とか )
計算量:指数関数• 基本的に指数関数はダメです• 参考 http://www.youtube.com/watch?v=
Q4gTV4r0zRs
• 「 F(n) は何度も計算したところで毎回値同じでは…」という発想だけで高速になります。
関数のメモ化再帰をするF(n){ if “F(n) 計算済み” then “ 計算済みの値” if n < K then 1 else F(n-1)+F(n-2)+…+F(n-k)}
( 注 ) どの言語でもないので実行できません
• さっきのものに 2 行書きたし、無駄に計算しないようにしました。
• かなり速く動くようになります。
• まだ漸化式から想像がつくでしょう。
メモ化再帰での計算量• 関数は n の値あたり 1 回しか呼び出されな
いとしてよい• ( 正確には何回か呼ばれてるだろうけど最
初の 1 文だけだしどうでもいい )• n の値によって関数は 1 回ずつ呼ばれて計
O(n)• 関数 1 回では K 個の数の和を計算するので
O(k)• これら全部まとめると O(nk)• さっきより格段に速い !!
動的計画法を使うa[N] # 配列a[1]=a[2]=…=a[k]=1for i in (k+1)..n a[i]=a[i-1]+a[i-2]+…+a[i-k]
• 今度は関数を使わずに、ループをまわすだけでかいてみました。
• 本当は k 個の数の和を求めるのでもループを使うんですがね。
• コードが短め。
動的計画法での計算量• メモ化再帰と同じ• n 回くらいループをまわしていて、 (O(n))• それぞれでは k 個の数の和を計算 (O(k))
• 全部まとめると O(nk)• やっぱりメモ化再帰と同じ
問題• 以下の数列を考える
• このうちの k と求める項の番号 n が与えられるので、 an を 109+7 で割ったあまりを答えよ。
• k 100≦• n 100 000≦• 解答例: さっきのメモ化再帰 , 動的計画法
問題• 以下の数列を考える
• このうちの k と求める項の番号 n が与えられるので、 an を 109+7 で割ったあまりを答えよ。
• k 100≦• n 1 000 000 000 000 000 000≦• 解答例: さっきのメモ化再帰 , 動的計画法• さっきの方法では間に合わない!!
別世界• ここから先はコード書くの面倒なのでか
きません。• 情報科学の試験レベルなら別にこのくら
いでゴールして良いんじゃないでしょうか。• ここから先は k が大きいのには対処できま
せんが、 n が大きいのに対処できる方法です。
行列累乗• k=2 のとき、
• k=3 のとき、
• k=4 のとき、
行列累乗• こういう感じの形になるので、 an を得るた
めには 1 を並べたベクトルに n-k 回さっきのような行列をかければよい。
• 行列のかけ算• A, A2, A4, A8, A16, A32, … は同じものをかける処
理をすると、全部で O(log n) 回で求められる• ほかのものが得たいときは、例えば
A21= A × A4 × A16 などと 2 進数っぽくする。
行列累乗での計算量• ということは、行列のかけ算は O(log n) 回
行えばよい。• ここで使う行列は k 次正方行列なので 1 回
のかけ算は O(k3) かかる ( 定義に従って素直に計算するしかない )
• ということで全体での計算量は O(k3 log n)
問題• 以下の数列を考える
• このうちの k と求める項の番号 n が与えられるので、 an を 109+7 で割ったあまりを答えよ。
• k 100≦• n 1 000 000 000 000 000 000≦• 解答例: 行列累乗
問題• 以下の数列を考える
• このうちの k と求める項の番号 n が与えられるので、 an を 109+7 で割ったあまりを答えよ。
• k 1 000≦• n 1 000 000 000 000 000 000≦• 解答例: 行列累乗• さっきの方法では間に合わない!
整式の割り算を使う方法• 実は、この問題の第 n 項の値は、
「 xn を xk-xk-1-xk-2-…-1 で割ったあまりの係数の総和」に等しいです。 ( 証明略 )
xn を xk-xk-1-xk-2-…-1 で割ったあまりは、 x[n/2]
を xk-xk-1-xk-2-…-1 で割ったあまりを 2 乗し (nが奇数ならさらに x をかける ) 、その式をxk-xk-1-xk-2-…-1 で割ったあまりを求めればよいです。
整式の割り算を使う方法• k-1 次の多項式二つの乗算は O(k2) ででき
ます• この多項式の乗算は O(log n) 回呼ばれます
• ということで全体での計算量は O(k2 log n)です。
参考問題• http://tdpc.contest.atcoder.jp/tasks/
tdpc_fibonacci• けっこう多くの言語で (Ruby でも ) 解くこ
とができるっぽいです。• 解答例http://tdpc.contest.atcoder.jp/submissions/119305 (C++, 48 行 )
おまけ• 整式のかけ算は畳み込みなので FFT で計算
できて O(k log k) になります。• が、その整式を割ったあまりの計算がど
うやったら O(k2) から落ちるかわかりません。
• 誰か教えてください• あと、今回 109+7 で割ったあまりを求めた
のは答えが大きくなりすぎるからです。
The End