피보나치 수¶
피보나치 수열은 다음과 같이 정의됩니다:
수열의 처음 몇 원소(OEIS A000045)는 다음과 같습니다:
성질¶
피보나치 수에는 흥미로운 성질이 많습니다. 그중 몇 가지는 다음과 같습니다:
- 카시니 항등식:
이는 귀납법으로 증명할 수 있습니다. Knuth의 한 줄짜리 증명은 아래의 2x2 행렬 형태에서 행렬식을 취하여 얻어집니다.
- "덧셈" 규칙:
- 앞의 항등식을 $k = n$인 경우에 적용하면 다음을 얻습니다:
-
이로부터 임의의 양의 정수 $k$에 대하여 $F_{nk}$가 $F_n$의 배수임을 귀납법으로 증명할 수 있습니다.
-
$n \ge 3$일 때는 그 역도 성립합니다: $F_m$이 $F_n$의 배수이면 $m$은 $n$의 배수입니다.
-
GCD 항등식:
- 피보나치 수는 유클리드 알고리즘에 대해 가능한 최악의 입력입니다(유클리드 알고리즘의 라메 정리를 참고하십시오)
피보나치 부호화¶
이 수열을 사용하여 양의 정수를 이진 부호어로 부호화할 수 있습니다. 제켄도르프 정리에 따르면, 모든 자연수 $n$은 피보나치 수의 합으로 유일하게 표현될 수 있습니다:
이때 $k_1 \ge k_2 + 2,\ k_2 \ge k_3 + 2,\ \ldots,\ k_r \ge 2$를 만족합니다(즉, 이 표현에서는 연속된 두 피보나치 수를 사용할 수 없습니다).
따라서 모든 수는 피보나치 부호화로 유일하게 부호화될 수 있습니다. 그리고 이 표현을 이진 부호 $d_0 d_1 d_2 \dots d_s 1$로 나타낼 수 있으며, 여기서 $d_i$는 표현에 $F_{i+2}$가 사용되면 $1$입니다. 부호어의 끝을 나타내기 위해 부호 뒤에 $1$을 덧붙입니다. 연속된 두 개의 1비트가 나타나는 곳은 이곳뿐이라는 점에 유의하십시오.
정수 $n$의 부호화는 간단한 탐욕 알고리즘으로 수행할 수 있습니다:
-
$n$ 이하인 수를 찾을 때까지 피보나치 수를 가장 큰 것부터 가장 작은 것 순으로 순회합니다.
-
이 수가 $F_i$였다고 합시다. $n$에서 $F_i$를 빼고 부호어의 $i-2$ 위치에 $1$을 놓습니다(가장 왼쪽 비트부터 가장 오른쪽 비트까지 0부터 인덱싱합니다).
-
나머지가 없을 때까지 반복합니다.
-
부호어의 끝을 나타내기 위해 마지막에 $1$을 덧붙입니다.
부호어를 복호화하려면 먼저 마지막 $1$을 제거합니다. 그다음 $i$번째 비트가 설정되어 있으면(가장 왼쪽 비트부터 가장 오른쪽 비트까지 0부터 인덱싱합니다) 수에 $F_{i+2}$를 더합니다.
$n^{\text{th}}$ 피보나치 수에 대한 공식¶
닫힌 형태의 식¶
Moivre가 이미 알고 있었지만 "Binet의 공식"으로 알려진 공식이 있습니다:
이 공식은 귀납법으로 쉽게 증명할 수 있습니다. 또한 생성 함수의 개념을 이용하거나 함수 방정식을 풀어서 유도할 수도 있습니다.
두 번째 항의 절댓값은 항상 $1$보다 작고 매우 빠르게(지수적으로) 줄어든다는 것을 바로 알 수 있습니다. 따라서 첫 번째 항의 값만으로도 "거의" $F_n$입니다. 이는 엄밀하게 다음과 같이 쓸 수 있습니다:
여기서 대괄호는 가장 가까운 정수로 반올림한다는 뜻입니다.
이 두 공식은 소수를 다룰 때 매우 높은 정밀도가 필요하므로 실제 계산에서는 거의 쓸모가 없습니다.
선형 시간에 피보나치 수 구하기¶
$n$번째 피보나치 수는 $n$까지 수를 하나씩 계산하여 $O(n)$에 쉽게 구할 수 있습니다. 하지만 뒤에서 살펴보듯이 더 빠른 방법도 있습니다.
$F_n = F_{n-1} + F_{n-2}$ 공식을 이용하는 반복적 접근법에서 시작할 수 있습니다. 구현할 때는 기저 사례인 $F_0$과 $F_1$에서 시작하여 가장 최근의 두 값만 저장하면 됩니다.
int fib(int n) {
int a = 0;
int b = 1;
for (int i = 0; i < n; i++) {
int tmp = a + b;
a = b;
b = tmp;
}
return a;
}
이렇게 하면 $O(n)$ 시간과 $O(1)$ 추가 메모리를 사용하는 선형 해법을 얻습니다.
행렬 형태¶
$(F_n, F_{n-1})$에서 $(F_{n+1}, F_n)$으로 넘어가기 위해 선형 점화식을 2x2 행렬 곱셈으로 나타낼 수 있습니다:
이렇게 하면 점화식을 반복하는 과정을 행렬 곱셈의 반복으로 다룰 수 있으며, 행렬 곱셈의 반복에는 유용한 성질이 있습니다. 특히,
여기서 $F_1 = 1, F_0 = 0$입니다. 실제로 다음이 성립하므로
행렬을 직접 사용할 수 있습니다:
따라서 $F_n$을 $O(\log n)$ 시간에 구하려면 행렬을 n제곱해야 합니다. (이진 거듭제곱 참조)
struct matrix {
long long mat[2][2];
matrix friend operator *(const matrix &a, const matrix &b){
matrix c;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c.mat[i][j] = 0;
for (int k = 0; k < 2; k++) {
c.mat[i][j] += a.mat[i][k] * b.mat[k][j];
}
}
}
return c;
}
};
matrix matpow(matrix base, long long n) {
matrix ans{ {
{1, 0},
{0, 1}
} };
while (n) {
if(n&1)
ans = ans*base;
base = base*base;
n >>= 1;
}
return ans;
}
long long fib(int n) {
matrix base{ {
{1, 1},
{1, 0}
} };
return matpow(base, n).mat[0][1];
}
빠른 더블링 방법¶
위의 행렬 식을 $n = 2\cdot k$에 대해 전개하면
다음과 같은 더 간단한 방정식을 얻을 수 있습니다:
따라서 위의 두 방정식을 사용하면 다음 코드로 피보나치 수를 쉽게 계산할 수 있습니다:
pair<int, int> fib (int n) {
if (n == 0)
return {0, 1};
auto p = fib(n >> 1);
int c = p.first * (2 * p.second - p.first);
int d = p.first * p.first + p.second * p.second;
if (n & 1)
return {d, c + d};
else
return {c, d};
}
p를 법으로 하는 주기성¶
$p$를 법으로 하는 피보나치 수열을 생각해 봅시다. 이 수열이 주기적임을 증명하겠습니다.
이를 귀류법으로 증명해 봅시다. $p$로 나눈 나머지를 취한 피보나치 수의 처음 $p^2 + 1$개 쌍을 생각해 봅시다:
$p$로 나눈 나머지는 $p$가지뿐이고 나머지 쌍은 많아야 $p^2$가지이므로, 이들 가운데 똑같은 쌍이 적어도 두 개 있습니다. 피보나치 수는 바로 앞의 두 수만으로 결정되므로, 이것만으로도 수열이 주기적임을 충분히 증명할 수 있습니다. 따라서 연속한 두 수의 쌍이 반복된다면, 그 쌍 뒤에 오는 수들도 같은 방식으로 반복된다는 뜻입니다.
이제 수열에서 인덱스가 가장 작은, 나머지가 같은 두 쌍을 고릅니다. 이 두 쌍을 $(F_a,\ F_{a + 1})$와 $(F_b,\ F_{b + 1})$이라고 합시다. $a = 0$임을 증명하겠습니다. 이것이 거짓이라면 바로 앞의 두 쌍 $(F_{a-1},\ F_a)$와 $(F_{b-1},\ F_b)$가 존재하며, 피보나치 수의 성질에 따라 이 두 쌍도 같아야 합니다. 그러나 이는 인덱스가 가장 작은 쌍을 골랐다는 사실에 모순되므로, 전주기가 없다는 것(즉, 수열은 $F_0$부터 주기적이라는 것)이 증명됩니다.
연습 문제¶
- SPOJ - Euclid Algorithm Revisited
- SPOJ - Fibonacci Sum
- HackerRank - Is Fibo
- Project Euler - Even Fibonacci numbers
- DMOJ - Fibonacci Sequence
- DMOJ - Fibonacci Sequence (Harder)
- DMOJ UCLV - Numbered sequence of pencils
- DMOJ UCLV - Fibonacci 2D
- DMOJ UCLV - fibonacci calculation
- LightOJ - Number Sequence
- Codeforces - C. Fibonacci
- Codeforces - A. Hexadecimal의 정리
- Codeforces - B. Blackboard Fibonacci
- Codeforces - E. 피보나치 수