Skip to Content

Raíz primitiva

Definición

En aritmética modular, un número gg se llama raíz primitiva módulo n si todo número coprimo con nn es congruente con una potencia de gg módulo nn. Matemáticamente, gg es una raíz primitiva módulo n si y solo si para cualquier entero aa tal que gcd(a,n)=1\gcd(a, n) = 1, existe un entero kk tal que:

gka(modn)g^k \equiv a \pmod n.

kk se llama entonces el índice o logaritmo discreto de aa en base gg módulo nn. gg también se llama el generador del grupo multiplicativo de enteros módulo nn.

En particular, para el caso en que nn es primo, las potencias de la raíz primitiva recorren todos los números de 11 a n1n-1.

Existencia

Existe raíz primitiva módulo nn si y solo si:

  • nn es 1, 2, 4, o
  • nn es potencia de un primo impar (n=pk)(n = p^k), o
  • nn es el doble de una potencia de un primo impar (n=2pk)(n = 2 \cdot p^k).

Este teorema fue demostrado por Gauss en 1801.

Relación con la función de Euler

Sea gg una raíz primitiva módulo nn. Entonces podemos mostrar que el menor número kk para el cual gk1(modn)g^k \equiv 1 \pmod n es igual a ϕ(n)\phi (n). Además, el recíproco también es cierto, y este hecho se usará en este artículo para hallar una raíz primitiva.

Más aún, la cantidad de raíces primitivas módulo nn, si hay alguna, es igual a ϕ(ϕ(n))\phi (\phi (n) ).

Algoritmo para hallar una raíz primitiva

Un algoritmo naive es considerar todos los números en el rango [1,n1][1, n-1]. Y luego comprobar si cada uno es una raíz primitiva, calculando todas sus potencias para ver si son todas distintas. Este algoritmo tiene complejidad O(gn)O(g \cdot n), que sería demasiado lento. En esta sección proponemos un algoritmo más rápido usando varios teoremas conocidos.

De la sección anterior, sabemos que si el menor número kk para el cual gk1(modn)g^k \equiv 1 \pmod n es ϕ(n)\phi (n), entonces gg es una raíz primitiva. Como para cualquier número aa coprimo con nn, sabemos por el teorema de Euler que aϕ(n)1(modn)a ^ { \phi (n) } \equiv 1 \pmod n, entonces para comprobar si gg es raíz primitiva, basta con comprobar que para todo dd menor que ϕ(n)\phi (n), gd≢1(modn)g^d \not \equiv 1 \pmod n. Sin embargo, este algoritmo sigue siendo demasiado lento.

Por el teorema de Lagrange, sabemos que el índice de 1 de cualquier número módulo nn debe ser un divisor de ϕ(n)\phi (n). Así, basta verificar para todo divisor propio dϕ(n)d \mid \phi (n) que gd≢1(modn)g^d \not \equiv 1 \pmod n. Este ya es un algoritmo mucho más rápido, pero todavía podemos hacerlo mejor.

Factoricemos ϕ(n)=p1a1psas\phi (n) = p_1 ^ {a_1} \cdots p_s ^ {a_s}. Demostramos que en el algoritmo anterior basta considerar solo los valores de dd que tienen la forma ϕ(n)pj\frac { \phi (n) } {p_j}. En efecto, sea dd cualquier divisor propio de ϕ(n)\phi (n). Entonces, obviamente, existe un jj tal que dϕ(n)pjd \mid \frac { \phi (n) } {p_j}, es decir dk=ϕ(n)pjd \cdot k = \frac { \phi (n) } {p_j}. Sin embargo, si gd1(modn)g^d \equiv 1 \pmod n, obtendríamos:

gϕ(n)pjgdk(gd)k1k1(modn)g ^ { \frac { \phi (n)} {p_j} } \equiv g ^ {d \cdot k} \equiv (g^d) ^k \equiv 1^k \equiv 1 \pmod n.

es decir, entre los números de la forma ϕ(n)pi\frac {\phi (n)} {p_i}, habría al menos uno tal que las condiciones no se cumplieran.

Ahora tenemos un algoritmo completo para hallar la raíz primitiva:

  • Primero, hallar ϕ(n)\phi (n) y factorizarlo.

  • Luego iterar por todos los números g[1,n]g \in [1, n], y para cada número, para comprobar si es raíz primitiva, hacemos lo siguiente:

    • Calcular todos los gϕ(n)pi(modn)g ^ { \frac {\phi (n)} {p_i}} \pmod n.
    • Si todos los valores calculados son distintos de 11, entonces gg es una raíz primitiva.

    El tiempo de ejecución de este algoritmo es O(Anslogϕ(n)logn)O(Ans \cdot \log \phi (n) \cdot \log n) (suponiendo que ϕ(n)\phi (n) tiene logϕ(n)\log \phi (n) divisores).

Shoup (1990, 1992) demostró, asumiendo la hipótesis de Riemann generalizada , que gg es O(log6p)O(\log^6 p).

Implementación

El siguiente código asume que el módulo p es un número primo. Para que funcione para cualquier valor de p, hay que añadir el cálculo de ϕ(p)\phi (p).

int powmod (int a, int b, int p) { int res = 1; while (b) if (b & 1) res = int (res * 1ll * a % p), --b; else a = int (a * 1ll * a % p), b >>= 1; return res; } int generator (int p) { vector<int> fact; int phi = p-1, n = phi; for (int i=2; i*i<=n; ++i) if (n % i == 0) { fact.push_back (i); while (n % i == 0) n /= i; } if (n > 1) fact.push_back (n); for (int res=2; res<=p; ++res) { bool ok = true; for (size_t i=0; i<fact.size() && ok; ++i) ok &= powmod (res, phi / fact[i], p) != 1; if (ok) return res; } return -1; }