# W2 Notes: Number Theory 1 [ToC] ## Number Theory: Finding Divisors, Number and Sum of Divisors, and Primality Test You are given four problems, all concerning the positive divisors (factors) of a positive integer $n$: 1. Find **all the divisors** of $n$. 1. Find the **number (count) of all the divisors** of $n$. 1. Find the **sum of all the divisors** of $n$. 1. Find if $n$ is a [prime number](https://en.wikipedia.org/wiki/Prime_number), i.e., test it for **primality**. Let's first come up with brute-force solutions for them. Eventually, we'll see that it's possible to solve all four of them more efficiently using a neat pattern of divisors. ### Brute-force solutions - Brute-force implementation for *divisors*: ```cpp= vector<int> divisors(int n) { vector<int> div; for (int i = 1; i <= n; ++i) { if (n % i == 0) { div.push_back(i); } } return div; } ``` - Brute-force implementation for *number of divisors (NOD)*: ```cpp= int NOD(int n) { int cnt = 0; for (int i = 1; i <= n; ++i) { if (n % i == 0) { ++cnt; } } return cnt; } ``` - Brute-force implementation for *sum of divisors (SOD)*: ```cpp= int SOD(int n) { int sum = 0; for (int i = 1; i <= n; ++i) { if (n % i == 0) { sum += i; } } return sum; } ``` - Brute-force implementation for *primality test*: ```cpp= bool is_prime(int n) { for (int i = 2; i < n; ++i) { if (n % i == 0) { return false; } } return true; } ``` - Time coplexity: $O(n)$ - Is this complexity good enough if $n \geq 10^{9}$? Or if $1 \leq n \leq 10^5$ but you have to answer $t$ ($1 \leq t \leq 10^{4}$) cases per test? --- NO ### Better solutions - **Observation:** If $x$ is a divisor (factor) of $n$, then $n / x$ must also be a divisor of $n$. That is, $x | n \Rightarrow (n / x) | n$. Let $y = n / x \Rightarrow x \cdot y = n$. Thus, for each divisor $x$ of $n$, we get a *complementary divisor pair* $(x, y) = (x, n / x)$. - If $x = \sqrt n$, then $y = \sqrt n$ must also hold, otherwise $x \cdot y = n$ would not be possible. For the same reason, - if $x < \sqrt n$, then $y > \sqrt n$ must also hold, and - if $x > \sqrt n$, then $y < \sqrt n$ must also hold. This essentially means that **if we find all the divisors of $n$ that are less than or equal to $\sqrt n$, we also get all of its divisors greater than or equal to $\sqrt n$** at the same time. Thus, we can see that the number of divisors of $n$ is always **less than $2\sqrt n$**, which is expressed in the big-O notation as $O(2\sqrt n) \sim O(\sqrt n)$. Hence, we only need to find the divisors up to $\sqrt n$ to exhaustively search all the divisors. - **Example:** Let's iterate over the sequence of divisors $a = [1, 2, 3, 4, 6, 9, 12, 18, 36]$ of $n = 36$, and simultaneously generate another sequence $b$ such that $a_i \cdot b_i = n \Rightarrow b_i = n / a_i$. | $a_i$ | $b_i = n / a_i$ | |--------|-----------------| | $1$ | $36$ | | $2$ | $18$ | | $3$ | $12$ | | $4$ | $9$ | | $6$ | $6$ | | $9$ | $4$ | | $12$ | $3$ | | $18$ | $2$ | | $36$ | $1$ | Notice that $b = reverse(a)$, and the sequence of the unordered pairs $(a_i, b_i)$ is symmetric about the point where $a_i = \sqrt n$. Up to that point, all the divisors of $36$ can be found thus: $36 \\ = 1 \cdot 36 \\ = 2 \cdot 18 \\ = 3 \cdot 12 \\ = 4 \cdot 9 \\ = 6 \cdot 6$ - $O(\sqrt n)$ implementation for *divisors*: - Simpler implementation: ```cpp= vector<int> divisors(int n) { vector<int> div; for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { div.push_back(i); if (n / i != i) { div.push_back(n / i); } } } // Sort the divisors if needed. Note that this // changes the complexity to O(sqrt(n) * log(sqrt(n))), // because `std::sort` on an array of length n takes // O(n * log(n)) time. sort(div.begin(), div.end()); return div; } ``` - More efficient implementation: ```cpp= vector<int> divisors(int n) { vector<int> div; vector<int> buf; // buffer (temp. storage) for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { div.push_back(i); buf.push_back(n / i); } } int from = buf.front() == div.back() ? 1 : 0; for (int i = buf.size() - 1; i >= from; --i) { div.push_back(buf[i]); } return div; } ``` - $O(\sqrt n)$ implementation for *number of divisors (NOD)*: ```cpp= int NOD(int n) { int cnt = 0; for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { ++cnt; if (n / i != i) { ++cnt; } } } return cnt; } ``` - $O(\sqrt n)$ implementation for *sum of divisors (SOD)*: ```cpp= int SOD(int n) { int sum = 0; for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { ++cnt; if (n / i != i) { ++cnt; } } } return sum; } ``` - $O(\sqrt n)$ implementation for *primality test*: ```cpp= bool is_prime(int n) { for (int i = 2; i * i <= n; ++i) { if (n % i == 0) { return false; } } return true; } ``` ## Number Theory: Modular Arithmetic The [Wikipedia article](https://en.wikipedia.org/wiki/Modular_arithmetic) says: > In mathematics, modular arithmetic is a system of arithmetic for integers, where numbers "wrap around" when reaching a certain value, called the modulus. Make sure to learn about *congruence relations* from [this section](https://en.wikipedia.org/wiki/Modular_arithmetic#Congruence) of that article. For this lesson, let $M$ be the modulus. - **Modular Addition:** $\begin{align*} (a + b) \mod M \\ = ((a \mod M) + (b \mod M)) \mod M \end{align*}$ - **Modular Subtraction:** $\begin{align*} (a - b) \mod M \\ = ((a \mod M) - (b \mod M)) \mod M \end{align*}$ - **Modular Multiplication:** $\begin{align*} (a \cdot b) \mod M \\ = ((a \mod M) \cdot (b \mod M)) \mod M \end{align*}$ - The case for modular division is different, and is beyond the scope of this class.