Processing math: 1%
본문 바로가기

Algorithm/D. Math & Number Theory

71. Order & Discrete Logarithm

 

모듈러 산술이 사용되는 이산 구조에서의 추가적인 연산에 대해 알아본다.


서로소인 두 정수 a,m와 양의 정수 k에 대해 a^k \equiv 1 \pmod{m}이고 k보다 작은 임의의 양의 정수 k'에 대해 a^{k'} \not\equiv 1 \pmod{m}일 때 k를 모듈러 m에 대한 a위수(Order)라고 하며 기호로는 \text{ord}_m(a)로 나타낸다. 오일러 토션트 함수의 성질에 의하면 a^{\phi(m)} \equiv 1 \pmod{m}이므로  a의 위수 k\phi(m)보다 커질 수 없으며 조금 더 확장하면 k\phi(m)의 약수가 되어야 한다.

 

모듈러 m에 대한 a의 위수가 \phi(m)일 때 a를 모듈러 m에 대한 원시근(Primitive Root)이라고 한다. a가 모듈러 m에 대한 원시근일 때, a^1, a^2, \ldots, a^{\phi(m)}m으로 나눈 나머지는 모두 다르며 각각의 나머지는 m과 서로소이다. 증명은 다음과 같이 할 수 있다.

더보기

각각의 나머지가 m과 서로소라는 사실은 am의 소인수가 하나도 곱해지지 않았다는 사실에서 쉽게 알 수 있다. 각각의 나머지가 서로 다르다는 사실은 다음과 같이 증명이 가능하다.

x, y\phi(m) 이하의 양의 정수이고 x < y, a^x \equiv a^y \pmod{m}이라고 하면 \gcd(a, m) = 1이므로 a^{y-x} \equiv 1 \pmod{m}이다. 이때 y-x < \phi(m)이고, 이것은 \text{ord}_m(a) = \phi(m)이라는 사실과 모순이다. 따라서 위의 식을 만족하는 쌍 x, y는 존재하지 않으며, 이는 각각의 나머지가 서로 다르다는 것을 의미한다.

 

원시근을 갖는 정수는 2, 4, p^k이다. (p는 홀수 소수, k는 양의 정수) 이 글에서는 이에 대한 증명 전체를 다루지 않지만, 일부 정수의 경우 원시근의 존재 여부를 확인하는 방법이 어렵지 않으므로 다루었다.

더보기
  • m = 2인 경우와 m = 4인 경우는 직접 계산하자. m4일 때는 3이 원시근이다.
  • 임의의 홀수 x에 대해 x^28로 나눈 나머지는 1이므로 m8의 배수일 경우 원시근이 존재하지 않는다.
  • m이 서로 다른 두 홀수 소수 x, y의 공배수일 경우 다음과 같은 방법으로 원시근이 존재하지 않음을 보일 수 있다. m보다 작은 양의 정수 am의 원시근이라고 가정하면, \text{ord}_x(a) = x-1, \text{ord}_y(a) = y-1이다. 이때 am의 원시근이므로 \text{ord}_{xy}(a) = (x-1)(y-1)이어야 하고, 이것은 \text{lcm}(x-1, y-1) = (x-1)(y-1)을 의미한다. 따라서 x-1y-1이 서로소여야 하는데, x-1y-1이 모두 짝수이므로 서로소가 될 수 없다. 이것은 am의 원시근이라는 처음의 가정이 잘못되었음을 의미한다.

 

m이 원시근을 갖는다면 원시근의 개수는 \phi(\phi(m))이다. 이에 대한 증명은 참고 링크의 블로그 2에서 확인할 수 있다.

 

원시근을 빠르게 찾는 확실한 방법에 대해서는 아직도 연구가 진행중이지만, 원시근이 존재하는 경우 그 개수가 충분히 많다고 할 수 있으므로 m 미만의 임의의 양의 정수를 하나 고른 뒤 그 수가 m의 원시근인지 확인하는 과정을 반복하면 높은 확률로 원시근을 빠르게 찾을 수 있다. 고른 수가 m의 원시근인지 확인하기 위해서는 위수를 계산하면 되며, 위수를 계산하는 방법은 참고 링크의 블로그 1에서 확인할 수 있다.


