BumCode

백준 7677 Fibonacci C++

문제 풀러가기 7677

피보나치수를 구하는 문제였다.
다만 N의 최대가 1,000,000,000이라 예전에 dp로 풀었을 땐 시간초과가 났다.

이산수학에서 행렬을 배우고 다시 보니 풀 수 있을 것 같아서 다시 풀어봤다.

풀이

  • 행렬의 거듭제곱을 이용해야한다.
  • 다만, N번 곱한다면 시간복잡도가 O(N) 이기에 이문제는 풀 수 없다.
    분할정복을 이용한 거듭제곱 알고리즘을 사용해 O(logN)으로 풀 수 있다.

분할정복을 이용한 거듭제곱
N이 짝수 A^N = A^(N/2) * A^(N/2)
N이 홀수 A^N = A^((N-1)/2) * A^((N-1)/2) * A

2*2라 그냥 2차원배열로 만들까 생각도 했지만 이번기회에 벡터사용에 익숙해지면 좋을 것 같아 벡터를 사용했다.

우선 행렬 matrix를 vector를 이용해 선언해주고

typedef vector<vector<int>> matrix;

연산자 오버로딩으로 행렬의 곱연산을 정의해줬다. 3중 for문이라 구현이 어려운 것 같다.

matrix operator*(const matrix& a, const matrix& b)
{
	matrix res(size, vector<int>(size)); //크기를 지정하면 백터는 0으로 초기화됨

	for (int i = 0; i < size; i++)
	{
		for (int j = 0; j < size; j++)
		{
			for (int k = 0; k < size; k++) //a의 행과 b의 열은 i,j,로 고정시키고 열과 행을 k로 바꾸면서 연산
			{
				res[i][j] += a[i][k] * b[k][j];
			}
			res[i][j] %= 10000; //10000으로 나눈 나머지.
		}
	}
	return res;
}

다음은 행렬과 정수를 받아 제곱을 해주는 함수이다.
반복문으로 구현할 수도 있지만 구현이 더 쉬운 재귀로 구현했다. 7677 N이 10억이라 해도 재귀는 30번만 돌기때문에 메모리는 충분하다.

matrix power(matrix a, int b) //a^b
{
	if (b == 1) return a;
	if (b % 2 == 0)
	{
		matrix half = power(a, b / 2); 
		return half * half;
	}
	else
	{
		matrix half = power(a, (b - 1) / 2);
		return half * half * a;
	}
}

전체코드

#include <bits/stdc++.h>
#define size 2
using namespace std;

typedef vector<vector<int>> matrix;


matrix operator*(const matrix& a, const matrix& b)
{
	matrix res(size, vector<int>(size)); //크기를 지정해서 0으로 초기화됨

	for (int i = 0; i < size; i++)
	{
		for (int j = 0; j < size; j++)
		{
			for (int k = 0; k < size; k++) //a의 행과 b의 열은 i,j,로 고정시키고 열과 행을 k로 바꾸면서 연산
			{
				res[i][j] += a[i][k] * b[k][j];
			}
			res[i][j] %= 10000; //10000으로 나눈 나머지.
		}
	}
	return res;
}

matrix power(matrix a, int b) //a^b
{
	if (b == 1) return a;
	if (b % 2 == 0)
	{
		matrix half = power(a, b / 2); 
		return half * half;
	}
	else
	{
		matrix half = power(a, (b - 1) / 2);
		return half * half * a;
	}
}

int main()
{
	int N; //0 <= N <= 1,000,000,000
	matrix res{ {1, 1}, 
				{ 1,0 } };
	matrix original{ {1, 1},
				{ 1,0 } };
	while (true)
	{
		cin >> N;
		if (N == -1) break;
		if (N == 0) { cout << 0 << '\n'; continue; }
		res = original;
		res = power(res, N);
		cout << res[0][1] << endl;
	}

	return 0;
}

행렬 곱연산은 size만 건드리면 되기에 다른 곳에도 써먹을 수 있을 것 같다.