Introduction
Binary exponentiation allows us to calculate in time complexity instead of complexity.
Key idea
The idea is to reduce the problem size by repeatedly squaring the base and halving the exponent. This exploits the property:
Recursive algorithm
exponentiation.java
static int solve(int a, int n) {
if (n == 0) return 1; // Base case
if (n % 2 == 0) return solve(a * a , n / 2); // Recursive case
return a * solve(a * a, (n - 1) / 2); // Recursive case
}
Iterative algorithm
exponentiation.java
static int solve(int a, int n) {
int ans = 1; // Initialize the answer
while (n > 0) {
if ((n & 1) > 1) ans *= a; // If n is odd, multiply ans with a
a *= a; // Raise a's power by 1
n >>= 1; // Divide n by 2
}
return ans;
}
Applications
Applying permutation times
A permutation of size maps each element of a set to another element of the same set.
We need to compute the result of applying the permutation times denoted by
-
Key idea
- A permutation consists of cycles. For example: means: Each element eventually loops back to its starting position. Understanding these cycles allows us to compute the result efficiently.
- Treat the permutation as a transformation matrix . Applying times is equivalent to raising to the power denoted by . Binary exponentiation helps to calculate in time instead of time.
-
Iterative algorithm
static int[] permuteKTimes(int[] a, int k) {
int n = a.length;
int[] P = new int[n];
// Intialize P with input array
for (int i = 0; i < n; ++i) P[i] = a[i];
while (k > 0) {
if (k % 2 == 1) P = applyPermutation(P, a);
P = applyPermutation(P, P); // Square the permutation
k /= 2;
}
return P;
}
static int applyPermutation(int[] a, int [] b) {
int n = a.length;
int[] result = new int[n];
for (int i = 0; i < n; ++i) {
result[i] = b[a[i]];
}
return result;
}