Mastering Modular Arithmetic for Algorithms
Modular Exponentiation
Fast Exponentiation
Calculating powers can be slow. If you need to find , a simple loop that multiplies by itself times is too inefficient when is large, like . This would take far too long to compute. We need a faster way.
The solution is a technique called binary exponentiation, also known as exponentiation by squaring. The core idea might feel familiar if you've worked with binary lifting for finding the Lowest Common Ancestor in trees. In both cases, we leverage powers of two to make huge leaps instead of taking small steps.
Instead of multiplying times, we can compute in just multiplications.
The insight is to use the binary representation of the exponent . Any integer can be written as a sum of powers of two. For example, the number 13 in binary is 1101, which corresponds to:
Using this, we can rewrite :
This is a big improvement because we can find the terms very quickly. Each term is just the square of the one before it, all calculated modulo :
So, the algorithm has two parts: first, generate the powers of by squaring. Second, multiply the ones that correspond to the '1' bits in the binary representation of .
Iterative Implementation
We can implement this efficiently with a while loop. The loop continues as long as the exponent b is greater than 0. In each step, we check if the last bit of b is a 1. If it is, we multiply our current result by the current power of . Then, we square and right-shift b by one bit (which is the same as integer dividing by 2).
long long binpow(long long a, long long b, long long m) {
a %= m;
long long res = 1;
while (b > 0) {
// If b is odd, multiply a with result
if (b & 1) {
res = (res * a) % m;
}
// a becomes a^2, a^4, a^8, ...
a = (a * a) % m;
// b is shifted to the right (divided by 2)
b >>= 1;
}
return res;
}
Let's trace binpow(3, 13, 100):
res = 1,a = 3,b = 13(binary 1101).bis odd, sores = (1 * 3) % 100 = 3. Thena = (3 * 3) % 100 = 9,b = 6.res = 3,a = 9,b = 6(binary 110).bis even. Thena = (9 * 9) % 100 = 81,b = 3.res = 3,a = 81,b = 3(binary 11).bis odd, sores = (3 * 81) % 100 = 243 % 100 = 43. Thena = (81 * 81) % 100 = 6561 % 100 = 61,b = 1.res = 43,a = 61,b = 1(binary 1).bis odd, sores = (43 * 61) % 100 = 2623 % 100 = 23. Thena = (61 * 61) % 100 = 3721 % 100 = 21,b = 0.bis 0, the loop terminates. The function returns23.
Binary exponentiation (also known as exponentiation by squaring) is a trick which allows to calculate using only multiplications (instead of multiplications required by the naive approach).
Recursive Approach
Binary exponentiation can also be framed recursively. The logic is based on a simple observation:
- If is even, then .
- If is odd, then .
This gives us a clear recursive structure. We solve the problem for and use that result to find the answer for .
long long binpow_recursive(long long a, long long b, long long m) {
if (b == 0) {
return 1;
}
long long res = binpow_recursive(a, b / 2, m);
res = (res * res) % m;
if (b % 2 == 1) { // If b is odd
res = (res * a) % m;
}
return res;
}
Both the iterative and recursive versions have a time complexity of because they halve the exponent in each step. The iterative version is often preferred in competitive programming as it avoids the overhead of function calls and the risk of stack overflow for very large recursion depths, though that's rarely an issue for this algorithm.
What is the primary advantage of using binary exponentiation to calculate compared to a simple loop that multiplies by itself times?
The core idea of binary exponentiation is to leverage the binary representation of the exponent. If you are calculating , and the binary representation of 22 is 10110, which powers of will be multiplied together to get the final result?
With this technique, you can now handle calculations involving enormous exponents efficiently, a common requirement in number theory and cryptography problems.