백준
백준 11444번: 피보나치 수 6 (Graph, Matrix)
츄츄츄츄츄츄츄
2022. 12. 30. 23:13
문제링크:11444번: 피보나치 수 6 (acmicpc.net)
11444번: 피보나치 수 6
첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.
www.acmicpc.net
피보나치 수는 알다시피 점화식이 Fn=Fn-1+Fn-2 로 표현될 수 있다. 그렇다면 N번째 피보나치 수를 구하려면 반드시 0부터 N까지 더해가며 An을 구해야 할까? 나도 처음에 그래야 할 것이라고 생각했지만 답은 아니었다. 답은 행렬의 곱셈을 이용하여 N번째 피보나치 수의 일반항을 구할 수 있다.
위 식을 보면 행렬 [[Fn],[Fn-1]] 행렬에 행렬 [[1,1],[1,0]]을 곱하면 각각 다음 피보나치 수가 나오는 것을 알 수 있다. 이는 행렬의 곱셈 과정 중 1행에는 (1*Fn)+(1*Fn-1) 이 남고, 2행에는 (1*F*n)+(0*Fn-1) 가 남기 때문에 1행은 Fn+1이 남고 2행은 Fn이 남게 된다. 그렇다면 만약 피보나치 0번항, 1번항에 행렬[[1,1],[1,0]]을 i번 곱한다면 아래와 같은 모양이 될 것이다.
이렇게 해서 우리는 피보나치 수의 일반항을 구할 수 있다. 행렬의 제곱 연산을 분할 정복을 통해 구한다면, 시간 내에 답을 구할 수 있다.
import sys
N=int(sys.stdin.readline().rstrip())
mod=1000000007
fibo=[[1,1],[1,0]]
def matpower(mat,n):
sqrmat=[[0 for _ in range(2)] for _ in range(2)]
if n==0:
return [[0,0],[0,0]]
if n==1:
for i in range(2):
for j in range(2):
sqrmat[i][j]=mat[i][j]%mod
return sqrmat
if n%2==0:
for i in range(2):
for j in range(2):
val=0
for k in range(2):
A=mat[i][k]
B=mat[k][j]
val+=A*B
sqrmat[i][j]=val%mod
return matpower(sqrmat,n//2)
else:
for i in range(2):
for j in range(2):
sqrmat[i][j]=mat[i][j]%mod
return matmult(matpower(sqrmat,n-1),sqrmat)
def matmult(mat1,mat2):
multmat=[[0 for _ in range(2)] for _ in range(2)]
for i in range(2):
for j in range(2):
val=0
for k in range(2):
A=mat1[i][k]
B=mat2[k][j]
val+=A*B
multmat[i][j]=val%mod
return multmat
if N==0:
print(0)
elif N==1:
print(1)
else:
print(matpower(fibo,N-1)[0][0])
점화식이 덧셈으로 이루어져 있는 특징을 이용해 행렬 곱셈을 통해 일반항을 구할 수 있었다. 행렬 곱셈에 아직 익숙하지 않지만 점화식의 일반항을 빠르게 구하는 데에 사용 될 수 있을 것 같다.