# Перебор всех делителей числа 1. **Наивный алгоритм за $O(n)$** ```cpp= vector<int> divisors; for(int i = 1; i <= n; i++){ if(n % i == 0) divisors.push_back(i); } ``` 2. **Улучшенный алгоритм за $O(\sqrt{n})$** Заметим факт, что если $\frac{n}{a} = b$ и $a < \sqrt{n}$, то $b > \sqrt{n}$ ($a$ и $b$ - целые числа). Следовательно нам достаточно перебрать делители до $\sqrt{n}$. ```cpp= vector<int> divisors; for(int i = 1; i <= sqrt(n); i++){ if(n % i == 0){ divisors.push_back(i); if(i != n / i) divisors.push_back(n / i); } } ``` 3. **Проверка на простоту** Число $n$ является **простым**, если оно не имеет делителей, кроме $n$ и $1$. ```cpp= bool isPrime(int n){ if(n == 1) return false; for(int i = 2; i <= sqrt(n); i++){ if(n % i == 0){ return false; } } return true; } ``` # Решето Эратосфена **Решето Эратосфена** позволяет найти все простые числа в на отрезка за $O(n\log{\log{n}})$ ```cpp= int n; bool notPrime[n + 1]; // ввод fill(notPrime, notPrime + n + 1, 0); notPrime[0] = notPrime[1] = true; vector<int> primes; for(long long i = 2;i <= n;i++){ if(notPrime[i]) continue; primes.push_back(i); if(i * i >= n) continue; for(int j = i * i; j <= n; j += i) notPrime[j] = true; } ``` # Быстрое возведение в степень 1. **Наивное решение за $O(p)$** ```cpp= int x, p; //ввод int ans = 1; for(int i = 0;i < p;i++) ans *= x; ``` 2. **Улучшенный алгоритм за $O(\log{p})$** Заметим, что для любого **чётного** $p$ выполняется свойство: $x^p = x^{\frac{p}{2}} * x^{\frac{p}{2}}$ А для **любого** $p$ выполняется: $x^p = x^{p - 1} * x$ Значит мы можем переписать алгоритм таким образом: ```cpp= int binPow(int x, int p){ if(p == 0) return 1; if(p % 2 == 1) return binPow(x, p - 1) * x; int X = binPow(x, p / 2); return X * X; } ``` В худшем случае функия будет вызвана $2 *\log{n}$ раз. Также данный алгоритм называют **бинарным возведением в степень**. Реализация возведения $x$ в степень $p$ по **модулю** $m$. ```cpp= ll binPow(int x, int p, ll m){ if(p == 0) return 1; if(p % 2 == 1) return (binPow(x, p - 1) * x) % m; int X = binPow(x, p / 2); return (X * X) % m; } ``` 3. Итеративная реализация ```cpp= int binPow(int x, int p){ int now = 1; while(p != 0){ if(p % 2 == 1){ now *= x; p--; } x *= x; p /= 2; } return now; } ``` Реализция с **модулем** ```cpp= ll binPow(ll x, ll p, ll m){ ll now = 1; while(p != 0){ if(p % 2 == 1){ now = (x * now) % m; p--; } x = (x * x) % m; p /= 2; } return now; } ``` # НОД **НОД** - Наибольший Общий делитель. То есть, НОД($a$, $b$) = $max(k)$ такое, что $k$ делит и $a$, и $b$. Алгоритм поиска НОДа: ```cpp= int gcd(int a, int b){ if(b == 0) return a; return gcd(b, a % b); } ``` Этот алгоритм называется - **Алгоритм Евклида** Время работы $O(\log{n})$ **Доказательство:** 1. Пусть $k$ — любой общий делитель чисел $a$ и $b$, тогда $a=t1*k$, $b=t2*k$. 2. Тогда $k$ также общий делитель чисел $b$ и $r$, так как $b$ делится на $k$, а $r=a−b*q=(t1−t2*q)*k$ 3. Обратное также верно и доказывается аналогично. 4. Следовательно, все общие делители пар чисел $a$, $b$ и $b$, $r$ совпадают. Другими словами, нет общего делителя у чисел $a$, $b$, который не был бы также делителем $b$, $r$, и наоборот. # НОК **НОК** - Наибольшее Общее кратное То есть, НОК($a$, $b$) = $min(k)$ такое, что $k$ делится и на $a$, и на $b$. Как же его найти? Довольно просто НОК($a$, $b$) = $\frac{a * b}{gcd(a, b)}$