BOJ 풀이

[BOJ/백준 2749/C++] 피보나치 수 3

Vfly 2023. 3. 7. 20:56

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

 

2749번: 피보나치 수 3

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

www.acmicpc.net

 

[문제 요약]

- 피보나치 수는 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으로 나눈 나머지를 출력한다.

- 시간 제한 : 1초

- 메모리 제한 : 128MB

 

[문제 풀이]

이번 문제는 n이 엄청 매우 큰 경우일때의 피보나치 수를 구해야 한다.

 

우리가 피보나치 수를 구하는 방법 대표적으로 2가지가 있다.

 

첫번째로는 다이나믹 프로그래밍 방법을 이용하여 아래와 같은 식으로 dp[i] = dp[i-1] + dp[i-2] ( i ≥ 2 )를 이용한 방법

 

 

두번째로는 재귀를 이용하여 f(n) = f(n-1) + f(n-2) 구하는 방법이 있다.

 

하지만 재귀의 경우는 dp보다 당연히 효율이 매우 안 좋고, dp방법의 경우도 n이 이번 문제와 같이 매우 큰 경우에선느 적용되지 않는 단점들이 존재한다.

 

따라서 새로운 방법으로 정답을 구해야 하는데 그 방법은 다음과 같다.

따라서 특정 행렬을 n제곱을 하면 우리가 원하는 Fn을 구할 수 있게 된다.

 

그렇다면 행렬을 n제곱을 빠르게 하는 방법을 찾아야 한다.

 

그 방법으로는 다음과 같다.

  • N제곱을 한다고 가정했을 때 N을 이진수로 나타낸다.
  • 이진수로 나타낸 N이 예를들어 1101(2)라고 하면, 앞에서 부터 1이면 행렬을 제곱 후 한 번 더 곱해준다, 0일 경우 제곱만 한다.
  • 1101(2)의 경우 십진수로 나타내면 13이다. 처음의 경우 0제곱상태로 가정하고 위의 과정을 적용하면 

 

[전체 소스 코드]

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>

#define INF 987654321
#define mod 1000000
#define pii pair<int,int>

using namespace std;

long long N, B;
vector<vector<long long>> origin;

vector<vector<long long>> multiple(vector<vector<long long>>& A, vector<vector<long long>> &B)
{
	vector<vector<long long>> result(2, vector<long long>(2, 0));

	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			long long sum = 0;
			for (int k = 0; k < 2; k++)
			{
				sum += (A[i][k] * B[k][j]) % mod;
			}
			result[i][j] = sum % mod;
		}
	}

	return result;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> B;

	string bit;
	while (B) 
	{ 
		bit.push_back((B % 2) + '0'); B /= 2; 
	}
	reverse(bit.begin(), bit.end());

	vector<vector<long long>> ans(2, vector<long long>(2, 0));
	ans[0][0] = 1; ans[0][1] = 0; ans[1][0] = 0; ans[1][1] = 1;

	origin.resize(2); for (int i = 0; i < 2; i++) origin[i].resize(2);
	origin[0][0] = 1; origin[0][1] = 1; origin[1][0] = 1; origin[1][1] = 0;

	for (int i = 0; i < bit.size(); i++)
	{
		ans = multiple(ans, ans);
		if (bit[i] == '1') ans = multiple(ans, origin);
	}

	cout << ans[1][0] % mod;
}

행렬 곱셈 , 피보나치 수가 합쳐진 환장의 문제