본문 바로가기
공부/알고리즘 문제풀이 기록

[백준 11778] 피보나치 수와 최대공약수

by 도리언 옐로우 2024. 4. 11.

 

11778번: 피보나치 수와 최대공약수

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

www.acmicpc.net

알고리즘 : 수학

난이도 : 플래티넘Ⅴ

 

1. 문제요약

  • 1,000,000,000,000,000,000 이하의 두 자연수 n,m이 주어진다.
  • n번째 피보나치 수와 m번째 피보나치 수의 최대공약수를 출력한다.
  • 다만 숫자가 너무 커지므로 1,000,000,007로 나눈 나머지를 출력한다.

2. 아이디어

  • $gcd(F_n,F_m)$을 구하는 문제인데, 이 문제를 풀려면 유클리드 호제법을 알고 있어야 한다.
    유클리드 호제법은 $gcd(x,y) = gcd(x-y,y)$을 의미하는데, 이를 일반화하면 $gcd(x,y) = gcd(y,z)$가 된다. (여기서 z는 x를 y로 나눈 나머지를 의미함)
  • 편의상 m이 n보다 크거나 같다고 하고, 문제풀이의 실마리를 얻기위해 $F_n$ 부터 $F_m$까지 나열해보자.
    $F_n$,   $F_{n+1}$,  $F_n+F_{n+1}$,  $F_n+2*F_{n+1}$,  $2*F_n+3*F_{n+1}$,  $3*F_n+5*F_{n+1}$ ...  
  • 피보나치수의 특성상 $F_m = F_{m-n-1}*F_n + F_{m-n}*F_{n+1}$이 됨을 알 수 있다. 이는 위 수열에서도 확인할 수 있다.
  • 여기에 유클리드 호제법을 적용하면 $gcd(F_n,F_m) = gcd(F_n, F_{m-n}*F_{n+1})$이 된다.
  • 연속하는 두 피보나치수는 서로소임을 유클리드 호제법으로 아래처럼 증명할 수 있다.
    $gcd(F_{n+1},F_n) = gcd(F_n, F_{n+1}-F_n) = gcd(F_n,F_{n-1}) = ... =gcd(2,1) = 1$
  • 따라서 $gcd(F_n,F_m)=gcd(F_n,F_{m-n}*F_{n+1})=gcd(F_n,F_{m-n})$ 을 얻는다.
  • 위 식이 유클리드 호제법과 유사한 형태임에 주목하면, 결국 $gcd(F_n,F_m) = F_{gcd(n,m)}$임을 알 수 있다.
  • 결국 피보나치 수를 구하는 문제로 환원되었는데, 이는 앞선 문제 '피보나치 수 6'을 참고하자.

3. 구현과정

  • 구현과정은 피보나치 수 6과 거의 동일하다.
  • 다만 추가되는 것은 최대공약수를 구하는 함수 gcd(x,y)이다. 이는 유클리드 호제법에 기반하여 재귀적으로 구현하면 된다.

4. 파이썬 코드

# 최대공약수 함수
def gcd(x, y):
    if y == 0:
        return x
    else:
        return gcd(y, x % y)

# A*B 함수
def mat_mul(A, B):
    c00 = (A[0][0] * B[0][0] + A[0][1] * B[1][0]) % 1000000007
    c01 = (A[0][0] * B[0][1] + A[0][1] * B[1][1]) % 1000000007
    c10 = (A[1][0] * B[0][0] + A[1][1] * B[1][0]) % 1000000007
    c11 = (A[1][0] * B[0][1] + A[1][1] * B[1][1]) % 1000000007
    return [[c00, c01], [c10, c11]]

# A^(2^n) 함수
def mat_pow(A, n):
    for _ in range(n):
        A = mat_mul(A, A)
    return A

# 2의 거듭제곱의 합 변환 함수
def sum_of_bin(x):
    tmp = bin(x)[2:]
    arr = []
    for i in range(len(tmp)):
        if tmp[i] == '1':
            arr.append(len(tmp) - i - 1)
    return arr

n, m = map(int, input().split())
Fibo = [[1, 1], [1, 0]]
ans = [[1, 0], [0, 1]]
target = sum_of_bin(gcd(n, m))
for i in target:
    ans = mat_mul(ans, mat_pow(Fibo, i))
print(ans[1][0])

'공부 > 알고리즘 문제풀이 기록' 카테고리의 다른 글

[백준 14503] 로봇 청소기  (0) 2024.05.15
[백준 14499] 주사위 굴리기  (0) 2024.04.25
[백준 11444] 피보나치 수 6  (0) 2024.04.11
[백준 1019] 책 페이지  (0) 2024.04.09
[백준 1107] 리모컨  (0) 2024.04.09