E018. Первообразные корни

e-maxx algorithm original: C/C++ #algorithm #emaxx #math #number-theory
선택한 UI 언어에 맞게 문제 텍스트를 러시아어에서 번역합니다. 코드는 변경하지 않습니다.

Источник: e-maxx.ru/algo, страница PDF 49.

정의

Первообразным корнем по модулю

(primitive root modulo

) называется такое number

, что все его степени по модулю

пробегают по всем числам, взаимно простым с

. Математически это формулируется таким образом: если

является первообразным корнем по модулю

, то для любого целого

такого, что

, найдётся

такое целое

, что

.

В частности, для случая простого

степени первообразного корня пробегают по всем числам от

до

.

Существование

Первообразный корень по модулю

существует тогда и только тогда, когда

является либо степенью

нечётного простого, либо удвоенной степенью простого, а также в случаях

,

,

. Эта теорема (которая была полностью доказана Гауссом в 1801 г.) приводится здесь без доказательства.

Связь с функцией Эйлера

Пусть

- первообразный корень по модулю

. Тогда можно показать, что наименьшее number

, для

которого

(т.е.

— показатель

(multiplicative order)), равно

. Более того, верно и

обратное, и этот факт будет использован нами ниже в 알고리즘е нахождения первообразного корня.

Кроме того, если по модулю

есть хотя бы один первообразный корень, то всего их

(т.к. циклическая группа

с

elementами имеет

генераторов).

알고리즘 нахождения первообразного корня

Наивный 알고리즘 потребует для каждого тестируемого значения

времени, чтобы вычислить все его степени

и проверить, что они все различны. Это слишком медленный 알고리즘, ниже мы с помощью нескольких известных теорем из теории чисел получим более быстрый 알고리즘.

Выше была приведена теорема о том, что если наименьшее number

, для которого

(т.е.

— показатель

), равно

, то

— первообразный корень. Так как для любого числа

выполняется теорема

Эйлера (

), то чтобы проверить, что

первообразный корень, достаточно проверить, что

для всех чисел

, меньших

, выполнялось

. Однако пока это слишком медленный 알고리즘. Из теоремы Лагранжа следует, что показатель любого числа по модулю

является делителем

. Таким

образом, достаточно проверить, что для всех собственных делителей

выполняется

. Это уже значительно более быстрый 알고리즘, однако можно пойти ещё дальше.

Факторизуем number

. Докажем, что в предыдущем 알고리즘е достаточно рассматривать в

качестве

лишь числа вида

. Действительно, пусть

— произвольный собственный делитель

.

Тогда, очевидно, найдётся такое

, что

, т.е.

. Однако, если бы

, то

мы получили бы:

т.е. всё равно среди чисел вида

нашлось бы то, для которого 문제 설명 не выполнилось, что и требовалось доказать. Таким образом, 알고리즘 нахождения первообразного корня такой. Находим

, факторизуем его. Теперь

перебираем все числа

, и для каждого считаем все величины

. Если для текущего

все эти числа оказались отличными от

, то это

и является искомым первообразным корнем.

실행 시간 알고리즘а (считая, что у числа

имеется

делителей, а возведение в

степень выполняется 알고리즘ом Бинарного возведения в степень, т.е. за

)

равно

плюс время факторизации числа

, где

— результат, т.е. значение искомого первообразного корня.

Про скорость роста первообразных корней с ростом

известны лишь приблизительные оценки. Известно,

что первообразные корни — сравнительно небольшие величины. Одна из известных оценок — оценка Шупа (Shoup), что, в предположении истинности гипотезы Римана, первообразный корень есть .

구현

Функция powmod() выполняет Binary exponentiation по модулю, а функция generator (int p) -

находит первообразный корень по простому модулю

(факторизация числа

здесь осуществлена

простейшим 알고리즘ом за

). Чтобы адаптировать эту функцию для произвольных

, достаточно

добавить вычисление функции Эйлера в переменной phi.

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;

}

C# 해법

자동 초안, 제출 전 검토
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    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) {
            List<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;
    }
}

C++ 해법

매칭됨/원본
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;
}

Java 해법

자동 초안, 제출 전 검토
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    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) {
            ArrayList<Integer> 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) {
                    boolean 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;
    }
}

Материал разбит как 알고리즘ическая 문제: изучить постановку, понять асимптотику и реализовать 알고리즘 на выбранном языке.

Vacancies for this task

활성 채용 with overlapping task tags are 표시됨.

전체 채용
아직 활성 채용이 없습니다.