Перейти до основного вмісту

Обернений елемент за модулем

Означення​

Обернений елемент за модулем для цілого числа aa — це таке ціле число xx, що a⋅xa \cdot x конгруентне 11 за деяким модулем mm. Формально: ми хочемо знайти таке ціле число xx, що

a⋅x≡1mod  m.a \cdot x \equiv 1 \mod m.

Ми також будемо позначати xx просто як a−1a^{-1}.

Зауважимо, що обернений елемент за модулем існує не завжди. Наприклад, нехай m=4m = 4, a=2a = 2. Перебравши всі можливі значення за модулем mm, легко переконатися, що ми не можемо знайти a−1a^{-1}, яке задовольняє наведене рівняння. Можна довести, що обернений елемент за модулем існує тоді й лише тоді, коли aa та mm взаємно прості (тобто gcd⁡(a,m)=1\gcd(a, m) = 1).

У цій статті ми наведемо два методи знаходження оберненого елемента за модулем у випадку, коли він існує, і один метод знаходження оберненого елемента для всіх чисел за лінійний час.

Коли підходить цей алгоритм?
  • Чи виконується умова існування оберненого, тобто gcd⁡(a,m)=1\gcd(a, m) = 1 (для довільного aa без цієї умови оберненого немає)?
  • Чи потрібно ділити за модулем у задачі, де модуль mm не обов'язково простий? Тоді підходить розширений алгоритм Евкліда (формула am−2a^{m-2} через бінарне піднесення працює лише для простого mm).
  • Чи треба передобчислити обернені для всіх чисел 1…m−11 \ldots m-1 за один прохід? Тоді використовуйте лінійний метод із цієї статті замість mm окремих викликів.

Знаходження оберненого елемента за модулем за допомогою розширеного алгоритму Евкліда​

Розглянемо таке рівняння (з невідомими xx та yy):

a⋅x+m⋅y=1a \cdot x + m \cdot y = 1

Це лінійне діофантове рівняння з двома змінними. Як показано у статті за посиланням, коли gcd⁡(a,m)=1\gcd(a, m) = 1, рівняння має розв'язок, який можна знайти за допомогою розширеного алгоритму Евкліда. Зауважимо, що gcd⁡(a,m)=1\gcd(a, m) = 1 — це також умова існування оберненого елемента за модулем.

Тепер, якщо взяти обидві частини за модулем mm, ми можемо позбутися m⋅ym \cdot y, і рівняння набуває вигляду:

a⋅x≡1mod  ma \cdot x \equiv 1 \mod m

Отже, оберненим елементом для aa є xx.

Реалізація виглядає так:

int x, y;
int g = extended_euclidean(a, m, x, y);
if (g != 1) {
cout << "No solution!";
}
else {
x = (x % m + m) % m;
cout << x << endl;
}

Зверніть увагу на те, як ми змінюємо x. Отримане з розширеного алгоритму Евкліда значення x може бути від'ємним, тому x % m також може бути від'ємним, і нам спочатку доводиться додати m, щоб зробити його додатним.

Знаходження оберненого елемента за модулем за допомогою бінарного піднесення до степеня​

Інший метод знаходження оберненого елемента за модулем — використати теорему Ейлера, яка стверджує, що наступна конгруенція виконується, якщо aa та mm взаємно прості:

aϕ(m)≡1mod  ma^{\phi (m)} \equiv 1 \mod m

ϕ\phi — це функція Ейлера. Знову ж таки, зауважимо, що взаємна простота aa та mm була також умовою існування оберненого елемента за модулем.

Якщо mm — просте число, це спрощується до малої теореми Ферма:

am−1≡1mod  ma^{m - 1} \equiv 1 \mod m

Помножимо обидві частини наведених рівнянь на a−1a^{-1}, і отримаємо:

  • Для довільного (але взаємно простого) модуля mm: aϕ(m)−1≡a−1mod  ma ^ {\phi (m) - 1} \equiv a ^{-1} \mod m
  • Для простого модуля mm: am−2≡a−1mod  ma ^ {m - 2} \equiv a ^ {-1} \mod m

З цих результатів ми можемо легко знайти обернений елемент за модулем за допомогою алгоритму бінарного піднесення до степеня, який працює за час O(log⁡m)O(\log m).

Хоча цей метод простіший для розуміння, ніж метод, описаний у попередньому абзаці, у випадку, коли mm не є простим числом, нам потрібно обчислити функцію Ейлера ϕ\phi, що передбачає факторизацію mm, яка може бути дуже складною. Якщо розклад mm на прості множники відомий, то складність цього методу становить O(log⁡m)O(\log m).

Знаходження оберненого елемента за модулем для простих модулів за допомогою ділення з остачею​

Нехай дано простий модуль m>am > a (або ми можемо застосувати модуль, щоб зменшити число за один крок); згідно з діленням з остачею

m=k⋅a+rm = k \cdot a + r

