일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 삼성 SW 역량테스트
- 시뮬레이션
- CI/CD
- r
- 접미사 배열
- 생활코딩
- Air Table
- ICPC
- 펜윅 트리
- 백준 1753번
- BFS
- REACT
- 우선순위 큐
- 고속 푸리에 변환
- 데이터 분석
- Cloud Pub/Sub
- LCS
- 다익스트라
- dp
- 이분탐색
- JavaScript
- Bit
- 삼성SW역량테스트
- Cloud Run
- 컴퓨터 구조
- 다이나믹 프로그래밍
- 그리디
- 종만북
- jpa
- 수학
- Today
- Total
코딩스토리
백준 2749번 - 피보나치 수 3 본문
https://www.acmicpc.net/problem/2749
본격적인 행렬 제곱 DP를 사용한 문제이다.
피보나치 수를 구하는 문제는 다이나믹 프로그래밍의 전형적인 문제 유형이다.
대부분 DP를 시작하는 사람들은 피보나치 수를 처음 접하면서 시작한다.
이 문제 역시 피보나치 수를 구하는 문제이다.
다른 점은 n 제한 하나이다.
이 문제의 n제한은 "1,000,000,000,000,000,000"이다.
이번에도 이 숫자가 내 통장잔고였으면.. 하지만 이번 건 스케일이 좀 남다르다.
내 통장잔고의 문제가 아니라 몇 년치 국가 예산보다 크지 않을까 싶다.
10^18은 조 단위를 훌쩍 뛰어넘는, 해가 10^20이니까 거의 해 단위와 가까운 숫자이다.
다시 생각해보면 O(n)으로 구한다면 컴퓨터로 해도 최소 10^10초가 걸린다는 이야기이다.
근데 DP를 O(n) 보다 짧은 시간 안에 구할 수 있나?
결론부터 말하면 가능하다.
사실 여기부터는 모르면 못 푸는? 알고리즘 스킬이 들어간다.
피보나치 수는 아래와 같다.
이 공식을 아래와 같이 행렬로 표현이 가능하다.
행렬을 풀어써보면 뭐.. 어렵지 않은 공식이다.
어렵지는 않은데 생각해내진 못하는 그런 공식..
그럼 이제 k=n이라고 가정해보자.
그럼 아래와 같은 공식이 유도된다.
처음에는 잉? 왜?? 물음표*100일 수 있는데 천천히 생각해보면 너무나 당연하다.
예를 들어 n = 3이라 가정해보자
위의 공식을 사용하면 위와 같다.
그럼 다시 F_2와 F_1을 구해보자.
이때 F_1 = 1이고 F_0 = 0 이므로 정리해보면 아래와 같다.
여기서 유추할 수 있는 건 n이 커지더라도 [ [1 1] [1 0] ] 행렬이 거듭제곱 형태로 표현되겠구나!이다.
그럼 다시 일반화해보면 다음과 같다.
행렬의 거듭제곱은 분할 정복 기법을 사용하면 O(logn)만에 구할 수 있기 때문에 이제 밑도 끝도 없는, 해에 가까운 연산 역시 1초 안에 구할 수 있게 되었다!
(행렬의 거듭제곱은 앞서 포스팅한 문제에 있기 때문에 따로 설명하진 않음)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll arr[2][2] = { {1,1}, {1,0} };
ll rem[2][2] = { {1,1}, {1,0} }; // 고정
void squared(ll a[][2], ll b[][2]) {
ll temp[2][2];
memset(temp, 0, sizeof(temp));
for (ll i = 0; i < 2; i++) {
for (ll j = 0; j < 2; j++) {
for (ll k = 0; k < 2; k++) {
temp[i][j] += a[i][k] * b[k][j];
temp[i][j] %= 1000000;
}
}
}
memcpy(arr, temp, sizeof(arr));
}
void reculs(ll x) {
if (x == 1) return;
if (x % 2 == 1) {
reculs(x / 2);
squared(arr, arr);
squared(arr, rem);
}
else {
reculs(x / 2);
squared(arr, arr);
}
}
int main() {
ll n;
cin >> n;
if (n > 1)reculs(n - 1);
cout << arr[0][0] << "\n";
}
10830번 문제와 거의 똑같은 코드지만 난이도는 3단계 차이이다.
결국 이런 문제를 풀어봤는지 아닌지에 따라 난이도의 차이가 이렇게 벌어지는 게 아닐까 싶다..
'알고리즘 > BOJ 문제 풀이' 카테고리의 다른 글
백준 2042번 - 구간 합 구하기 (0) | 2021.05.28 |
---|---|
백준 14289번 - 본대 산책 3 (0) | 2021.05.22 |
백준 10830번 - 행렬 제곱 (2) | 2021.05.19 |
백준 1484번 - 다이어트 (0) | 2021.04.30 |
백준 2602번 - 돌다리 건너기 (0) | 2021.04.24 |