快速幂,二进制取幂(Binary Exponentiation,也称平方法),是一个在 $O(\log n)$ 的时间内计算 $a^n$ 的小技巧,而暴力的计算需要 $O(n)$ 的时间。

过程

首先将 $n$ 表示为二进制,例如

$$ 3^{13}=3^{(1101)_2}=3^8 \cdot 3^4 \cdot 3^1 $$

因为 $n$ 有 $[\log_2n]+1$ 个二进制位,所以只需要计算 $\log(n)$ 次乘法就可以计算出 $a^n$ 。

于是我们只需要知道一个快速的方法来计算上述 3 的 $2^k$ 次幂的序列。这个问题很简单,因为序列中(除第一个)任意一个元素就是其前一个元素的平方。举一个例子:

$$ \begin{align} 3^1 &= 3 \\ 3^2 &= \left(3^1\right)^2 = 3^2 = 9 \\ 3^4 &= \left(3^2\right)^2 = 9^2 = 81 \\ 3^8 &= \left(3^4\right)^2 = 81^2 = 6561 \end{align} $$

因此为了计算 $3^{13}$,我们只需要将对应二进制位为 1 的整系数幂乘起来就行了:

$$ 3^{13} = 6561 \cdot 81 \cdot 3 = 1594323 $$

一般实现

递归实现:

// a:底数; b:指数
long binpow(long a, long b) {
  if (b == 0)	return 1;
  long res = binpow(a, b >> 1);
  if ((b & 1) == 1) 
    return res * res * a;
  return res * res;
}

非递归实现:

// a:底数; b:指数
long binpow(long a, long b) {
  long res = 1;
  while (b > 0) {
    if ((b & 1) == 1)	res *= a;
    a *= a;
    b >>= 1
  }
  return res;
}

模意义下实现

计算 $x^n\bmod m$ 。

取模的运算不会干涉乘法运算,因此我们只需要在计算的过程中取模即可。

long binpow(long a, long b, long m) {
  a %= m;
  long res = 1;
  while (b > 0) {
    if ((b & 1) == 1) res = res * a % m;
    a = a * a % m;
    b >>= 1;
  }
  return res;
}

根据费马小定理,如果 $m$ 是一个质数,我们可以计算 $x^{n\bmod(m-1)}$ 来加速算法过程。

参考

[1] 快速幂 - OI Wiki