Diamond 2
문제 분석
피보나치 수는 다음과 같은 규칙으로 만들어지는 수열이다.
F1=1F2=1Fn+2=Fn+Fn+1
처음 몇 개의 항은 다음과 같다.
1,1,2,3,5,8,13,...
이때 다음 합을 구하는 문제이다.
i=1∑nj=1∑ngcd(Fi,Fj)
이때 gcd는 두 수의 최대공약수를 의미한다.
n 이 109 까지 커질 수 있으므로 무조건 O(n) 미만으로 깎아야 한다.
풀이
피보나치 수의 성질
우선 피보나치수의 성질을 이용해서 다음과 같이 정리하자.
i=1∑nj=1∑ngcd(Fi,Fj)=i=1∑nj=1∑nFgcd(i,j)
해당 성질의 증명은 GCD of Fibonacci Numbers에 있다.
아직도 시간 내에 풀기에는 불가능하므로 ∑을 하나 떼어내 보자.
이를 위해 gcd(i,j)=d를 만족하는 (i,j)의 개수를 세서 같은 d를 가진 피보나치 수끼리 묶어보자.
위 개수를 cntn(d) 라고 하면 아래와 같이 식이 정리된다.
d=1∑ncntn(d)Fd
cnt(d)에 대한 고찰
물론 일일히 모든 (i,j) 쌍을 검사할 수도 있지만, 그러면 시그마를 떼어낸 의미가 없다.
따라서 다음과 같이 cnt를 수정하자.
cntn(d)=cnt⌊n/d⌋(1)
그리고 cntn(1)=S(n) 이라고 표현하자. 그러면 식은 여기까지 정리된다.
d=1∑ncntn(d)Fd=d=1∑nS(⌊n/d⌋)Fd
S(n) 은 어떻게 구할 수 있을까?
우선 S(n) 은 n×n 격자에서 좌표가 서로소인 점들의 개수이다.
따라서 격자의 개수 n2에서 gcd(i,j)=2,...n 을 하나씩 빼주면 된다.
완성된 식은 다음과 같다.
S(n)=n2−d=2∑nS(⌊n/d⌋)
S(N) 개선
하지만 이것도 너무 느리다. 좀 더 개선해보자.
⌊n/d⌋를 잘 보면 d가 n에 가까워질 수록 변화가 거의 없다는 것을 알 수 있다.
⌊42/22⌋=⌊42/23⌋=⋯=⌊42/42⌋=1
여기에서 착안해서 함수를 두 부분으로 나눌 수 있다.
S(n)=n2−d=2∑⌊n⌋S(⌊n/d⌋)−k=1∑⌈n−1⌉(⌊n/k⌋−⌊n/(k+1)⌋)S(k)
이러한 트릭은 xudyh's sieve 또는 Mertens Trick 이라고 불리고, 전처리 과정을 포함한다면 O(n2/3) 으로 계산할 수 있다고 알려져있다.
다시 돌아와서
i=1∑nj=1∑ngcd(Fi,Fj)=d=1∑nS(⌊n/d⌋)Fd
S(n)을 개선할 때 사용한 아이디어를 다시 활용해보면 다음과 같이 정리할 수 있다.
d=1∑⌊n⌋S(⌊n/d⌋)Fd+k=1∑⌈n−1⌉(i=⌊k+1n⌋∑⌊kn⌋−1Fi)S(k)
풀고 있는 문제 리스트(Diamond II)