де k=⌊ma⌋k = \left\lfloor \frac{m}{a} \right\rfloor та r=m mod ar = m \bmod a, тоді

  ⟹  0≡k⋅a+rmod  m  ⟺  r≡−k⋅amod  m  ⟺  r⋅a−1≡−kmod  m  ⟺  a−1≡−k⋅r−1mod  m\begin{align*} & \implies & 0 & \equiv k \cdot a + r & \mod m \\ & \iff & r & \equiv -k \cdot a & \mod m \\ & \iff & r \cdot a^{-1} & \equiv -k & \mod m \\ & \iff & a^{-1} & \equiv -k \cdot r^{-1} & \mod m \end{align*}

Зауважимо, що це міркування не виконується, якщо mm не є простим, оскільки існування a−1a^{-1} у загальному випадку не означає існування r−1r^{-1}. Щоб це побачити, спробуймо обчислити 5−15^{-1} за модулем 1212 за наведеною формулою. Ми хотіли б отримати 55, оскільки 5⋅5≡1 mod 125 \cdot 5 \equiv 1 \bmod 12. Однак 12=2⋅5+212 = 2 \cdot 5 + 2, і ми маємо k=2k=2 та r=2r=2, причому 22 не є оборотним за модулем 1212.

Проте якщо модуль простий, то всі aa з 0<a<m0 < a < m є оборотними за модулем mm, і ми можемо записати таку рекурсивну функцію для обчислення оберненого елемента за модулем для числа aa відносно mm

int inv(int a) {
return a <= 1 ? a : m - (long long)(m/a) * inv(m % a) % m;
}

Точна часова складність цієї рекурсії невідома. Вона десь між O(log⁡mlog⁡log⁡m)O(\frac{\log m}{\log\log m}) та O(m13−2177+ϵ)O(m^{\frac{1}{3} - \frac{2}{177} + \epsilon}). Див. On the length of Pierce expansions. На практиці ця реалізація швидка, наприклад, для модуля 109+710^9 + 7 вона завжди завершиться менш ніж за 50 ітерацій.

Обернений елемент для кожного числа за модулем mm​

Застосувавши цю формулу, ми також можемо попередньо обчислити обернений елемент за модулем для кожного числа з діапазону [1,m−1][1, m-1] за O(m)O(m).

inv[1] = 1;
for(int a = 2; a < m; ++a)
inv[a] = m - (long long)(m/a) * inv[m%a] % m;

Знаходження оберненого елемента за модулем mm для масиву чисел​

Припустимо, нам дано масив, і ми хочемо знайти обернений елемент за модулем для всіх чисел у ньому (усі вони оборотні). Замість того щоб обчислювати обернений елемент для кожного числа, ми можемо розширити дріб префіксним добутком (не включаючи саме число) та суфіксним добутком (не включаючи саме число), і в результаті обчислити лише один обернений елемент.

xi−1=1xi=x1⋅x2⋯xi−1⏞prefixi−1⋅ 1 ⋅xi+1⋅xi+2⋯xn⏞suffixi+1x1⋅x2⋯xi−1⋅xi⋅xi+1⋅xi+2⋯xn=prefixi−1⋅suffixi+1⋅(x1⋅x2⋯xn)−1\begin{align} x_i^{-1} &= \frac{1}{x_i} = \frac{\overbrace{x_1 \cdot x_2 \cdots x_{i-1}}^{\text{prefix}_{i-1}} \cdot ~1~ \cdot \overbrace{x_{i+1} \cdot x_{i+2} \cdots x_n}^{\text{suffix}_{i+1}}}{x_1 \cdot x_2 \cdots x_{i-1} \cdot x_i \cdot x_{i+1} \cdot x_{i+2} \cdots x_n} \\ &= \text{prefix}_{i-1} \cdot \text{suffix}_{i+1} \cdot \left(x_1 \cdot x_2 \cdots x_n\right)^{-1} \end{align}

У коді ми можемо просто побудувати масив префіксних добутків (не включаючи саме число, починаючи з нейтрального елемента), обчислити обернений елемент за модулем для добутку всіх чисел, а потім помножити його на префіксний та суфіксний добутки (не включаючи саме число). Суфіксний добуток обчислюється ітеруванням з кінця до початку.

std::vector<int> invs(const std::vector<int> &a, int m) {
int n = a.size();
if (n == 0) return {};
std::vector<int> b(n);
int v = 1;
for (int i = 0; i != n; ++i) {
b[i] = v;
v = static_cast<long long>(v) * a[i] % m;
}
int x, y;
extended_euclidean(v, m, x, y);
x = (x % m + m) % m;
for (int i = n - 1; i >= 0; --i) {
b[i] = static_cast<long long>(x) * b[i] % m;
x = static_cast<long long>(x) * a[i] % m;
}
return b;
}

Задачі для практики​

Відеоматеріали​