- Простые числа
- Разложение на простые множители
- Решето Эратосфена
- Линейное решето Эратосфена*
- НОД и НОК
- Алгоритм Евклида
- Расширенный алгоритм Евклида*
- Операции по модулю
- Быстрое возведение в степень
- Деление по простому модулю*
Простым называется натуральное число, которое делится только на единицу и на себя. Единица при этом простым числом не считается. Составным числом называют непростое число, которое еще и не единица.
Примеры простых чисел:
Примеры составных чисел:
Еще одно определение простого числа:
С точки зрения программирования интересно научиться проверять, является ли число
def is_prime(n):
if n == 1:
return False
for i in range(2, n): # начинаем с 2, так как на 1 все делится; n не включается
if n % i == 0:
return False
return True
for i in range(1, 10):
print(i, is_prime(i))
(1, False)
(2, True)
(3, True)
(4, False)
(5, True)
(6, False)
(7, True)
(8, False)
(9, False)
Алгоритм можно ускорить с
Пусть
Почему? Потому что если
Иными словами, если число
Из этого следует, что если число
def is_prime(n):
if n == 1:
return False
# Удобно вместо for i in range(2, n ** 0.5) писать так:
i = 2
while i * i <= n:
if n % i == 0:
return False
i += 1
return True
for i in [1, 2, 3, 10, 11, 12, 10**9+6, 10**9+7]:
print(i, is_prime(i))
(1, False)
(2, True)
(3, True)
(10, False)
(11, True)
(12, False)
(1000000006, False)
(1000000007, True)
Любое натуральное число можно разложить на произведение простых, и с такой записью очень легко работать при решении задач. Разложение на простые множители еще называют факторизацией.
Рассмотрим, например, такую задачу:
Условие: Нужно разбить
Решение: По сути нас просят найти число делителей
Теперь подумаем над этим выражением с точки зрения комбинаторики. Чтобы «сгенерировать» какой-нибудь делитель, нужно подставить в степень
Применяя алгоритм проверки числа на простоту, мы умеем легко находить минимальный простой делитель числа N. Ясно, что как только мы нашли простой делитель числа
Будем перебирать простой делитель от
Напишем алгоритм факторизации:
def factorize(n):
factors = []
i = 2
while i * i <= n: # перебираем простой делитель
while n % i == 0: # пока N на него делится
n //= i # делим N на этот делитель
factors.append(i)
i += 1
# возможно, в конце N стало большим простым числом,
# у которого мы дошли до корня и поняли, что оно простое
# его тоже нужно добавить в разложение
if n > 1:
factors.append(n)
return factors
for i in [1, 2, 3, 10, 11, 12, 10**9+6, 10**9+7]:
print(i, '=', ' x '.join(str(x) for x in factorize(i)))
1 =
2 = 2
3 = 3
10 = 2 x 5
11 = 11
12 = 2 x 2 x 3
1000000006 = 2 x 500000003
1000000007 = 1000000007
За сколько работает этот алгоритм?
.
.
.
.
За те же самые
А итераций деления
Докажите, что число
Вообще, про простые числа известно много свойств, но почти все из них очень трудно доказать. Вот еще некоторые из них:
- Простых чисел, меньших
$N$ , примерно$\frac{N}{\ln N}$ . - N-ое простое число равно примерно
$N\ln N$ . - Простые числа распределены более-менее равномерно. Например, если вам нужно найти какое-то простое число в промежутке, то можно их просто перебрать и проверить — через несколько сотен какое-нибудь найдется.
- Для любого
$N \ge 2$ на интервале$(N, 2N)$ всегда найдется простое число (Постулат Бертрана) - Впрочем, существуют сколь угодно длинные отрезки, на которых простых чисел нет. Самый простой способ такой построить - это начать с
$N! + 2$ . - Есть алгоритмы, проверяющие число на простоту намного быстрее, чем за корень.
-
Максимальное число делителей равно примерно
$O(\sqrt[3]{n})$ . Это не математический результат, а чисто эмпирический — не пишите его в асимптотиках. - Максимальное число делителей у числа на отрезке
$[1, 10^5]$ — 128 - Максимальное число делителей у числа на отрекзке
$[1, 10^9]$ — 1344 - Максимальное число делителей у числа на отрезке
$[1, 10^{18}]$ — 103680 - Наука умеет факторизовать числа за
$O(\sqrt[4]{n})$ , но об этом как-нибудь в другой раз. - Любое число больше трёх можно представить в виде суммы двух простых (гипотеза Гольдбаха), но это не доказано.
Часто нужно не проверять на простоту одно число, а найти все простые числа до
Но древний грек Эратосфен предложил делать так:
Запишем ряд чисел от 1 до
- делящиеся на 2, кроме самого числа 2
- затем деляющиеся на 3, кроме самого числа 3
- затем на 5, затем на 7, и так далее и все остальные простые до n. Таким образом, все незачеркнутые числа будут простыми — «решето» оставит только их.
Найдите этим способом на бумажке все простые числа до 50, потом проверьте с программой:
N = 50
prime = [1] * (N + 1)
prime[0], prime[1] = 0, 0
for i in range(2, N + 1): # можно и до sqrt(N)
if prime[i]:
for j in range(2 * i, N + 1, i): # идем с шагом i, можно начиная с i * i
prime[j] = 0
for i in range(1, N + 1):
if prime[i]:
print(i)
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
У этого алгоритма можно сразу заметить несколько ускорений.
Во-первых, число
Во-вторых, по этой же самое причине
Такой код будет работать за
Научимся оценивать асимптотику величины
Возьмем
Каждое из этих слагаемых имеет вид
Таким образом, наша сумма не превосходит
Оценку снизу можно получить аналогичным образом, оценив каждое такое слагаемое снизу значением
Мы знаем, что гармонический ряд
А что такое асимптотика решета Эратосфена? Мы как раз ровно
Известно, что простых чисел до
Но вообще-то решето можно сделать и линейным.
Решите 5 первых задач из этого контеста:
https://informatics.msk.ru/mod/statements/view.php?id=34271
Наша цель — для каждого числа до
Основное утверждение такое:
Пусть у числа
$M$ минимальный делитель равен$a$ . Тогда, если$M$ составное, мы хотим вычеркнуть его ровно один раз при обработке числа$\frac{M}{a}$ .
Мы также перебираем число
Далее мы хотим вычеркнуть все числа
Алгоритм пометит все числа по одному разу, поэтому он корректен и работает за
N = 30
primes = []
min_d = [0] * (N + 1)
for i in range(2, N + 1):
if min_d[i] == 0:
min_d[i] = i
primes.append(i)
for p in primes:
if p > min_d[i] or i * p > N:
break
min_d[i * p] = p
print(i, min_d)
print(min_d)
print(primes)
2 [0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
3 [0, 0, 2, 3, 2, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
4 [0, 0, 2, 3, 2, 0, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
5 [0, 0, 2, 3, 2, 5, 2, 0, 2, 3, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0]
6 [0, 0, 2, 3, 2, 5, 2, 0, 2, 3, 2, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0]
7 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 0, 2, 0, 2, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 0, 0]
8 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 0, 0]
9 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 0, 0, 3, 0, 0, 0, 5, 0, 3, 0, 0, 0]
10 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 0, 2, 3, 0, 0, 0, 5, 0, 3, 0, 0, 0]
11 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 0, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 0, 5, 0, 3, 0, 0, 0]
12 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 0, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 5, 0, 3, 0, 0, 0]
13 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 0, 0, 0]
14 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 0]
15 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
16 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
17 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
18 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 0, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
19 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
20 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
21 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
22 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 0, 2, 5, 2, 3, 2, 0, 2]
23 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
24 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
25 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
26 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
27 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
28 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 0, 2]
29 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 29, 2]
30 [0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 29, 2]
[0, 0, 2, 3, 2, 5, 2, 7, 2, 3, 2, 11, 2, 13, 2, 3, 2, 17, 2, 19, 2, 3, 2, 23, 2, 5, 2, 3, 2, 29, 2]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Этот алгоритм работает асимптотически быстрее, чем обычное решето. Но на практике, если писать обычное решето Эратсфена с оптимизациями, то оно оказывается быстрее линейнего. Также линейное решето занимает гораздо больше памяти - ведь в обычном решете можно хранить просто
Зато один из «побочных эффектов» алгоритма — он неявно вычисляет факторизацию всех чисел от
Введем два определения.
Наибольший общий делитель (НОД) чисел
Наименьшее общее кратное (НОК) чисел
Например,
- НОД(18, 30) = 6
- НОД(60, 180, 315) = 15
- НОД(1, N) = 1
- НОК(12, 30) = 6
- НОК(1, 2, 3, 4) = 12
- НОК(1,
$N$ ) =$N$
Зачем они нужны? Например, они часто возникают в задачах.
Условие: Есть
Решение: Когда одна шестеренка крутится на 1 зубчик, все остальные тоже крутятся на один зубчик. Нужно найти минимальное такое число зубчиков
Еще пример задачи на применение НОД и НОК:
Условие: Город — это прямоугольник
Решение: Вертолет пересечет по вертикали
Однако еще есть случай, когда он пересекает одновременно обе границы (то есть пролетает над каким-нибудь углом) — ровно тот случай, когда нового посещения квартала не происходит. Сколько таких будет? Ровно столько, сколько есть целых решений уравнения
Пусть
Тогда
Значит, итоговый ответ:
Кстати, когда
Осталось придумать, как искать НОД и НОК. Понятно, что их можно искать перебором, но мы хотим хороший быстрый способ.
Давайте для начала научимся искать
Мы можем воспользоваться следующим равенством:
Оно доказывается очень просто: надо заметить, что множества общих делителей у пар
Из этого равенства сразу следует следующее равенство:
(так как $НОД(a, b) = НОД(a, b - a) = НОД(a, b - 2a) = НОД(a, b - 3a) = \ldots = НОД(a, b \operatorname{%} a)$)
Это равенство дает идею следующего рекурсивного алгоритма:
Например:
Примените алгоритм Евклида и найдите НОД чисел:
- 1 и 500000
- 10, 20
- 18, 60
- 55, 34
- 100, 250
По-английски наибольший общий делитель — greatest common divisor. Поэтому вместо НОД будем в коде писать gcd.
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
print(gcd(1, 500000))
print(gcd(10, 20))
print(gcd(18, 60))
print(gcd(55, 34))
print(gcd(100, 250))
print(gcd(2465473782, 12542367456))
1
10
6
1
50
6
Вообще, в C++ такая функция уже есть в компиляторе g++
— называется __gcd
. Если у вас не Visual Studio, то, скорее всего, у вас g++
. Вообще, там много всего интересного.
А за сколько оно вообще работает?
Докажите, что алгоритм Евклида для чисел
Кстати, интересный факт: самыми плохими входными данными для алгоритма Евклида являются числа Фибоначчи. Именно там и достигается логарифм.
$НОК(a, b) = \frac{ab}{НОД(a, b)}$
По этой формуле можно легко найти НОК двух чисел через их произведение и НОД. Почему она верна?
Посмотрим на разложения на простые множители чисел a, b, НОК(a, b), НОД(a, b).
Из определений НОД и НОК следует, что их факторизации выглядят так: $$ НОД(a, b) = p_1^{min(a_1, b_1)}\times p_2^{min(a_2, b_2)}\times\ldots\times p_n^{min(a_n, b_n)} $$ $$ НОК(a, b) = p_1^{max(a_1, b_1)}\times p_2^{max(a_2, b_2)}\times\ldots\times p_n^{max(a_n, b_n)} $$
Тогда посчитаем
Формула доказана.
Для того, чтобы искать НОД или НОК у более чем двух чисел, достаточно считать их по цепочке:
$НОД(a, b, c, d, \ldots) = НОД(НОД(a, b), c, d, \ldots)$
$НОК(a, b, c, d, \ldots) = НОК(НОК(a, b), c, d, \ldots)$
Почему это верно?
Ну просто множество общих делителей
С НОК то же самое, только фразу "множество общих делителей" надо заменить на "множество общих кратных".
Решите задачи F, G, H, I из этого контеста:
https://informatics.msk.ru/mod/statements/view.php?id=34271
Очень важным для математики свойством наибольшего общего делителя является следующий факт:
Для любых целых
$a, b$ найдутся такие целые$x, y$ , что$ax + by = d$ , где$d = \gcd(a, b)$ .
Из этого следует, что существует решение в целых числах, например, у таких уравнений:
$8x + 6y = 2$ $4x - 5y = 1$ $116x + 44y = 4$ $3x + 11y = -1$
Мы сейчас не только докажем, что решения у таких уравнений существуют, но и приведем быстрый алгоритм нахождения этих решений. Здесь нам вновь пригодится алгоритм Евклида.
Рассмотрим один шаг алгоритма Евклида, преобразующий пару
Предположим, что у нас есть решение данного уравнения для чисел
Теперь сделаем в этом выражении замену
Tаким образом, можно взять
В конце алгоритма Евклида мы всегда получаем пару
Это удобно реализовывать рекурсивно:
def extended_gcd(a, b):
if b == 0:
return a, 1, 0
d, x, y = extended_gcd(b, a % b)
return d, y, x - (a // b) * y
a, b = 3, 5
res = extended_gcd(a, b)
print("{3} * {1} + {4} * {2} = {0}".format(res[0], res[1], res[2], a, b))
3 * 2 + 5 * -1 = 1
Но также полезно и посмотреть, как будет работать расширенный алгоритм Евклида и на каком-нибудь конкретном примере. Пусть мы, например, хотим найти целочисленное решение такого уравнения:
Действительно,
Решите задачу J из этого контеста:
https://informatics.msk.ru/mod/statements/view.php?id=34273
Выражение
Еще это можно опрделить так:
Все целые числа можно разделить на классы эквивалентности — два числа лежат в одном классе, если они сравнимы по модулю
Сложение, вычитение и умножение по модулю определяются довольно интуитивно — нужно выполнить соответствующую операцию и взять остаток от деления.
С делением намного сложнее — поделить и взять по модулю не работает. Об этом подробнее поговорим чуть дальше.
a = 30
b = 50
mod = 71
print('{} + {} = {} (mod {})'.format(a, b, (a + b) % mod, mod))
print('{} - {} = {} (mod {})'.format(a, b, (a - b) % mod, mod)) # на C++ это может не работать, так как модуль от отрицательного числа берется странно
print('{} - {} = {} (mod {})'.format(a, b, (a - b + mod) % mod, mod)) # на C++ надо писать так, чтобы брать модулю от гарантированно неотрицательного числа
print('{} * {} = {} (mod {})'.format(a, b, (a * b) % mod, mod))
# print((a / b) % mod) # а как писать это, пока неясно
30 + 50 = 9 (mod 71)
30 - 50 = 51 (mod 71)
30 - 50 = 51 (mod 71)
30 * 50 = 9 (mod 71)
Посчитайте:
$2 + 3 \pmod 5$ $2 * 3 \pmod 5$ $2 ^ 3 \pmod 5$ $2 - 4 \pmod 5$ $5 + 5 \pmod 6$ $2 * 3 \pmod 6$ $3 * 3 \pmod 6$
Для умножения (в C++) нужно ещё учитывать следующий факт: при переполнении типа всё ломается (разве что если вы используете в качестве модуля степень двойки).
-
int
вмещает до$2^{31} - 1 \approx 2 \cdot 10^9$ . -
long long
вмещает до$2^{63} - 1 \approx 8 \cdot 10^{18}$ . -
long long long
в плюсах нет, при попытке заиспользовать выдает ошибкуlong long long is too long
. - Под некоторыми компиляторами и архитектурами доступен
int128
, но не везде и не все функции его поддерживают (например, его нельзя вывести обычными методами).
Очень часто в задаче нужно научиться считать число, которое в худшем случае гораздо больше, чем
Кстати, вместо того, чтобы писать
int mod = 1e9 + 7; # В C++
cout << mod;
1000000007
N = 1e9 + 7 # В питоне такое число становится float
print(N)
print(int(N))
1000000007.0
1000000007
Задача:
Даны натуральные числа
$a, b, c < 10^9$ . Найдите$a^b$ (mod$c$ ).
Мы хотим научиться возводить число в большую степень быстро, не просто умножая
Сам алгоритм довольно простой и рекурсивный, постарайтесь его придумать, решая вот такие примеры (прямо решать необязательно, но можно придумать, как посчитать значение этих чисел очень быстро):
$3^2$ $3^4$ $3^8$ $3^{16}$ $3^{32}$ $3^{33}$ $3^{66}$ $3^{132}$ $3^{133}$ $3^{266}$ $3^{532}$ $3^{533}$ $3^{1066}$
Да, здесь специально приведена такая последовательность, в которой каждое следующее число легко считается через предыдущее: его либо нужно умножить на
$a^0 = 1$ $a^{2k}=(a^{k})^2$ $a^{2k+1}=a^{2k}\times a$
Нужно только после каждой операции делать mod:
$a^0 \pmod c = 1$ $a^{2k} \pmod c = (a^{k} \pmod c)^2 \pmod c$ $a^{2k+1} \pmod c = ((a^{2k}\pmod c) \times a) \pmod c$
Этот алгоритм называется быстрое возведение в степень. Он имеет много применений:
- в криптографии очень часто надо возводить число в большую степень по модулю
- используется для деления по простому модулю (см. далее)
- можно быстро перемножать не только числа, но еще и матрицы (используется для динамики, например)
Асимптотика этого алгоритма, очевидно,
Решите задачу K из этого контеста:
https://informatics.msk.ru/mod/statements/view.php?id=34271
Решите как можно больше задач из практического контеста:
https://informatics.msk.ru/mod/statements/view.php?id=34273
Давайте все-таки научимся не только умножать, но и делить по простому модулю. Вот только что это значит?
Определение:
Утверждение: в кольце остатков по простому модулю
Например, обратный к
Найдите обратный элемент к:
- числу
$3$ по модулю$5$ - числу
$3$ по модулю$7$ - числу
$1$ по модулю$7$ - числу
$2$ по модулю$3$ - числу
$9$ по модулю$31$
Давайте докажем это утверждение: надо заметить, что если каждый ненулевой остаток
Из этого следует, что среди этих чисел есть
Это здорово, но этот обратный элемент еще хочется быстро находить. Быстрее, чем за
Есть несколько способов это сделать.
Малая теорема Ферма:
$a^{p-1} = 1 \pmod p$ , если$p$ - простое,$a \neq 0 \pmod p$ ).
Доказательство:
В предыдущем пункте мы выяснили, что множества чисел
А значит,
Как это применить
Осталось заметить, что из малой теоремы Ферма сразу следует, что
Обобщение
У малой теоремы Ферма есть обобщение для составных
Теорема Эйлера:
$a^{\varphi(p)} = 1 \pmod p$ ,$a$ - взаимно просто с$p$ , а$\varphi(p)$ - это функция Эйлера (количество чисел, меньших$p$ и взаимно простых с$p$ ).
Доказывается теорема очень похоже, только вместо ненулевых остатков
Для нахождения обратного по этой теореме достаточно посчитать функцию Эйлера
Но с этим возникают большие проблемы: посчитать функцию Эйлера сложно. Более того, на предполагаемой невозможности быстро ее посчитать построены некоторые криптографические алгоритм типа RSA. Поэтому быстро делить по составному модулю этим способом не получится.
Этим способом легко получится делить по любому модулю! Рекомендую.
Пусть мы хотим найти
Давайте найдем корни уравнения
Они есть и находятся расширенным алгоритмом Евклида за
Тогда если взять остаток по модулю
А значит, найденный
То есть надо просто найти