(출처: https://cp-algorithms.com/algebra/binary-exp.html)
\(a^n = a\times a\times a\times \cdots \times a\) 이렇게 a를 n번 곱해서 구하면 O(n)의 시간복잡도를 갖습니다.
Binary exponentiation(또는 Exponentiation by squaring)이라는 기법을 써주면 O(log n)의 시간복잡도로 a^n을 구할 수 있습니다. 이 방법은 분할정복을 이용한 거듭제곱이라고 부를 수 있겠습니다.
이 게시물에서는 n이 자연수일 때만 다루겠습니다. 그리고 (a*b)*c = a*(b*c) (결합법칙, Associativity)이 성립하는 원소만 적용 가능합니다.
\[3^{13} = 3^{1101_2} = 3^8 \cdot 3^4 \cdot 3^1\]
예를 들어 3^13을 구한다고 했을 때 13을 이진법으로 나타내면 1101이고, 13 = 1+4+8 입니다.
이 점을 보면 3을 squaring, 즉 이런식으로 3, 3^2, 3^4, 3^8, ... base를 제곱을 해가면서 결과에 포함되는 경우만 곱하면 답을 구할 수 있다는 걸 알 수 있습니다.
typedef long long ll;
ll binpow(ll a, ll b) {
ll res = 1;
while (b > 0) {
if (b & 1)
res *= a;
a = a * a;
b >>= 1;
}
return res;
}
반복문으로 짠 버전입니다. a(base)를 squaring을 하고, 동시에 b는 오른쪽 쉬프트를 해서 절반씩 줄여나갑니다. b&1일 때 해당 base를 곱해줍니다.
\[a^n = \begin{cases}
1 &\text{if } n == 0 \\
\left(a^{\frac{n}{2}}\right)^2 &\text{if } n > 0 \text{ and } n \text{ even}\\
\left(a^{\frac{n - 1}{2}}\right)^2 \cdot a &\text{if } n > 0 \text{ and } n \text{ odd}\\
\end{cases}\]
재귀 식으로 나타내면 위와 같습니다. 개념은 같습니다.
ll binpow_recur(ll a, ll b) {
if (b == 0) return 1;
ll res = binpow_recur(a, b/2);
if (b & 1)
return res * res * a;
else
return res * res;
}
재귀 버전입니다. 호출스택때문에 반복문 버전보단 약간 느릴 수도 있겠지만 호출스택이 터지거나 할 일은 없으니 걱정 안하셔도 됩니다. 스택 터지기 전에 오버플로우가 날겁니다.
'알고리즘 > 미분류' 카테고리의 다른 글
수학적 귀납법 (Mathematical Induction) (0) | 2020.06.08 |
---|---|
좌표 압축(Coordinate compression) (0) | 2020.05.09 |
요세푸스 문제(Josephus problem) (0) | 2019.05.08 |