[java,백준2749]피보나치 수 3 - 행렬 제곱과 DP를 활용한 최적화
행렬의 거듭제곱 성질과 DP를 활용하여 피보나치 수열의 N번째 수를 빠르게 구하는 문제입니다. 피사노 주기를 사용한 방법은 아닙니다.
아래 피보나치 수를 행렬로 바꿔 나타내보면 공식을 도출할 수 있다.(자세한 도출 과정은 생략. 필요시 구글링)
n=1일때 대입해보면 왼쪽처럼 {0,1},{1,1} 이 나옴을 알 수 있음.
만약 n=5면? {0,1},{1,1} 을 5번 행렬곱 하면 구해짐.
이 행렬식을 이용해서 피보나치 수 구하게 되면 정답 도출은 result[0][1] 이런식으로 fn 요소만 출력!
풀이
구현 주의사항
💡 다음 사항들을 반드시 체크해야 합니다:
- long 자료형 사용 필수
- 행렬 연산 적용
- 메모이제이션으로 중복 계산 방지
이 문제는 행렬의 거듭제곱을 분할정복으로 계산하고, DP로 중복 계산을 방지하는 방식으로 구현했습니다.
핵심 구현 사항
-
2x2 행렬의 곱셈 함수 구현
-
분할정복으로 거듭제곱 최적화
예로
200제곱x200제곱=400제곱
이라는 단순한 수학계산이지만 이 아이디어로 복잡도를 감소! -
HashMap을 활용한 메모이제이션 => top-bottom DP
행렬 곱셈
static Long[][] calMatrix(Long[][] m1, Long[][] m2, long n) {
Long[][] result = new Long[2][2];
result[0][0] = (m1[0][0]*m2[0][0] + m1[0][1]*m2[1][0]) % 1000000;
result[0][1] = (m1[0][0]*m2[0][1] + m1[0][1]*m2[1][1]) % 1000000;
result[1][0] = result[0][1]; // 피보나치 행렬의 특성
result[1][1] = (m1[1][0]*m2[0][1] + m1[1][1]*m2[1][1]) % 1000000;
map.put(n, result);
return result;
}
분할정복 피보나치
- 홀수의 이해를 돕자면, n=5일 경우 n/2=2 가 나옴.
우린 제곱수를 합하면 5가 나와야하니까 3이 필요!
그래서 n/2 에 +1 을 추가했을 뿐임. - 제곱수 합한다는 의미는
a^200 x a^200 = a^400
를 구할때 곱하기에서 지수는 더해진다는 수학적 특징을 말함.(지수:200+200=400)
static Long[][] fib(long n) {
if(n == 1) return matrix;
if(map.containsKey(n)) return map.get(n);
return n % 2 == 0 ?
calMatrix(fib(n/2), fib(n/2), n) :
calMatrix(fib(n/2), fib(n/2 + 1), n);
}
전체 코드
public class 피보나치수3_2749 {
static Long[][] matrix = // {0L, 1L}, {1L, 1L}가 할당되게 초기화 ㄱ
static HashMap<Long, Long[][]> map = new HashMap<>(); //메모이제이션
//행렬 연산 함수
static Long[][] calMatrix(Long[][] m1, Long[][] m2, long n) {
Long[][] result = new Long[2][2];
result[0][0] = (m1[0][0]*m2[0][0]+m1[0][1]*m2[1][0])%1000000;
result[0][1] = (m1[0][0]*m2[0][1]+m1[0][1]*m2[1][1])%1000000;
result[1][0] = result[0][1]; //=> 피보나치 수 행렬 특징임
result[1][1] = (m1[1][0]*m2[0][1]+m1[1][1]*m2[1][1])%1000000;
map.put(n, result);
return result;
}
static Long[][] fib(long n) {
//base condition
if(n==1) {
return matrix;
}
if (map.containsKey(n)) {
return map.get(n);
}
//recursion
if (n % 2 == 0) { //짝수
return calMatrix(fib(n / 2), fib(n / 2),n);
} else { //홀수
return calMatrix(fib(n / 2), fib(n / 2 + 1),n);
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
long n = Long.parseLong(br.readLine());
Long[][] result = fib(n);
System.out.println(result[0][1]);
}
}
주관적인 생각의 풀이이므로 틀린 부분 지적은 언제나 환영합니다⚡
댓글남기기