Binary Exponentiation¶
Binary exponentiation (also known as exponentiation by squaring) is a trick which allows to calculate
It also has important applications in many tasks unrelated to arithmetic, since it can be used with any operations that have the property of associativity:
Most obviously this applies to modular multiplication, to multiplication of matrices and to other problems which we will discuss below.
Algorithm¶
Raising
The idea of binary exponentiation is, that we split the work using the binary representation of the exponent.
Let's write
Since the number
So we only need to know a fast way to compute those. Luckily this is very easy, since an element in the sequence is just the square of the previous element.
So to get the final answer for
The final complexity of this algorithm is
The following recursive approach expresses the same idea:
Implementation¶
First the recursive approach, which is a direct translation of the recursive formula:
long long binpow(long long a, long long b) {
if (b == 0)
return 1;
long long res = binpow(a, b / 2);
if (b % 2)
return res * res * a;
else
return res * res;
}
The second approach accomplishes the same task without recursion.
It computes all the powers in a loop, and multiplies the ones with the corresponding set bit in
long long binpow(long long a, long long b) {
long long res = 1;
while (b > 0) {
if (b & 1)
res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
Applications¶
Effective computation of large exponents modulo a number¶
Problem:
Compute
Solution:
Since we know that the modulo operator doesn't interfere with multiplications (
long long binpow(long long a, long long b, long long m) {
a %= m;
long long res = 1;
while (b > 0) {
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
Note:
It's possible to speed this algorithm for large
Effective computation of Fibonacci numbers¶
Problem: Compute
Solution: For more details, see the Fibonacci Number article.
We will only go through an overview of the algorithm.
To compute the next Fibonacci number, only the two previous ones are needed, as
Applying a permutation times¶
Problem: You are given a sequence of length
Solution: Simply raise the permutation to
vector<int> applyPermutation(vector<int> sequence, vector<int> permutation) {
vector<int> newSequence(sequence.size());
for(int i = 0; i < sequence.size(); i++) {
newSequence[i] = sequence[permutation[i]];
}
return newSequence;
}
vector<int> permute(vector<int> sequence, vector<int> permutation, long long k) {
while (k > 0) {
if (k & 1) {
sequence = applyPermutation(sequence, permutation);
}
permutation = applyPermutation(permutation, permutation);
k >>= 1;
}
return sequence;
}
Note: This task can be solved more efficiently in linear time by building the permutation graph and considering each cycle independently. You could then compute
Fast application of a set of geometric operations to a set of points¶
Problem: Given
Solution: Let's look at how the different types of transformations change the coordinates:
- Shift operation: adds a different constant to each of the coordinates.
- Scaling operation: multiplies each of the coordinates by a different constant.
- Rotation operation: the transformation is more complicated (we won't go in details here), but each of the new coordinates still can be represented as a linear combination of the old ones.
As you can see, each of the transformations can be represented as a linear operation on the coordinates. Thus, a transformation can be written as a
that, when multiplied by a vector with the old coordinates and an unit gives a new vector with the new coordinates and an unit:
(Why introduce a fictitious fourth coordinate, you ask? That is the beauty of homogeneous coordinates, which find great application in computer graphics. Without this, it would not be possible to implement affine operations like the shift operation as a single matrix multiplication, as it requires us to add a constant to the coordinates. The affine transformation becomes a linear transformation in the higher dimension!)
Here are some examples of how transformations are represented in matrix form:
- Shift operation: shift
- Scaling operation: scale the
- Rotation operation: rotate
Now, once every transformation is described as a matrix, the sequence of transformations can be described as a product of these matrices, and a "loop" of
Number of paths of length in a graph¶
Problem: Given a directed unweighted graph of
Solution: This problem is considered in more detail in a separate article. The algorithm consists of raising the adjacency matrix
Note: In that same article, another variation of this problem is considered: when the edges are weighted and it is required to find the minimum weight path containing exactly
Variation of binary exponentiation: multiplying two numbers modulo ¶
Problem: Multiply two numbers
Solution: We simply apply the binary construction algorithm described above, only performing additions instead of multiplications. In other words, we have "expanded" the multiplication of two numbers to
Note: You can solve this task in a different way by using floating-point operations. First compute the expression
Practice Problems¶
- UVa 1230 - MODEX
- UVa 374 - Big Mod
- UVa 11029 - Leading and Trailing
- Codeforces - Parking Lot
- leetcode - Count good numbers
- Codechef - Chef and Riffles
- Codeforces - Decoding Genome
- Codeforces - Neural Network Country
- Codeforces - Magic Gems
- SPOJ - The last digit
- SPOJ - Locker
- LA - 3722 Jewel-eating Monsters
- SPOJ - Just add it
- Codeforces - Stairs and Lines