콘텐츠로 이동

원시근

정의

모듈러 연산에서 $n$과 서로소인 모든 수가 $n$을 법으로 하여 $g$의 거듭제곱과 합동이면, 수 $g$를 primitive root modulo n이라고 부릅니다. 수학적으로 $g$가 primitive root modulo n일 필요충분조건은 $\gcd(a, n) = 1$을 만족하는 임의의 정수 $a$에 대해 다음을 만족하는 정수 $k$가 존재하는 것입니다:

$g^k \equiv a \pmod n$.

이때 $k$를 $n$을 법으로 하고 $g$를 밑으로 하는 $a$의 index 또는 discrete logarithm이라고 부릅니다. 또한 $g$를 $n$을 법으로 하는 정수의 곱셈군의 generator라고도 부릅니다.

특히 $n$이 소수인 경우, 원시근의 거듭제곱은 $1$부터 $n-1$까지의 모든 수를 거칩니다.

존재성

$n$을 법으로 하는 원시근이 존재할 필요충분조건은 다음과 같습니다:

  • $n$이 1, 2, 4이거나,
  • $n$이 홀수 소수의 거듭제곱 $(n = p^k)$이거나,
  • $n$이 홀수 소수의 거듭제곱의 두 배 $(n = 2 \cdot p^k)$인 경우입니다.

이 정리는 1801년에 Gauss가 증명하였습니다.

오일러 함수와의 관계

$g$를 $n$을 법으로 하는 원시근이라고 합시다. 그러면 $g^k \equiv 1 \pmod n$을 만족하는 가장 작은 수 $k$가 $\phi (n)$과 같음을 보일 수 있습니다. 또한 그 역도 성립하며, 이 글에서는 이 사실을 이용하여 원시근을 찾습니다.

나아가 $n$을 법으로 하는 원시근이 존재한다면, 그 개수는 $\phi (\phi (n) )$과 같습니다.

원시근을 찾는 알고리즘

단순한 알고리즘은 $[1, n-1]$ 범위의 모든 수를 고려하는 것입니다. 그런 다음 각 수의 모든 거듭제곱을 계산하여 그것들이 모두 서로 다른지 확인함으로써 각 수가 원시근인지 판별합니다. 이 알고리즘의 복잡도는 $O(g \cdot n)$이며, 이는 너무 느립니다. 이 절에서는 잘 알려진 몇 가지 정리를 이용하여 더 빠른 알고리즘을 제시합니다.

이전 절에서 $g^k \equiv 1 \pmod n$을 만족하는 가장 작은 수 $k$가 $\phi (n)$이면 $g$가 원시근임을 알았습니다. $n$과 서로소인 임의의 수 $a$에 대해 오일러 정리로부터 $a ^ { \phi (n) } \equiv 1 \pmod n$임을 알고 있으므로, $g$가 원시근인지 확인하려면 $\phi (n)$보다 작은 모든 $d$에 대해 $g^d \not \equiv 1 \pmod n$임을 확인하는 것으로 충분합니다. 그러나 이 알고리즘은 여전히 너무 느립니다.

라그랑주 정리로부터 $n$을 법으로 하는 임의의 수에 대해 1의 지수는 반드시 $\phi (n)$의 약수여야 함을 알 수 있습니다. 따라서 모든 진약수 $d \mid \phi (n)$에 대해 $g^d \not \equiv 1 \pmod n$임을 확인하는 것으로 충분합니다. 이것만으로도 이미 훨씬 빠른 알고리즘이지만, 더 개선할 수 있습니다.

$\phi (n) = p_1 ^ {a_1} \cdots p_s ^ {a_s}$로 소인수분해합니다. 앞의 알고리즘에서 $\frac { \phi (n) } {p_j}$ 꼴의 $d$ 값만 고려해도 충분하다는 것을 증명하겠습니다. 실제로 $d$를 $\phi (n)$의 임의의 진약수라고 합시다. 그러면 자명하게 $d \mid \frac { \phi (n) } {p_j}$, 즉 $d \cdot k = \frac { \phi (n) } {p_j}$를 만족하는 $j$가 존재합니다. 그런데 $g^d \equiv 1 \pmod n$이라면 다음을 얻게 됩니다.

$g ^ { \frac { \phi (n)} {p_j} } \equiv g ^ {d \cdot k} \equiv (g^d) ^k \equiv 1^k \equiv 1 \pmod n$.

즉, $\frac {\phi (n)} {p_i}$ 꼴의 수 가운데 조건을 만족하지 않는 수가 적어도 하나 존재하게 됩니다.

이제 원시근을 찾는 완전한 알고리즘을 얻었습니다.

  • 먼저 $\phi (n)$을 구하고 이를 소인수분해합니다.
  • 그런 다음 모든 수 $g \in [1, n]$을 순회하면서, 각 수가 원시근인지 확인하기 위해 다음을 수행합니다.

    • 모든 $g ^ { \frac {\phi (n)} {p_i}} \pmod n$을 계산합니다.
    • 계산한 값이 모두 $1$과 다르면 $g$는 원시근입니다.

    이 알고리즘의 실행 시간은 $O(Ans \cdot \log \phi (n) \cdot \log n)$입니다($\phi (n)$의 약수가 $\log \phi (n)$개라고 가정합니다).

Shoup(1990, 1992)은 일반화된 리만 가설을 가정하여 $g$가 $O(\log^6 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;
}