https://www.acmicpc.net/problem/11444
문제
피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다.
이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n ≥ 2)가 된다.
n=17일때 까지 피보나치 수를 써보면 다음과 같다.
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597
n이 주어졌을 때, n번째 피보나치 수를 구하는 프로그램을 작성하시오.
입력
첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.
출력
첫째 줄에 n번째 피보나치 수를 1,000,000,007으로 나눈 나머지를 출력한다.
예제 입력
1000
예제 출력
517691607
코드 (채점번호 30612370)
https://www.acmicpc.net/source/30612370
using System; using System.Numerics; using System.Collections.Generic; Dictionary<BigInteger, long> memo = new() { [0] = 0, [1] = 1 }; long Fibonacci(BigInteger num) { if (!memo.ContainsKey(num)) { if (num % 2 == 0) { long fnM1 = Fibonacci(num / 2 - 1); long fn = Fibonacci(num / 2); memo[num] = (fn * (2 * fnM1 + fn)) % 1_000_000_007; } else { long fnP1 = Fibonacci(num / 2 + 1); long fn = Fibonacci(num / 2); memo[num] = (fn * fn + fnP1 * fnP1) % 1_000_000_007; } } return memo[num]; } Console.WriteLine(Fibonacci(BigInteger.Parse(Console.ReadLine())));
설명
이 문제가 다른 피보나치 문제랑 다른 점은 입력 n값이 매우 크다는 것이다(최대 100경)
따라서 평소처럼피보나치 수열의 점화식
을 재귀로 구현했다간 스택 오버플로우가 발생하고 말 것이다.
그래서 이번 문제에서는 도가뉴 항등식(d'Ocagne's identity)을 사용할것이다.도가뉴 항등식
이 항등식을 잘 변형하면
아래와 같은 새로운 점화식을 얻을 수 있다.
새로운 피보나치 수열의 점화식
이렇게 (한쪽 항만 쭉 호출하는 것을 고려할 때) 번 호출하던 것이 번만 호출해도 되도록 바뀌었다!
이제 이를 메소드로 구현 해 보면 아래와 같다.
long Fibonacci(BigInteger num) { if (!memo.ContainsKey(num)) //값이 저장되어 있지 않으면 점화식을 통해 값을 구해준다. { if (num % 2 == 0) //F_2n { long fnM1 = Fibonacci(num / 2 - 1); //F_n-1 long fn = Fibonacci(num / 2); //F_n memo[num] = (fn * (2 * fnM1 + fn)) % 1_000_000_007; } else //F_2n+1 { long fnP1 = Fibonacci(num / 2 + 1); //F_n+1 long fn = Fibonacci(num / 2); //F_n memo[num] = (fn * fn + fnP1 * fnP1) % 1_000_000_007; } } return memo[num]; }
여담으로
fn * (2 * fnM1 + fn)
에서
최대 결과값은 (long
)로
long
자료형으로도 오버플로우를 일으키지 않고 충분히 담을 수 있다.
마찬가지로fn * fn + fnP1 * fnP1
에서의 최대 결과값은 로 문제 없다.추가적으로 피보나치 문제들의 기본이 되는 메모이제이션은 이번 문제에선 설명하지 않겠다.