cppAlgorithmPS

피보나치 수열의 행렬 연산

2021.06.06


내용 참고


문제

첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.

n번째 피보나치 수를 출력한다.

F0=0, F1=1, Fn=Fn1+Fn2 (n2)F_0 = 0,\space F_1 = 1,\space F_{n} = F_{n-1} + F_{n-2} \space (n\ge2)


풀이

보통 피보나치 연산은 memoization 기법을 통해 O(N)O(N) 시간 만에 해결할 수 있지만, 이 문제의 경우 N의 크기가 매우 크므로, 제한시간안에 해결할 수 없다.

피보나치 연산을 행렬로 표현하여 O(log(N))O(log(N)) 시간 안에 해결할 수 있다.


(1) 피보나치 수열 정의

Fn+2=Fn+1+FnF_{n+2} = F_{n+1} + F_{n}

Fn+2=(11)(Fn+1Fn)\therefore F_{n+2} = \begin{pmatrix} 1 \quad 1\end{pmatrix}\begin{pmatrix} F_{n+1} \\ F_n\end{pmatrix}


(2) 일반적 성질

Fn+1=1Fn+1+0FnF_{n+1} = 1 \cdot F_{n+1} + 0 \cdot F_n

Fn+1=(10)(Fn+1Fn)\therefore F_{n+1} = \begin{pmatrix} 1 \quad 0\end{pmatrix}\begin{pmatrix} F_{n+1} \\ F_n\end{pmatrix}


(1), (2) 를 합치면 다음과 같이 쓸 수 있다.

(Fn+2Fn+1)=(1110)(Fn+1Fn)\therefore \begin{pmatrix}F_{n+2} \\ F_{n+1}\end{pmatrix} = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix}\begin{pmatrix}F_{n+1} \\ F_{n}\end{pmatrix}


let  Mn=(Fn+1Fn)let \space\space M_{n} = \begin{pmatrix}F_{n+1} \\ F_{n}\end{pmatrix}

Mn+1=(1110)Mn  (n0)\therefore M_{n+1} = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix}M_n \space\space (n \ge 0)

M0=(F1F0)=(10)M_{0} = \begin{pmatrix}F_1 \\ F_0\end{pmatrix}= \begin{pmatrix}1 \\ 0\end{pmatrix}

M1=(1110)M0=(1110)(10)=(11)M_{1} = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix} M_0 = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix} \begin{pmatrix}1 \\ 0\end{pmatrix} = \begin{pmatrix}1 \\ 1\end{pmatrix}

......

Mn=(1110)nM0=(1110)n(10)\therefore M_{n} = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix}^n M_0 = \begin{pmatrix}1 \quad 1 \\ 1 \quad 0\end{pmatrix}^n \begin{pmatrix}1 \\ 0\end{pmatrix}

이 때 생성되는 제곱 연산을 분할정복을 통해 계산하면, 피보나치 수열을 O(log(N))O(log(N)) 시간 안에 계산할 수 있다.


코드

// created by new command - lee@sungbin.dev
#include <iostream>
#define MOD 1000000007 // or 1000000
using namespace std;
typedef long long int ll;

struct M {
  ll d[2][2];
};

M multiply(M a, M b);
M pow(M a, ll n);

ll N;
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);

  cin >> N;
  M f = {{{1, 1}, {1, 0}}};
  f = pow(f, N);
  cout << f.d[1][0];
  return 0;
}

M multiply(M a, M b) {
  M c;
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
      int ans = 0;
      for (int k = 0; k < 2; k++) ans += (a.d[i][k] * b.d[k][j]) % MOD;
      c.d[i][j] = ans % MOD;
    }
  }
  return c;
}

M pow(M a, ll n) {
  if (n == 1) return a;
  if (n % 2 == 1) return multiply(a, pow(a, n - 1));
  M half = pow(a, n / 2);
  return multiply(half, half);
}