Inverso multiplicativo modular
Definición
Un inverso multiplicativo modular de un entero es un entero tal que es congruente con módulo algún módulo . De forma formal: queremos encontrar un entero tal que
También denotaremos simplemente como .
Hay que notar que el inverso modular no siempre existe. Por ejemplo, sea , . Al revisar todos los valores posibles módulo , queda claro que no podemos encontrar un que satisfaga la ecuación anterior. Se puede demostrar que el inverso modular existe si y solo si y son coprimos (es decir, ).
En este artículo presentamos dos métodos para calcular el inverso modular cuando existe, y un método para calcular el inverso modular de todos los números en tiempo lineal.
Cálculo del inverso modular usando el algoritmo de Euclides extendido
Consideremos la siguiente ecuación (con incógnitas e ):
Esta es una ecuación diofántica lineal en dos variables. Como se muestra en el artículo enlazado, cuando , la ecuación tiene una solución que se puede encontrar con el algoritmo de Euclides extendido. Nótese que es también la condición para que exista el inverso modular.
Ahora, si tomamos módulo en ambos lados, podemos eliminar , y la ecuación queda:
Así, el inverso modular de es .
La implementación es la siguiente:
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;
}Nótese cómo modificamos x.
El x que resulta del algoritmo de Euclides extendido puede ser negativo, así que x % m también podría ser negativo, y primero hay que sumarle m para que quede positivo.
Otro método para calcular el inverso modular es usar el teorema de Euler, que afirma que la siguiente congruencia es cierta si y son coprimos:
es la función φ de Euler. De nuevo, nótese que el hecho de que y sean coprimos era también la condición para que exista el inverso modular.
Si es un número primo, esto se simplifica al pequeño teorema de Fermat :
Si multiplicamos ambos lados de las ecuaciones anteriores por , obtenemos:
- Para un módulo arbitrario (pero coprimo):
- Para un módulo primo :
A partir de estos resultados, podemos calcular fácilmente el inverso modular con el algoritmo de exponenciación binaria, que trabaja en tiempo .
Aunque este método es más fácil de entender que el del párrafo anterior, cuando no es un número primo hay que calcular la función φ de Euler, lo que implica factorizar , y eso puede ser muy difícil. Si se conoce la factorización prima de , la complejidad de este método es .
## Cálculo del inverso modular para módulos primos usando división euclidianaDado un módulo primo (o podemos aplicar el módulo para hacerlo más chico en 1 paso), según la división euclidiana
donde y , entonces
Nótese que este razonamiento no vale si no es primo, porque la existencia de no implica la existencia de en el caso general. Para verlo, intentemos calcular módulo con la fórmula anterior. Quisiéramos llegar a , ya que . Sin embargo, , y tenemos y , y no es invertible módulo .
Si el módulo es primo, en cambio, todos los con son invertibles módulo , y podemos usar la siguiente función recursiva (en C++) para calcular el inverso modular del número respecto de
int inv(int a) {
return a <= 1 ? a : m - (long long)(m/a) * inv(m % a) % m;
}La complejidad temporal exacta de esta recursión no se conoce. Está en algún punto entre y . Ver On the length of Pierce expansions . En la práctica esta implementación es rápida; por ejemplo, para el módulo siempre termina en menos de 50 iteraciones.
Aplicando esta fórmula, también podemos precomputar el inverso modular de cada número en el rango en .inv[1] = 1;
for(int a = 2; a < m; ++a)
inv[a] = m - (long long)(m/a) * inv[m%a] % m;Cálculo del inverso modular para un arreglo de números módulo
Supongamos que nos dan un arreglo y queremos calcular el inverso modular de todos sus números (todos son invertibles). En lugar de calcular el inverso de cada número, podemos ampliar la fracción por el producto de prefijos (excluyéndose a sí mismo) y el producto de sufijos (excluyéndose a sí mismo), y terminar calculando un solo inverso.
{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}
En el código podemos armar un arreglo de productos de prefijos (excluyéndose a sí mismo, empezando desde el elemento identidad), calcular el inverso modular del producto de todos los números y después multiplicarlo por el producto de prefijos y el producto de sufijos (excluyéndose a sí mismo). El producto de sufijos se calcula iterando de atrás hacia adelante.
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;
}