Raíz discreta
El problema de hallar una raíz discreta se define de la siguiente manera. Dados un primo y dos enteros y , hallar todos los para los cuales:
El algoritmo
Resolveremos este problema reduciéndolo al problema del logaritmo discreto.
Apliquemos el concepto de raíz primitiva módulo . Sea una raíz primitiva módulo . Nótese que, como es primo, debe existir, y se puede hallar en más el tiempo de factorizar .
Podemos descartar fácilmente el caso en que . En este caso, obviamente hay una sola respuesta: .
Como sabemos que es primo y cualquier número entre 1 y se puede representar como una potencia de la raíz primitiva, podemos representar el problema de la raíz discreta de la siguiente manera:
donde
Esto, a su vez, se puede reescribir como
Ahora tenemos una incógnita , que es un problema de logaritmo discreto. La solución se puede hallar usando el algoritmo baby-step giant-step de Shanks en (o podemos verificar que no hay soluciones).
Habiendo hallado una solución , una de las soluciones del problema de la raíz discreta será .
Hallar todas las soluciones a partir de una solución conocida
Para resolver el problema dado por completo, necesitamos hallar todas las soluciones conociendo una de ellas: .
Recordemos el hecho de que una raíz primitiva siempre tiene orden , es decir, la menor potencia de que da 1 es . Por lo tanto, si sumamos el término al exponente, seguimos obteniendo el mismo valor:
De ahí que todas las soluciones sean de la forma:
.
donde se elige de modo que la fracción deba ser un entero. Para que esto sea cierto, el numerador tiene que ser divisible por el mínimo común múltiplo de y . Recordemos que el mínimo común múltiplo de dos números es ; obtendremos
.
Esta es la fórmula final para todas las soluciones del problema de la raíz discreta.
Implementación
Aquí hay una implementación completa, incluyendo procedimientos para hallar la raíz primitiva, el logaritmo discreto, y hallar e imprimir todas las soluciones.
int gcd(int a, int b) {
return a ? gcd(b % a, a) : b;
}
int powmod(int a, int b, int p) {
int res = 1;
while (b > 0) {
if (b & 1) {
res = res * a % p;
}
a = a * a % p;
b >>= 1;
}
return res;
}
// Encuentra la raíz primitiva módulo p
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 (int factor : fact) {
if (powmod(res, phi / factor, p) == 1) {
ok = false;
break;
}
}
if (ok) return res;
}
return -1;
}
// Este programa encuentra todos los números x tales que x^k = a (mod n)
int main() {
int n, k, a;
scanf("%d %d %d", &n, &k, &a);
if (a == 0) {
puts("1\n0");
return 0;
}
int g = generator(n);
// Algoritmo de logaritmo discreto baby-step giant-step
int sq = (int) sqrt (n + .0) + 1;
vector<pair<int, int>> dec(sq);
for (int i = 1; i <= sq; ++i)
dec[i-1] = {powmod(g, i * sq * k % (n - 1), n), i};
sort(dec.begin(), dec.end());
int any_ans = -1;
for (int i = 0; i < sq; ++i) {
int my = powmod(g, i * k % (n - 1), n) * a % n;
auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
if (it != dec.end() && it->first == my) {
any_ans = it->second * sq - i;
break;
}
}
if (any_ans == -1) {
puts("0");
return 0;
}
// Imprime todas las respuestas posibles
int delta = (n-1) / gcd(k, n-1);
vector<int> ans;
for (int cur = any_ans % delta; cur < n-1; cur += delta)
ans.push_back(powmod(g, cur, n));
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for (int answer : ans)
printf("%d ", answer);
}