autor: iamtwz, billchenchina, CBW2007, CCXXXI, chinggg, Enter-tainer, eyedeng, FFjet, gaojude, Great-designer, H-J-Granger, Henry-ZHR, hsfzLZH1, Ir1d, kenlig, Konano, ksyx, luoguyuntianming, Marcythm, Menci, NachtgeistW, ouuan, Peanut-Tang, qwqAutomaton, sshwy, StudyingFather, Tiphereth-A, TrisolarisHD, TRSWNCA, Xeonacid, Yuuko10032, Zhangjiacheng2006, Zhoier, Hszzzx, shenshuaijie, kfy666
Introduction
Fast exponentiation (fast exponentiation), also called binary exponentiation (binary exponentiation) or exponentiation by squaring (exponentiation by squaring), is a small technique for computing in time, whereas brute-force computation requires time.
This technique can be applied whenever multiplication involving is associative, such as modular exponentiation and matrix powers. See the later Applications section for details.
Process
Computing the -th power of means multiplying copies of together: . However, this method is not suitable when is too large or a single multiplication is too expensive. The idea of binary exponentiation is to divide the exponentiation task into smaller tasks according to the exponent's binary representation.
Example
Suppose we want to compute . Expanding it as a product requires multiplications. However,
so if we can quickly compute , we can compute with multiplications. Thus, we only need a fast way to compute the sequence for with exponents . This is easy because, except for the first element, every element in the sequence is the square of the previous element.
From this analysis, the computation of is as follows:
Only multiplications are performed in this process.
This is the basic idea of fast exponentiation. There are two common implementations.
Iterative version
Let the binary representation of be , that is,
where . Then,
Note that only terms with actually appear in the product.
According to this expression, we can first compute the values of the -th powers of in time, and then spend time multiplying into the final result the powers corresponding to binary bits equal to . This is the iterative implementation of fast exponentiation.
The pseudocode is as follows:
Computing fast exponentiation this way requires multiplications.
Recursive version
The same process can also be implemented recursively. Notice that the binary expansion of the exponent can be written recursively as
Therefore, can be computed recursively as
This is the recursive implementation of fast exponentiation.
The pseudocode is as follows:
Computing fast exponentiation this way requires recursive calls and also multiplications. Although the complexities are the same, the iterative version is faster in practice because recursion has some overhead.
Applications
Modular exponentiation
Luogu P1226 [Template] Fast Exponentiation
Given three integers , find , where .
This is a very common application; for example, it can be used to compute a modular multiplicative inverse. Since taking a modulus does not interfere with multiplication, we only need to take the modulus during the computation.
First, we can implement it directly according to the recursive method above:
Reference implementation
C++
long long binpow(long long a, long long b, long long p) {
if (b == 0) return 1;
long long res = binpow(a, b / 2, p);
if (b % 2)
return res * res % p * a % p;
else
return res * res % p;
}
Python
def binpow(a, b, p):
if b == 0:
return 1
res = binpow(a, b // 2, p)
if (b % 2) == 1:
return res * res * a % p
else:
return res * res % p
The second implementation is non-recursive. It multiplies into the answer the powers corresponding to binary bits equal to 1 during the loop. Although the theoretical complexities of the two are the same, the second is faster in practice because recursion incurs some overhead.
Reference implementation
C++
long long binpow(long long a, long long b, long long p) {
long long res = 1;
while (b > 0) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
Python
def binpow(a, b, p):
res = 1
while b > 0:
if b & 1:
res = res * a % p
a = a * a % p
b >>= 1
return res
Note
- The modulus is usually greater than . In the very special case where the modulus may equal , the case needs special consideration.
- When the exponent is very large, use Euler's theorem extension to reduce the exponent before computing.
Computing Fibonacci numbers
According to the recurrence of the Fibonacci sequence, we can construct a matrix to represent the transformation from to . Then, when computing the -th power of this matrix, we can use the idea of fast exponentiation to obtain the result in time. For more details, see Fibonacci sequence; an implementation of matrix fast exponentiation is available in Matrix-accelerated recurrence.
Multiple permutations
Problem statement
Given a sequence of length and a permutation, apply the permutation times to the sequence.
Simply raise the permutation to the -th power and apply it to the sequence. The time complexity is . For more details, see Composition of permutations.
Note
Construct a graph for the permutation and compute the -th power separately on each cycle (which is equivalent to taking modulo the cycle length); this solves the problem in time.
Accelerating operations on point sets in geometry
HDU 4087 A Letter to Programmers
Given points in three-dimensional space, apply operations to these points. There are 3 types of operations:
- Move a point along a vector (Shift).
- Scale the coordinates of the point proportionally (Scale).
- Rotate around a line (Rotate).
There is also a special operation: repeat an operation sequence times (Repeat). Repeat operations can be nested. Output the coordinates of every point after all operations.
Referring to Vectors and matrices, each operation can be represented by a transformation matrix, and a sequence of consecutive transformations can be represented by a product of matrices. A Repeat operation is equivalent to raising a matrix to the -th power. Thus, the final matrix of the entire transformation sequence can be computed in time. Applying it to the points gives total complexity .
Counting paths of fixed length
Problem statement
Given a directed graph with edge weights 1, find, for any two vertices , the number of paths of length from to .
Raise the adjacency matrix of the graph to the -th power. Then is the number of paths of length from to . The complexity is . See the Matrix page for details.
Modular integer multiplication
Problem statement
Given nonnegative integers and a positive integer , compute , where .
As with binary exponentiation, this time we express one of the factors as a sum of integer powers of 2. When multiplying a number by 2 and taking a modulus, we can use addition and subtraction to avoid integer overflow. This solves the problem in time. The recursive method is:
In practice, however, this method is inefficient because it introduces greater computational complexity. In actual programming, fast multiplication is usually used for multiplication when the modulus range is long long.
High-precision fast exponentiation
Prerequisite: Big-integer multiplication
Luogu P1045 [NOIP 2003 Primary Group] Mersenne Number
Given an integer (), find the number of digits of and its last digits (written in decimal). If there are fewer than digits, pad the higher positions with 0.
Implementation
#include <cmath>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 500;
int a[505], b[505], t[505];
// big-integer multiplication
void mult(int x[], int y[]) {
memset(t, 0, sizeof(t));
for (int i = 1; i <= x[0]; i++) {
for (int j = 1; j <= y[0]; j++) {
if (i + j - 1 > M) continue;
t[i + j - 1] += x[i] * y[j];
t[i + j] += t[i + j - 1] / 10;
t[i + j - 1] %= 10;
t[0] = i + j;
}
}
memcpy(b, t, sizeof(b));
}
// fast exponentiation
void binpow(int p) {
if (p == 1) {
memcpy(b, a, sizeof(b));
return;
}
binpow(p / 2); // (2^(p/2))^2=2^p
mult(b, b); // square b
if (p % 2 == 1) mult(b, a);
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int p;
cin >> p;
a[0] = 1; // record the number of digits in array a
a[1] = 2; // square 2
b[0] = 1; // record the number of digits in array b
b[1] = 1; // answer array
binpow(p);
cout << (int)(log10(2) * p) + 1 << '\n';
b[1] -= 1; // subtract 1 from the last digit
for (int i = M; i >= 1; i--) {
cout << b[i];
if ((i - 1) % 50 == 0) {
cout << '\n';
}
}
}
Precomputed fast exponentiation with a fixed base
When the base is fixed, we can use block decomposition to preprocess for some amount of time and answer each power query in time. This algorithm is also called light-speed exponentiation. The process is as follows:
- Select a number . Precompute the values of and and store them in two arrays.
- For each query , decompose as . Then , so the answer can be obtained in time.
Suppose the range of the exponent is . The block length is often chosen as or a nearby power of . Choosing gives the optimal preprocessing complexity , while choosing a power of allows bit operations to simplify the computation.
In particular, for modular exponentiation, having the same base also implies that the modulus must be the same. By Euler's theorem extension, for any modulus , the upper bound of the preprocessed exponent range is ; for a prime modulus , the upper bound is . The preprocessing complexity in both cases is .
Reference code
int a, mod, pow1[65536], pow2[65536];
void preproc() {
pow1[0] = pow2[0] = 1;
for (int i = 1; i < 65536; i++) pow1[i] = 1LL * pow1[i - 1] * a % mod;
int pow65536 = 1LL * pow1[65535] * a % mod;
for (int i = 1; i < 65536; i++) pow2[i] = 1LL * pow2[i - 1] * pow65536 % mod;
}
int query(int pows) {
return 1LL * pow1[pows & 65535] * pow2[pows >> 16] % mod;
}
Exercises
- UVa 1230 - MODEX
- UVa 374 - Big Mod
- UVa 11029 - Leading and Trailing
- Codeforces - Parking Lot
- SPOJ - The last digit
- SPOJ - Locker
- SPOJ - Just add it
Some content on this page is translated from the blog post Binary Exponentiation and its English translation Binary Exponentiation. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.