Raouf Ould Ali - Algerian Olympiad in Informatics
The Incarnadine Training - Incarnadine I
Binary Exponentiation
Lesson & Problems
© Raouf Ould Ali - Algerian Olympiad in Informatics 1
The Incarnadine Training Binary Exponentiation
Problem: Modular Exponentiation 💡
Consider the following problem:
• Write a program that outputs the remainder of the Euclidean division of 3N by M , where
1 ≤ N ≤ 1018 and 1 ≤ M ≤ 109 .
Straightforward Algorithm 📏
The straightforward algorithm would be the following:
#include <bits/stdc++.h>
using namespace std;
int main(){
long long N;
int M;
cin >> N >> M;
long long result = 1;
for(int i = 0; i < N; i++){
result = result * 3;
}
cout << result % M << endl;
}
After trying N = 10, M = 2000, for example, we may think that we’re done with the problem.
But when trying with some higher values, we start getting some nonsense values. For instance,
with N = 100, M = 32000, we get −9263, which doesn’t make sense at all! This has a very simple
explanation: as 3N becomes larger and larger, it exceeds the maximum integer C++ can store in
memory. Do you have any idea about how to solve this issue?
Modular Arithmetic to the Rescue 🦸
Remember that we’re not searching for the exact value, but only its remainder when divided by
M . Hence, we can use the fact that:
ab mod M = (a mod M )(b mod M )
This allows us to perform the computations by replacing at each step the variable result by its
division remainder by M :
© Raouf Ould Ali - Algerian Olympiad in Informatics 2
The Incarnadine Training Binary Exponentiation
#include <bits/stdc++.h>
using namespace std;
int main(){
long long N;
int M;
cin >> N >> M;
long long result = 1;
for(int i = 0; i < N; i++){
result = result * 3;
result = result % M;
}
cout << result << endl;
}
Retrying N = 100, M = 32000 gives us the intended solution (2001). Let’s goooo, we solved it!
🥳
Optimizing for Large N 🚀
Remember that the constraints say that N ’s upper bound is 1018 , but our algorithm obviously runs
in O(N ), which is too slow (You can notice it yourself trying N = 100000000000, M = 237564).
Can you figure out a solution?
Instead of multiplying N times the result variable by 3, you can notice the following facts:
32k = 3k × 3k
32k+1 = 3k × 3k × 3
This allows us to perform our calculation in O(log N ) instead of O(N ), as to compute 32k , we only
need to compute 3k once and just square it, as shown in the algorithm below:
#include <bits/stdc++.h>
using namespace std;
long long ThreePow(long long k, int M){
if(k == 0) return 1;
long long subRes = ThreePow(k / 2, M); // If k is odd, k/2 computes its floor, aka
(2j+1)/2 = j
if(k % 2) // k is odd
return (((subRes * subRes) % M) * 3) % M;
else // k is even
return (subRes * subRes) % M;
}
int main(){
long long N;
int M;
cin >> N >> M;
cout << ThreePow(N, M) << endl;
}
© Raouf Ould Ali - Algerian Olympiad in Informatics 3
The Incarnadine Training Binary Exponentiation
As we’re dividing each time k by 2, it is obvious that the number of calls to our function is
equal to blog2 N c + 2, giving a time complexity of O(log N ). (You can try some test cases by hand
on a piece of paper to figure it out.)
Your Favorite Section: Problems 📚
Time for problems; 50 points each!
• UVA 374 - Big Mod
• LeetCode 1922 - Count Good Numbers
• Codeforces 630I - Again Twenty Five!
• Codeforces Group Problem A (made by me 😉)
If You Wanna Read More 📖
These articles give further explanations but also more advanced examples:
• Binary Exponentiation - CP Algorithms
• Binary Exponentiation for Competitive Programming - GeeksforGeeks
© Raouf Ould Ali - Algerian Olympiad in Informatics 4