Information
Science
Fibonacci Sequence is Magic

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=Q4gTV4r0
zRs
• 「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+=1
だけでかいてみまし
for 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-xk2-…-1で割ったあまりを求めればよいです。
整式の割り算を使う方法
• k-1次の多項式二つの乗算はO(k2)でできま
す
• この多項式の乗算はO(log n)回呼ばれます

• ということで全体での計算量はO(k2 log n)
です。
参考問題
• http://tdpc.contest.atcoder.jp/tasks/tdpc_fibo
nacci
• けっこう多くの言語で(Rubyでも)解くこと
ができるっぽいです。
• 解答例
http://tdpc.contest.atcoder.jp/submissions/1193
05 (C++, 48行)
おまけ
• 整式のかけ算は畳み込みなのでFFTで計算
できてO(k log k)になります。
• が、その整式を割ったあまりの計算がど
うやったらO(k2)から落ちるかわかりませ
ん。
• 誰か教えてください
• あと、今回109+7で割ったあまりを求めた
のは答えが大きくなりすぎるからです。
The End

情報科学シケスラ Fibonacci