문제 설명
피보나치 수는 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번째 피보나치 수를 구하는 프로그램을 작성하시오.
제한 사항
풀이
피보나치 수열을 구하는 문제는 간단하다.
설명에 나온대로 i-1, i-2를 더하면 된다.
하지만, 해당 문제에서는 N이 굉장히 크다.
즉, $O(N)$만에 답을 구해도 시간초과라는 것이다.
해당 문제를 풀기 위해서는 피사노 주기에 대해 알고 있어야 한다.
피사노 주기는 피보나치 배열을 특정한 수 M으로 나눈 나머지가 일정한 주기를 갖고 순환한다는 법칙이다.
즉, 해당 문제와 딱 들어맞는 상황이다.
그럼 주기의 길이만 구한다면 N번째 피보나치 수를 쉽게 구할 수 있다.
피사노 주기는 M이 $10^k (k=2)$보다 크다면 항상 다음과 같은 공식이 성립한다.
$P = 15 * (k-1)$
즉, 해당 문제는 M이 $1'000'000(10^6)$이기 때문에 P는 $15 * (6-1)$이 된다.
그럼 이제 0번부터 P번까지의 피보나치수열을 구하면 된다.
for (int i = 2; i < P; i++)
{
nums[i] = (nums[i - 1] + nums[i - 2]) % M;
}
이후, 정답은 주기에 의해 다음과 같이 된다.
cout << nums[N % P];
전체 코드
#include <stdio.h>
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <cmath>
#include <climits>
#include <queue>
#include <map>
#include <unordered_map>
#include <set>
using namespace std;
long long N;
const int M = 1'000'000;
const int P = 15 * (M / 10);
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> N;
vector<int> nums(P);
if (N == 0) cout << 0;
else if (N == 1) cout << 1;
else
{
nums[0] = 0;
nums[1] = 1;
for (int i = 2; i < P; i++)
{
nums[i] = (nums[i - 1] + nums[i - 2]) % M;
}
cout << nums[N % P];
}
return 0;
}