03 Math1
03 Math1
Bundle 3
Math 1
Editor
Sadık Ekin Özbay
Reviewers
Yusuf Hakan Kalaycı
Burak Buğrul
Kadır Emre Oto
Contributors
Ömer Talip Akalın
1
Contents
1 Introduction 3
2 Number Theory 3
2.1 Primality Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Optimized Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Finding Primes up to N . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.2 Sieve of Eratosthenes Approach . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Modular Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.1 Properties of Modular Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 Inverse Modular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4 GCD - Greatest Common Divisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4.2 Euclidean Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 LCM - Least Common Multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5.2 GCD Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6 Benzout’s Identity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3 Factorization 21
3.1 Factorization Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.1.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.1.2 Optimized Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Prime Factorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4 Combinatorics 23
4.1 Factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2 Permutation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.1 Permutation Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.2 Generating permutations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 Combination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.3.1 Combination Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.3.2 Combination Calculation by Using Recurrence Formula . . . . . . . . . . . . . 30
4.4 Binomial Coefficient . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5 Exponentiation 33
5.1 Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2 Fast Exponentiation Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.2.1 Calculating Fibonacci with Fast Matrix Exponentiation . . . . . . . . . . . . . 35
2
1 Introduction
Next section is about the Number Theory. It will be quite a generous introduction to the questions
that are related to Mathematics.
Mathematics is quite essential to the programmers that want to improve themselves in the topic of
competitive programming. We are going to use these topics from graph theory to the subject of
strings. Therefore, a strong understanding of mathematics is fundamental.
2 Number Theory
Number theory is a study for the positive natural numbers. Numbers are split into several groups.
Number theory is related to the connection between these different groups of numbers [1].
It has a long history of development. The first tablet ever found by scientists was about the
Pythagorean triples. The tablet was created in 1800 BC by the Mesopotamian people. Ancient
Greek, China, and Islamic states have a critical effect on the growth of number theory [2].
The main question that comes to our minds is how to find out if the given number is prime or not.
Let us see a function that returns if the given number is prime or not.
We are going to test the number of n as 179424673 for each prime number algorithm. It is a very
large prime number. It allows us to see the run-time differences between the algorithms.
3
2.1.1 Naive Approach
Time Complexity: O(n)
The first thing that pops in our minds is iterating from 2 to n-1 and check if the given n is evenly
divisible by the current number. This is the naive approach to testing the prime.
For example, let’s say we have number A. Our plan in this algorithm is looping from 2 to A-1. For
each value in the loop, we will try to divide A by the current value. If the current value divides A
evenly, we can say A is not a prime number. We also know the divisor(factor) of A should be smaller
than or equal to A. Therefore, we should find at least one divisor of A, if there is one.
1 #include <iostream>
2 #include <time.h>
3
4 using namespace std;
5
6 bool isPrime(long long n) {
7 // 0 and 1 are not prime numbers. Therefore, we can return false directly.
8 if(n == 0 || n == 1) return false;
9 // Check until n
10 for(long long i = 2; i < n; i++)
11 if(n%i == 0)
12 return false;
13
14 // If nothing divides n, return true.
15 return true;
16 }
17
18
19 int main() {
20 // Read the input
21 long long n;
22 scanf("%lld", &n);
23
24 // Calculate the runtime of the isPrime function.
25 clock_t tStart = clock();
26 bool nIsPrime = isPrime(n);
27 printf("Time taken: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
28
29 // Prepare the output string.
30 string finalAnswer;
31 if(nIsPrime)
32 finalAnswer = "It is unquestionably a prime number.";
33 else
34 finalAnswer = "Hmm. I am not quite sure about that.";
35
36 printf("%s\n", finalAnswer.c_str());
37 return 0;
38 }
4
Output
Instead of iterating√from 2 to n-1, we can stop when the current number exceeds the square root of
the given number( n)
For example, we will test the number 100. In the naive approach, we were looping up to n-1.
However, we are checking redundant numbers. Since we are checking the primeness, we should start
checking from 2.
• The first factor is 2. Since we know 2 divides 100, we do not need to check 50. (2*50 = 100)
• The second factor is 5. Since we know 5 divides 100, we do not need to check 20. (5*20 = 100)
• The fourth factor is 20. However, we have already checked the complementary number of 20,
which is 5. - Checking 20 is unnecessary
• The fourth factor is 50. However, we have already checked the complementary number of 50,
which is 2. - Checking 50 is unnecessary
√
If we are going to find a divisor C of a number A which is smaller than or equal than √ A. We are
quite sure that there will be complementary D which is either bigger than or equal to A. (C * D
= A).Checking D is redundant since we have already checked its complementary.
5
1 #include <iostream>
2 #include <time.h>
3
Output
We are trying our algorithms with a large prime number. There will be no divisor in prime numbers.
Therefore, we will iterate until the end of the loop. Performing the large prime number will lead us
to the worst-case. If we take a number that is quite big but even such as 1010 , we would break our
loop in i = 2
6
There are some other algorithms for testing the primality of a number. For example, the link here
explains the Miller Approach.
These two methods allow us to check if n is prime or not. It is just a number. What will happen if
we want to find all positive prime numbers smaller than or equal to n?
√
We know that we can find the primeness in O( n) for a number. The first thing that we can do is
iterating through 1 to n and using the optimized naive method for each number.
Numbers 2√ 3√ √4 5√ 6√ √7 ... N
√
Primality Test Operations 2 3 4 5 6 7 ... N
√ √ √ √ √
We will do the 2 + 3 + 4 ... + N processes. √ The square root sum is limited by the N · N.
Therefore, we would get time complexity as O(n• n) [3].
7
1 #include <iostream>
2 #include <time.h>
3 #include <vector>
4
5 using namespace std;
6
7 bool isPrime(int t) {
8 // 0 and 1 are not prime numbers. Therefore, we can return false directly.
9 if(t == 0 || t == 1) return false;
10 // Check until i*i is smaller or equal then t
11 for(int i = 2; i*i <= t; i++)
12 if(t%i == 0)
13 return false;
14
Output
8
2.2.2 Sieve of Eratosthenes Approach
Time Complexity: O(n • log log n)
The Sieve approach was developed by the Greek Mathematician Eratosthenes. It allows us to obtain
the prime states of the numbers between 1 and n.
√
The algorithm is quite straightforward. We need to start from 1 to n. If the current number(i) is
prime, we can mark every number that’s evenly divisible by i as not prime [4].
√
How do we have the time complexity of O(n ∗ loglogn)? We need to go until to n for deleting the
numbers [5]. But why do we have loglogn in the time complexity? How many processes do we do in
each prime until O(n)?
2 will delete 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 - (N/2) process
...
For each prime number until N, we will do N/pi process for each prime number(pi ). So in total, we
will be doing the following processes,
N
X N
T otalP rocess =
i=1 pi
We can write the following equation according to the Euler proof [6].
n
X 1
ln ln n =
i=1 pi
√
Then, we can√write the total process as in the following. Since pi < n covers pi < n, we can write
n instead of n.
N
X 1
T otalP rocess = N ·
i=1 pi
9
Figure 1: The visual representation of the Sieve Algorithm.
1 #include <iostream>
2 #include <time.h>
3 #include <vector>
4
5 using namespace std;
6
7 void sieve(int n, vector<bool> &isPrimeArray){
8 isPrimeArray[0] = false, isPrimeArray[1] = false;
9
10 for(int i = 2; i*i < n; i++)
11 if(isPrimeArray[i])
12 for(int j = i*2; j < n; j += i)
13 isPrimeArray[j] = false;
14 }
15
16 int main() {
17 printf("Enter the size of the array. (n) \n");
18 int n;
19 scanf("%d", &n);
20
21 // Initially, start marking every node with true.
22 vector<bool> isPrimeArray(n+1, true);
23
24 // Calculate the runtime of the function.
25 clock_t tStart = clock();
26 sieve(n, isPrimeArray);
27 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
28
29 return 0;
30 }
10
Output
• a and b concurrent mod n if the reminder of a/n and b/n are equal.
Addition
Multiplication
Exponentiation
11
Division
a·b≡1 mod m
Some equations do not have the modular inverse. Therefore, we might not find the modular inverse
of some equations.
• Naive Approach
In the naive approach, we need to iterate up to m and check if the current element satisfies the
condition. If it satisfies the condition(a · b ≡ 1 mod m) we select i as the inverse modulo of the a.
The run-time of this algorithm becomes O(m)
Fermat’s little theorem allows us to write the following for the prime number called m [7].
am−1 ≡ 1 mod m
Let’s refer to the modular inverse of a as b. If we multiply both sides with b we would get the
following formula,
b ≡ am−2 mod m
We get the latter formula, Since a · b ≡ 1 mod m. b deletes one of the a in ap−1 and makes it ap−2
Therefore,the modular inverse of a, which is b becomes am−2 mod m. We can use fast exponentiation
for finding am−2 . The run-time of this algorithm is O(log(m))
12
2.4 GCD - Greatest Common Divisor
Greatest Common Divisor(GCD) of two numbers A and B is the largest number D that evenly divides
both of the numbers A and B.
• GCD of 2 and 4 is 2
• GCD of 3 and 4 is 1
• GCD of 3 and 6 is 3
• GCD of 10 and 15 is 5
We can start from 1 and proceed until the minimum of these two numbers. If we encounter a
number that is both divisible by A and B, we can say that the number is a common divisor. How-
ever, we have to go until the minimum of A and B. We are trying to find the greatest divisor. The
GCD might equal to the minimum of A and B.
1 #include <iostream>
2 #include <algorithm>
3 using namespace std;
4 int main() {
5 long long n,m;
6 scanf("%lld%lld", &n, &m);
7
8 long long minVal = min(n,m), gcd = 0;
9 // Calculate the untime.
10 clock_t tStart = clock();
11 for(int i=1;i<=minVal;i++){
12 // If n is evenly divisible by i, n%i will return 0.
13 // C++ is a weakly typed language.
14 // Therefore, We can change types.
15 // False can cast to the integer as 0.
16 // Therefore, If n is evenly divisible by i, n%i will return 0(False).
17 if(!(n % i) && !(m % i))
18 gcd = i;
19 }
20 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
21 printf("The GCD is %lld\n", gcd);
22 return 0;
23 }
Output
13
• Time taken: 2.652236s
Euclid states that If A and B has GCD of any C, A − K · B has the GCD of C as well. We
are going to use A mod B instead of A − K · B. If we give K the biggest value that makes A − K · B
the smallest non-negative number, we would get A mod B. For example,
So we know that A mod B does not change GCD. Therefore, we can take A mod B first.After this
modulo operation, B will be bigger than A (B > A). So we can take B mod A. After this modulo
operation, A will be bigger than (A > B). So that, we can now do this recursively until one of them
is zero. Let’s give an example,
A = 13 & B = 17
A mod B | A = 13 & B = 17
B mod A | A = 13 & B = 4
A mod B | A = 1 & B = 4
B mod A | A = 1 & B = 0
Since we hit 0 on the side of B, we can proudly say that our answer( GCD(A,B) ) is 1.
The time complexity of this algorithm is logarithmic. The proof of this comes from the taking GCD
two consecutive Fibonacci numbers. Further reading materials can be found here.
14
1 #include <iostream>
2 #include <algorithm>
3
10
11 while(n != 0){
12 temp = n;
13 n = m%n;
14 m = temp;
15 }
16
17 return m;
18 }
19
20 int main() {
21 long long n,m;
22 scanf("%lld%lld", &n, &m);
23
24 long long minVal = min(n,m), gcd = 0;
25
26 // Calculate the runtime of the function.
27 clock_t tStart = clock();
28
29 gcd = calculateGCD(n, m);
30
31 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
32
Output
15
2.5 LCM - Least Common Multiple
Least Common Multiple(LCM) of two numbers A and B is the minimum number D that is divisible
by both of the numbers A and B.
• LCM of 2 and 4 is 4
• LCM of 3 and 4 is 12
• LCM of 3 and 6 is 6
• LCM of 10 and 15 is 30
We can start from the maximum number and go until the multiplication of these two numbers.
If we encounter that both are divisible by A and B, we can say that number is the LCM. Since we
have found the LCM of A and B, we do not need to go further. We can terminate the loop.
The biggest LCM(N, M) that we can find is N · M if N and M have no common factors. So, there
will be no common number that is divisible by both N and M except N · M .
1 #include <iostream>
2 #include <algorithm>
3 #include <time.h>
4 using namespace std;
5
6 int main() {
7 long long n, m;
8 scanf("%lld%lld", &n, &m);
9 long long maxVal = max(n, m), lcm = 0;
10
11 // Calculate the runtime of the function.
12 clock_t tStart = clock();
13 for(long long i=maxVal;i<=n*m;i++){
14 // If i is both divisible by n and m, is lcm of these two numbers.
15 if(i%n == 0 && i%m == 0){
16 lcm = i;
17 break;
18 }
19 }
20 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
21 printf("The LCM is %lld\n", lcm);
22 return 0;
23 }
Output
16
• Time taken: 0.791119s
In this algorithm, we will use GCD(M, N) for finding LCM(N, M). We are going use the relationship
between GCD(M, N) and LCM(M, N).
Let’s prove the correctness of this formula by using Unique Factorization Theorem [10].
LCM (M, N ) = ad11 · ad22 · · · adkk and GCD(N, M ) = ae11 · ae22 · · · aekk .
So we can write,
di = min(bi , ci ) and ei = max(bi , ci )
di + ei = bi + ci
With using the latter formula, we can write the following equations.
17
1 #include <iostream>
2 #include <algorithm>
3
Output
18
2.6 Benzout’s Identity
Time Complexity: O(log min(n, m))
Benzout Identity algorithm allows us to find the x and y integer values from the any a and b
values in the following formula [11]. Benzout Identity algorithm is also called Extended Euclidean
Algorithm.
a · x + b · y = gcd(a, b)
We know that we can use a recursive formula for finding the GCD of a and b by using the Euclidean
Algorithm. Therefore, we can give similar values in the formula for finding x and y. So let’s say that
we will give (b mod a, a) instead of (a,b) and (x1 , y1 ) instead of (x,y) in the latter formula.
(b mod a) · x1 + a · y1 = gcd(a, b)
$ %
b
b mod a = b − ·a
a
$ %
b
(b − · a) · x1 + a · y1 = gcd(a, b)
a
$ %
b
(b − · a) · x1 + a · y1 = gcd(a, b)
a
$ % !
b
b · x 1 + a · y1 − · x1 = gcd(a, b)
a
y = x1
$ % !
b
x = y1 − · x1
a
This was the one execution of the recursive approach. We should find x and y until we hit the end
of the Euclidean GCD approach. In other words, we need to repeat this equation as in the Euclidean
approach.
19
1 #include <iostream>
2
3 using namespace std;
4
5 // Take x and y as the reference to change them while going inside the recursion
6 int gcd(int a, int b, int & x, int & y) {
7 // End of the recursion
8 if (a == 0) {
9 x = 0;
10 y = 1;
11 return b;
12 }
13
14 int x1, y1;
15 int d = gcd(b % a, a, x1, y1);
16 // Find x and y value, recursively
17 x = y1 - (b / a) * x1;
18 y = x1;
19 return d;
20 }
21
22
23 int main() {
24 int a,b;
25 scanf("%d %d", &a, &b);
26 int x,y;
27 // Calculate the runtime of the sieve function.
28 clock_t tStart = clock();
29
30 gcd(a, b, x, y);
31
32 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
33
34 printf("The x is %d, the y is %d\n", x, y);
35 }
36
Output
• The x is 2, the y is -1
20
3 Factorization
3.1 Factorization Algorithms
In the topic of factorization, we will learn how to find all numbers that evenly divide the given number
n.
The naive approach to this problem is quite straightforward. We can start from 1 and loop un-
til n. While we are doing our iteration, we can check if the current number divides n evenly or not.
If current number divides n evenly, we can take that number as the factor of n.
1 #include <stdio.h>
2 #include <vector>
3 using namespace std;
4 int main() {
5 printf("Enter the number. (n) \n");
6 int n;
7 scanf("%d", &n);
8 vector<int> factors;
9 // Calculate the runtime.
10 clock_t tStart = clock();
11 for(int i = 1;i <= n; i++)
12 if(n%i == 0)
13 factors.push_back(i);
14 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
15
16 printf("The size of the factors array is %d\n", (int)factors.size());
17 for(auto f : factors) printf("%d ", f);
18 printf("\n");
19 }
20
Output
• 1 2 3 5 6 7 10 11 13 14 15 17 19 21 22 23 ... 223092870
21
√
The factors of n pair up. Therefore, we do not need to go until n. We can go until n and
find the current number’s pair by dividing n by the current number.
We need to use set in here. Because set keeps the values in sorted format. Set does not contain
duplicate values. [8] Therefore, it allows us to check the cases like 4. (1*4 – 2*2) Set is an implemented
type in STL library. The STL library was mentioned at week one.
1 #include <stdio.h>
2 #include <set>
3
4 using namespace std;
5
6 int main() {
7 printf("Enter the number. (n) \n");
8 int n;
9 scanf("%d", &n);
10
11 set<int> factors;
12
Output
• 1 2 3 5 6 7 10 11 13 14 15 17 19 21 22 23 ... 223092870
22
(i) number-b is a prime number.
For finding the prime factors of a given number, we can run the Sieve Algorithm. Since we know
that pi deletes a number ai if and only if pi evenly divides the number ai . Therefore, we can append
pi into the prime factors of ai . The run-time of this will be the have the same run-time as the Sieve
Algorithm, which is O(n · log log n)
4 Combinatorics
In this section, we are going to look into the topic of combinatorics. The subtopics that we will
eximine of combinatorics are permutation, combination and binomial coefficient. We will also see the
example codes of the topic.
4.1 Factorial
Time Complexity: O(n)
n! = 1 · 2 · ... · n
23
1 #include <iostream>
2 #include <time.h>
3
27 return res;
28 }
29
30 int main() {
31 long long n;
32 scanf("%lld",&n);
33 // Calculate the runtime of the function.
34 clock_t tStart = clock();
35
36 long long res = facIt(n);
37
Output
24
4.2 Permutation
4.2.1 Permutation Basics
Permutation allows us to find the different subsets of the given set that is the order of subsets matter.
The following formula allows us to calculate how many potential permutations we have [12].
n!
P =
(n − k)!
For example, if we want to select 4 questions from the set of 10 questions for the bundle-3-math-1
contest we would have 10!
6!
= 5040 different order of questions.
1 #include <iostream>
2 #include <time.h>
3
4 using namespace std;
5
6 int main() {
7 long long n,k;
8 scanf("%lld%lld",&n,&k);
9
10 long long mult = 1, destination = n - k;
11
12 // Calculate the runtime.
13 clock_t tStart = clock();
14
15 while(n > destination){
16 mult *= n;
17 n--;
18 }
19
20 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
21
22 printf("%lld", mult);
23 return 0;
24
25 }
25
4.2.2 Generating permutations
Time Complexity: O(n*n!)
We have a set s with size n. The problem is finding all n-permutations of given set s. For ex-
ample,
s = {1, 2, 3}
All permutations of the following set would be in the following,
{1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 2, 1}, {3, 1, 2}
We can recursively add an element to the temporary array one by one until the temporary array has
the same size as the given number n. [13]
26
1 #include <iostream>
2 #include <vector>
3 #include <time.h>
4
5 using namespace std;
6 #define ll long long
7
8 // The input set and it's size
9 vector<ll> input;
10 int n;
11 // Temp vector that is used for each permutaiton
12 vector<ll> tempPer;
13 // The output set of permutations
14 vector< vector<ll> > output;
15 // Check if we add the element into the set.
16 vector<bool> isAdded;
17
18 void genPer() {
19 if(tempPer.size() == n){
20 output.push_back(tempPer);
21 }else{
22 for(int i = 1; i <= n; i++){
23 if(isAdded[i]) continue;
24 // Make the changes for finding permutation
25 isAdded[i] = true;
26 tempPer.push_back(i);
27 genPer();
28 // Remove the changes that we have made
29 isAdded[i] = false;
30 tempPer.pop_back();
31 }
32 }
33 }
34
35 int main() {
36 scanf("%d", &n);
37 isAdded.resize(n+1);
38
39 // Create the array
40 for(int i=1;i<=n;i++) input.push_back(i);
41
42 // Calculate the runtime.
43 clock_t tStart = clock();
44 genPer();
45 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
46
47 printf("The size of the all permutation is %d\n", (int)output.size());
48 for(auto o : output) {
49 // Print number in a line
50 for( auto c : o) printf("%lld ", c);
51 printf("\n");
52 }
53 return 0;
54 }
27
Output
• The input is 10
Let us see one more output example for this code. It has a very big run-time ( O(n•n!) ). Therefore,
it would be nice to highlight the importance of the run-time by running this algorithm with another
input.
Output
• The input is 11
For a better understanding of how does this algorithm work, let’s check the following table. For the
sake of simplicity we will take n = 3.
Recursion
Steps isAdded tempPer output
1 {T, F, F} {1} {}
2 {T, T, F} {1,2} {}
3 {T, T, T} {1,2,3} {}
4 {T, T, F} {1, 2} {{1,2,3}}
5 {T, F, F} {1} {{1,2,3}}
6 {T, F, T} {1,3} {{1,2,3}}
7 {T, T, T} {1,3,2} {{1,2,3}}
8 {T, F, T} {1,3} {{1,2,3}, {1,3,2}}
9 {T, F, F} {1} {{1,2,3}, {1,3,2}}
10 {F, F, F} {} {{1,2,3}, {1,3,2}}
11 {F, T, F} {2} {{1,2,3}, {1,3,2}}
···
The name of the table headers(isAdded, tempPer, output) are the variables in the given code.
28
4.3 Combination
4.3.1 Combination Basics
Combination is selecting items from a set. The order of selected sets does not matter. The order is
the main difference between permutation. The following formula allows us to calculate how many
potential combinations we have [14].
n!
C=
(n − k)! · k!
1 #include <iostream>
2 #include <time.h>
3
Output
29
4.3.2 Combination Calculation by Using Recurrence Formula
Time Complexity: O(n*r)
For bigger integer, we can not benefit from modulo operation while we are finding the combina-
tion. We have division operation in the latter formula. Therefore, this method is used to benefit
from the modulo operation.
We will get the benefit from the following rules for the implementation of this algorithm,
! !
n n! n n!
= =1 = =1
0 0! · (n − 0)! n n! · (n − n)!
! !
n n! n n!
= =n = =n
0 1! · (n − 1)! n−1 (n − 1)! · (n − (n − 1))!
! ! !
n+1 n n
= +
r+1 r+1 r
Let’s prove the latter formula,
!
n+1 (n + 1)!
=
r+1 (r + 1)! · (n + 1 − (r + 1))!
! !
n n n! · (r + 1) + n! · (n − r) n! · (n + 1)
+ = =
r+1 r (r + 1)! · (n − r)! (r + 1)! · (n − r)!
So we can divide nr into n−1r−1
and n−1
r
recursively to find the nr . However, this dividing would
not benefit us unless we do some caching operations. We might encounter the same n and r’s over
and over again in the recursive function. Therefore, we should store the output of nr to use it again
in the near future [15].
30
1 #include <iostream>
2 #include <vector>
3 #include <time.h>
4 using namespace std;
5
6 // For saving the values
7 vector< vector<long long> > savedVal;
8
9 long long combination(int n, int k){
10 // Base condition
11 if(k == n || k == 0) {
12 savedVal[n][k] = 1LL;
13 return savedVal[n][k];
14 }
15
16 // Base condition
17 if(k == n-1 || k == 1){
18 savedVal[n][k] = (long long)n;
19 return savedVal[n][k];
20 }
21
22 if(savedVal[n][k] != 0) return savedVal[n][k];
23
24 // Save the output of the recursion
25 savedVal[n][k] = (combination(n-1, k) + combination(n-1, k-1));
26 return savedVal[n][k];
27 }
28
29 int main() {
30 int n,k;
31 // Read the input and resize the array
32 scanf("%d %d", &n, &k);
33 savedVal.resize(n+1, vector<long long>(k+1, 0LL));
34
35 clock_t tStart = clock();
36 long long combinationVal = combination(n, k);
37 printf("Time taken: %.6fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
38
39 printf("The output is %lld\n", combinationVal);
40
41 return 0;
42 }
43
Output
31
4.4 Binomial Coefficient
Binomial Coefficient is the coefficient of xk in the formula (x + 1)n . For example,
(x + 1)3 = x3 + 3x2 + 3x + 1
So, x2 ’s coefficient is 3.
We can also find the coefficient value by using the combination. C(n, k) means the coefficient of xk
in the formula (x + 1)n [16]. For example,
C(3, 2) = 3
32
5 Exponentiation
5.1 Naive Approach
Time Complexity: O(k)
The problem is finding the nk . In the naive approach, we simply multiply n with itself k times.
1 #include <iostream>
2 #include <time.h>
3
4 using namespace std;
5
6 #define mod 1000000007
7
8 long long exp(long long n, long long k){
9 long long res = 1;
10 while(k--){
11 res *= n;
12 // Since it might be too large for long long, we take the modulo.
13 res %= mod;
14 }
15 return res;
16 }
17
18 int main() {
19 long long n,k;
20 scanf("%lld%lld", &n, &k);
21
22 // Calculate the runtime of the function.
23 clock_t tStart = clock();
24
Output
• 494499948
33
5.2 Fast Exponentiation Approach
Time Complexity: O(log k)
We will get a benefit from the following rule for finding the exponentiation.
nk/2
∗ nk/2 if k is even
nk = (k−1)/2 (k−1)/2
(1)
n ∗n ∗ n if k is odd
1 #include <iostream>
2 #include <time.h>
3
4 using namespace std;
5
6 #define mod 1000000007
7
34
Output
• 494499948
This section is about finding the Nth Fibonacci Number by using the fast exponentiation approach.
We all heard the Fibonacci Sequence.
We are going to use the matrix representation of the Fibonacci sequence. We can write the following
equation. [17]
Fn+2 Fn+1 + Fn 1 1 Fn+1
Fn+1 = Fn+1 = 1 0 × Fn
Fn+3 Fn+2 + Fn+1 1 1 F 1 1 1 1 F
Fn+2 = Fn+2 = 1 0 × Fn+2 = 1 0 × 1 0 × Fn+1
n+1 n
Therefore, we can write the following rule for the Fibonacci sequence in matrix form,
n
Fn+2 1 1 F
Fn+1 = 1 0 × F2
1
So far so good, we have a general matrix equation for the finding Nth Fibonacci number. The fast
exponentiation comes to play in here. We can find the matrix exponentiation as in the following.
n/2 n/2
1 1 1 1
∗ if n is even
1 0 1 0
n
1 1
1 0 = (n−1)/2 (n−1)/2 (2)
1 1 1 1 1 1
∗ ∗ if n is odd
1 0 1 0 1 0
The latter formula gives us the find the matrix that has Nth Fibonacci number. Since we are finding
our matrix Exponentiation in log n processes, we would get the time complexity of O(log n).
35
6 Meet in the Middle
Meet in the middle - sometimes called split and merge - is a technique which uses caching to find
efficient solutions on specific problems. Now, let’s consider the following example.
Problem : Given an array of integers and a number, find the number of subsets of sum equal to given
number.
1 #include <iostream>
2 #include <time.h>
3 using namespace std;
4
5 int main() {
6 int n;
7 long long int k;
8 scanf("%d %lld", &n, &k);
9 int a[n+1];
10
11 for (int i = 1;i <= n;i++) {
12 scanf("%d", &a[i]);
13 }
14
15 long long int result = 0;
16 for (int i = 1;i <= (1 << n);i++) {
17 long long int sum = 0;
18 for (int j = 1;j <= n;j++) {
19 if (i & (1 << j)) {
20 sum += a[j];
21 }
22 }
23 if (sum == k) {
24 result++;
25 }
26 }
27
28 // Calculate the runtime of the function.
29 clock_t tStart = clock();
30
36
6.2 Meet in the Middle
Time Complexity: O(n ∗ 2n/2 ∗ log(n))
In this approach, we divide the array into two parts, A and B. After that, we calculate the sums
of all subsets in A and B. Then, we iterate through the subset sums of A, which is enough for the
desired result.
2 #include <iostream>
3 #include <time.h>
4 #include <vector>
5 #include <map>
6 using namespace std;
7
8 int main() {
9 int n;
10 long long int k;
11 scanf("%d %lld", &n, &k);
12
13 vector<int> a, b;
14 map<long long int, int> mp_a, mp_b;
15
16 for(int i = 0;n/2 > i;i++) {
17 int x;
18 scanf("%d", &x);
19 a.push_back(x);
20 }
21
22 for(int i = n/2;n > i;i++) {
23 int x;
24 scanf("%d", &x);
25 b.push_back(x);
26 }
27
28 int result = 0;
29
30 for(int i = 0;i < (1 << a.size());i++) {
31 long long int sum = 0;
32 for(int j = 0;a.size() > j;j++) {
33 if(i & (1 << j)) {
34 sum += a[j];
35 }
36 }
37 mp_a[sum]++;
38 }
39
40 for(int i = 0;i < (1 << b.size());i++) {
41 long long int sum = 0;
42 for(int j = 0;b.size() > j;j++) {
43 if(i & (1 << j)){
44 sum += b[j];
45 }
37
46 }
47 mp_b[sum]++;
48 }
49
50 for(auto it = mp_a.begin();it != mp_a.end();it++) {
51 long long int bak = (it->first);
52 result += (it->second) * mp_b[k - bak];
53 }
54
55 clock_t tStart = clock();
56 printf("Time taken: %.6fs\n", (double) (clock() - tStart) / CLOCKS_PER_SEC);
57 printf("%d\n", result);
58 }
38
References
[1] What Is Number Theory?
https://www.math.brown.edu/ jhs/frintch1ch6.pdf
[2] Number Theory. (n.d.). In Wikipedia. Retrieved October 21, 2018, from
https://en.wikipedia.org/wiki/Number_theory
[4] Khan Academy Labs (2014, April 24). Sieve of Eratosthenes [Video file]. Retrieved from
https://www.youtube.com/watch?time_continue=52&v=klcIklsWzrY.
[5] Sieve of Eratosthenes (n.d.). In Codility. Retrieved October 21, 2018, from
https://codility.com/media/train/9-Sieve.pdf
[7] Fermat’s little theorem. (n.d.). In Wikipedia. Retrieved October 21, 2018, from
https://en.wikipedia.org/wiki/Fermat%27s_little_theorem
[9] Euclid’s GCD Algorithm. (n.d ). Retrieved October 21, 2018, from
http://people.cs.ksu.edu/ schmidt/301s14/Exercises/euclid_alg.html
[11] Extended Euclidean Algorithm. (n.d ). Retrieved October 21, 2018, from
https://cp-algorithms.com/algebra/extended-euclid-algorithm.html
[13] 5.2 Generating permutations (n.d ). Retrieved October 21, 2018, from
http://disi.unitn.it/ montreso/acm-icpc/CompetitiveProgrammersHandbook.pdf
[15] Binomial Coefficient | DP-9. (n.d.). In geeksforgeeks. Retrieved October 21, 2018, from
39
https://www.geeksforgeeks.org/binomial-coefficient-dp-9/
[16] Pascal Triangle. (July 18, 2017). In 101computing.net. Retrieved October 21, 2018, from
https://www.101computing.net/pascal-triangle/
[17] Robert C Johnson (June 15, 2009). Fibonacci numbers and matrices. Retrieved October 21,
2018, http://maths.dur.ac.uk/ dma0rcj/PED/fib.pdf
40