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

Первісний корінь

Означення

У модульній арифметиці число gg називають первісним коренем за модулем n, якщо кожне число, взаємно просте з nn, конгруентне деякому степеню gg за модулем nn. Математично, gg є первісним коренем за модулем n тоді й лише тоді, коли для будь-якого цілого aa такого, що gcd(a,n)=1\gcd(a, n) = 1, існує таке ціле kk, що:

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

Тоді kk називають індексом або дискретним логарифмом числа aa за основою gg за модулем nn. Число gg також називають твірним елементом мультиплікативної групи цілих чисел за модулем nn.

Зокрема, у випадку, коли nn є простим числом, степені первісного кореня пробігають усі числа від 11 до n1n-1.

Існування

Первісний корінь за модулем nn існує тоді й лише тоді, коли:

  • nn дорівнює 1, 2, 4, або
  • nn є степенем непарного простого числа (n=pk)(n = p^k), або
  • nn є подвоєним степенем непарного простого числа (n=2pk)(n = 2 \cdot p^k).

Цю теорему довів Гаусс у 1801 році.

Зв'язок із функцією Ейлера

Нехай gg — первісний корінь за модулем nn. Тоді можна показати, що найменше число kk, для якого gk1(modn)g^k \equiv 1 \pmod n, дорівнює ϕ(n)\phi (n). Більше того, обернене також справедливе, і саме цей факт ми використаємо в цій статті для пошуку первісного кореня.

Крім того, кількість первісних коренів за модулем nn, якщо вони взагалі існують, дорівнює ϕ(ϕ(n))\phi (\phi (n) ).

Коли підходить цей алгоритм?
  • Чи модуль nn має форму, за якої первісний корінь узагалі існує (n{1,2,4}n \in \{1, 2, 4\}, n=pkn = p^k або n=2pkn = 2p^k для непарного простого pp)? Інакше шукати нема чого.
  • Чи можете ви розкласти на множники ϕ(n)\phi(n) — це потрібно, щоб перевіряти кандидатів лише за d=ϕ(n)pjd = \tfrac{\phi(n)}{p_j}? (для самого розкладу → Факторизація)
  • Чи задача справді потребує твірного мультиплікативної групи (наприклад, для дискретного логарифма чи дискретного кореня), а не просто степенів за модулем?

Алгоритм пошуку первісного кореня

Наївний алгоритм полягає в тому, щоб розглянути всі числа з відрізка [1,n1][1, n-1]. А потім перевірити кожне з них, чи є воно первісним коренем, обчислюючи всі його степені, щоб переконатися, що вони всі різні. Цей алгоритм має складність O(gn)O(g \cdot n), що було б надто повільно. У цьому розділі ми пропонуємо швидший алгоритм, який використовує кілька добре відомих теорем.

З попереднього розділу ми знаємо, що якщо найменше число kk, для якого gk1(modn)g^k \equiv 1 \pmod n, дорівнює ϕ(n)\phi (n), то gg є первісним коренем. Оскільки для будь-якого числа aa, взаємно простого з nn, з теореми Ейлера ми знаємо, що aϕ(n)1(modn)a ^ { \phi (n) } \equiv 1 \pmod n, то для перевірки того, чи є gg первісним коренем, достатньо перевірити, що для всіх dd, менших за ϕ(n)\phi (n), виконується gd≢1(modn)g^d \not \equiv 1 \pmod n. Однак цей алгоритм усе ще надто повільний.

З теореми Лагранжа ми знаємо, що мультиплікативний порядок будь-якого числа за модулем nn має бути дільником ϕ(n)\phi (n). Отже, достатньо перевірити для всіх власних дільників dϕ(n)d \mid \phi (n), що gd≢1(modn)g^d \not \equiv 1 \pmod n. Це вже значно швидший алгоритм, але ми можемо зробити ще краще.

Розкладемо ϕ(n)=p1a1psas\phi (n) = p_1 ^ {a_1} \cdots p_s ^ {a_s}. Доведемо, що в попередньому алгоритмі достатньо розглядати лише ті значення dd, які мають вигляд ϕ(n)pj\frac { \phi (n) } {p_j}. Справді, нехай dd — будь-який власний дільник ϕ(n)\phi (n). Тоді, очевидно, існує таке jj, що dϕ(n)pjd \mid \frac { \phi (n) } {p_j}, тобто dk=ϕ(n)pjd \cdot k = \frac { \phi (n) } {p_j}. Однак, якщо gd1(modn)g^d \equiv 1 \pmod n, ми б отримали:

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.

тобто серед чисел вигляду ϕ(n)pi\frac {\phi (n)} {p_i} знайшлося б принаймні одне таке, для якого умови не виконуються.

Тепер у нас є повний алгоритм пошуку первісного кореня:

  • Спочатку знаходимо ϕ(n)\phi (n) і розкладаємо його на множники.

  • Потім перебираємо всі числа g[1,n]g \in [1, n], і для кожного числа, щоб перевірити, чи є воно первісним коренем, робимо таке:

    • Обчислюємо всі gϕ(n)pi(modn)g ^ { \frac {\phi (n)} {p_i}} \pmod n.
    • Якщо всі обчислені значення відмінні від 11, то gg є первісним коренем.

    Час роботи цього алгоритму становить O(Anslogϕ(n)logn)O(Ans \cdot \log \phi (n) \cdot \log n) (припускаємо, що ϕ(n)\phi (n) має logϕ(n)\log \phi (n) дільників).

Шоуп (Shoup, 1990, 1992) довів, припускаючи узагальнену гіпотезу Рімана, що gg є O(log6p)O(\log^6 p).

Реалізація

У наведеному нижче коді припускається, що модуль p є простим числом. Щоб він працював для будь-якого значення p, ми маємо додати обчислення ϕ(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;
}

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