원시근에 대한 정의를 바탕으로 이산 로그(Discrete Logarithm)를 정의할 수 있다. am의 원시근이고 a^k \equiv p \pmod{m}일 때 k를 나머지 변수에 대해서 나타내면 일반적인 로그 형태가 되는데 모듈러 산술이 사용되는 이산 구조의 로그이므로 이를 이산 로그라고 하고 k = \text{ind}_a p로 나타낸다.

 

이산 로그를 구하는 방법 중 하나인 아기걸음 거인걸음(Baby-Step Giant-Step Algorithm, BSGS)에 대해서 소개하면서 이번 글을 마무리하려고 한다. 목표는 a^k \equiv p \pmod{m}이고 a, p, m이 주어졌을 때 k를 구하는 것인데, m이 작은 경우 a를 계속 곱해 나가면서 k를 구할 수도 있지만 시간복잡도가 \Theta(m)이므로 m10^9 정도로 커지면 속도가 너무 느려진다. Baby-Step Giant-Step Algorithm은 제곱근 분할법과 유사한 중간 만남 기법을 이용해서 시간복잡도를 \Theta(\sqrt{m}\text{ lg }m)으로 줄인다. (로그는 마지막에 서로 같은 값의 쌍을 찾는 과정에서 붙는다.) 먼저 \lceil \sqrt{m} \rceil = v라고 하면, 이산 로그가 존재할 경우 m보다 작은 양의 정수 k가 항상 존재하므로 k = iv + j로 나타낼 수 있다. (0 \le i, j < v) 이것을 처음 식에 대입한 다음 식을 변형하면 다음과 같이 된다.

a^{iv+j} \equiv p \pmod{m}

a^{iv} \equiv pa^{-j} \pmod{m}

(a^v)^i \equiv p(a^{-1})^j \pmod{m}

이제 i0, 1, \ldots, v-1일 때 (a^v)^i \,\bmod\, m의 값과 j0, 1, \ldots, v-1일 때 p(a^{-1})^j \,\bmod\, m의 값을 모두 구한 다음 양쪽에서 서로 같은 값이 나오는 경우를 찾는다. std::map에 저장한 다음 찾는 것이 알기 쉽다. i가 최대한 작게, i가 같다면 j가 최대한 작은 쌍을 찾으면 k가 될 수 있는 최소의 양의 정수를 구할 수 있다.

구현은 다음과 같다. 만약 구하는 이산 로그가 존재하지 않을 경우 discrete_log 함수는 -1을 반환한다. pow 함수phi 함수는 이전 글들에서 정의한 것과 동일하다. (phi 함수의 경우 매개변수의 자료형과 반환형으로 int형 대신 long long형을 사용했으나 나머지는 전부 같다.)

typedef long long LL;
map<LL, LL>M;
LL pow(LL x, LL y, LL p);
LL phi(LL n);
LL discrete_log(LL a, LL p, LL m)
{
    LL v = ceil(sqrt(m)), k = pow(a, phi(m) - 1, m);
    for(int i = 0; i < v; i++)
    {
        if(M.find(p) == M.end())M[s] = i;
        p = p * k % m;
    }
    k = pow(a, v, m);
    p = 1;
    for(int i = 0; i < v; i++)
    {
        if(M.find(p) != M.end())return i * v + M[p];
        p = p * k % m;
    }
    return -1;
}

이 방법을 이용하면 이산 로그를 빠르게 구할 수 있으며 am의 원시근이 아닌 경우에도 위 식을 만족하는 k를 구할 수 있다. 그밖에도 이산 로그를 구하는 다양한 방법이 존재하며, 이에 대한 내용은 참고 링크의 블로그 3에서 볼 수 있다.

 

[참고 링크]

 

순환군

원시근

블로그 1

블로그 2

블로그 3

 

[연습문제]

 

BOJ 4357. 이산 로그 (Platinum I)

더보기

Baby-Step Giant-Step으로 이산 로그를 구하면 되는데, 이산 로그가 존재하지 않는 경우도 입력될 수 있으므로 그런 경우를 처리해 주어야 한다. 마지막에 양변의 값이 같은 쌍이 하나도 나오지 않으면 이산 로그가 존재하지 않는다.

 

→ solved.ac tag: discrete_log

'Algorithm > D. Math & Number Theory' 카테고리의 다른 글