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

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

Означення

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

ax1modm.a \cdot x \equiv 1 \mod m.

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

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

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

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

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

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

ax+my=1a \cdot x + m \cdot y = 1

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

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

ax1modma \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)1modma^{\phi (m)} \equiv 1 \mod m

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

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

am11modma^{m - 1} \equiv 1 \mod m

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

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

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

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

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

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

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

де k=mak = \left\lfloor \frac{m}{a} \right\rfloor та r=mmodar = m \bmod a, тоді

    0ka+rmodm    rkamodm    ra1kmodm    a1kr1modm\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 не є простим, оскільки існування a1a^{-1} у загальному випадку не означає існування r1r^{-1}. Щоб це побачити, спробуймо обчислити 515^{-1} за модулем 1212 за наведеною формулою. Ми хотіли б отримати 55, оскільки 551mod125 \cdot 5 \equiv 1 \bmod 12. Однак 12=25+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(logmloglogm)O(\frac{\log m}{\log\log m}) та O(m132177+ϵ)O(m^{\frac{1}{3} - \frac{2}{177} + \epsilon}). Див. On the length of Pierce expansions. На практиці ця реалізація швидка, наприклад, для модуля 109+710^9 + 7 вона завжди завершиться менш ніж за 50 ітерацій.

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

Застосувавши цю формулу, ми також можемо попередньо обчислити обернений елемент за модулем для кожного числа з діапазону [1,m1][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 для масиву чисел

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

xi1=1xi=x1x2xi1prefixi1 1 xi+1xi+2xnsuffixi+1x1x2xi1xixi+1xi+2xn=prefixi1suffixi+1(x1x2xn)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;
}

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

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