Дискретное логарифмирование: baby-step giant-step для a^x ≡ b (mod m) — как и за сколько?
Дано a^x ≡ b (mod m), нужно найти x. Перебор всех x — это O(m), слишком долго при m ~ 10^9. Слышал про алгоритм «baby-step giant-step» за O(sqrt m). Можете объяснить идею и дать реализацию для простого модуля?
2 ответа
Baby-step giant-step (BSGS) решает a^x ≡ b (mod m) за O(sqrt m) времени и памяти. Идея — meet in the middle. Представим x = i·n − j, где n = ceil(sqrt(m)), 0 ≤ i ≤ n, 0 ≤ j < n. Тогда уравнение превращается в a^{i·n} ≡ b·a^{j} (mod m).
Алгоритм:
- Baby steps: для всех
jот 0 доn-1кладём в хеш-таблицуb·a^j mod m → j. - Giant steps: для всех
iот 1 доnсчитаемa^{i·n} mod mи ищем в таблице. Нашли совпадение →x = i·n − j.
long long bsgs(long long a, long long b, long long m) { // m простое, a не кратно m
a %= m; b %= m;
long long n = (long long)sqrt((double)m) + 1;
unordered_map<long long, long long> table;
long long cur = b;
for (long long j = 0; j < n; j++) { // baby steps: b * a^j
table[cur] = j;
cur = cur * a % m;
}
long long an = binpow(a, n, m); // a^n
cur = 1;
for (long long i = 1; i <= n; i++) { // giant steps: a^(i*n)
cur = cur * an % m;
if (table.count(cur))
return i * n - table[cur];
}
return -1; // решения нет
}
Время и память — O(sqrt m). Кладём b·a^j сначала и берём наименьший подходящий x (поэтому при коллизии в таблице оставляем больший j или перебираем i по возрастанию).
Нюансы: (1) для составного модуля или когда gcd(a, m) ≠ 1 базовый BSGS ломается — нужна обобщённая версия, которая сначала «выносит» общие множители. (2) unordered_map может ловить антитесты на коллизии хеша — на Codeforces безопаснее задать свою хеш-функцию или брать sqrt(m) чуть с запасом и сортированный массив + бинпоиск. (3) BSGS применим не только к степеням: это общий приём meet-in-the-middle для уравнений вида f(i) = g(j).