https://www.acmicpc.net/problem/11444

 

11444번: 피보나치 수 6

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

www.acmicpc.net


    • N 의 범위가 엄청나기 때문에, 반드시 logN 의 알고리즘이 필요하다.
    • 피보나치 값을 logN 으로 구하는 방법에는 행렬곱을 이용한 방식이 있다.
    • [ 0 1     <- 왼쪽 행렬을 N 제곱 한 결과는 [ n-1  n           각 위치가 피보나치 값을 나타내게 된다.
    •   1 1 ]                                                       n   n+1 ]
    • n번째 피보나치 값은 행렬을 n 번 곱한 뒤 [0][1] 번째 / [1][0] 번째 위치가 되는 것이다.
    • 행렬의 곱은 분할정복이 가능하므로 n 을 2진수로 표현한 뒤 각 자릿수에 해당하는 행렬을 곱해줌으로써 logN 에 계산한다.

#include <iostream>

#define ull unsigned long long
#define MOD 1000000007

using namespace std;

ull cur[4] = {0, 1, 1, 1}, mul[4] = {0, 1, 1, 1};

void matmul(ull (&a)[4], ull (&b)[4], ull (&res)[4])    // a, b 는 곱할 행렬, res 는 결과값 담아줄 행렬
{
    ull temp[4];
    temp[0] = ((a[0]*b[0])%MOD + (a[1]*b[2])%MOD)%MOD;  // 행렬곱, 모듈러 연산 적용
    temp[1] = ((a[0]*b[1])%MOD + (a[1]*b[3])%MOD)%MOD;
    temp[2] = ((a[2]*b[0])%MOD + (a[3]*b[2])%MOD)%MOD;
    temp[3] = ((a[2]*b[1])%MOD + (a[3]*b[3])%MOD)%MOD;
    for(int i = 0 ; i < 4 ; i++) res[i] = temp[i];      // 행렬곱 결과를 갱신해줌
}

ull fibonacci(ull n)
{
    ull digit = 1;
    while(n >= digit)
    {
        if(n & digit) matmul(cur, mul, cur);            // 만약 자릿수 겹치면 현재행렬에 제곱행렬 곱해줌
        matmul(mul, mul, mul);                          // 제곱행렬은 매 자릿수마다 제곱됨
        digit = digit << 1;
    }
    return cur[1];
}

int main(void)
{
    ull n;
    cin >> n;
    cout << fibonacci(n-1) << endl;
}

'PS > BOJ' 카테고리의 다른 글

[백준] 11779 : 최소비용 구하기 2  (0) 2021.05.24
[백준] 4485 : 녹색 옷 입은 애가 젤다지?  (0) 2021.05.23
[백준] 10819 : 차이를 최대로  (0) 2021.05.21
[백준] 13172 : Σ  (0) 2021.05.19
[백준] 9935 : 문자열 폭발  (0) 2021.05.18

+ Recent posts