소수와 약수, 배수의 정의
소수는 영어로 prime 이라고 한다. 또한, $p$가 소수일 때 $p^k$를 prime power 라고 한다.
정수 $a, b$가 있을 때 $a$가 $b$의 약수라는 것은 정수 $n$이 있어 $b=an$이라는 것이다. 이때 $b$를 $a$의 배수라고 한다.
자연수 $p\geq2$의 약수가 $1$과 $p$ 뿐이면, $p$를 소수라고 부른다.
소수 판별 알고리즘
소수의 정의만 가지고 단순히 생각해 보자. 주어진 수 $N$을 $1$부터 $N$까지 나누어 보고, 나누어 떨어지는 수가 2개라면 소수이다.
다르게 말한다면, $N$을 $2$부터 $N-1$까지의 수로 나누어 봤을 때 나누어 떨어지는 경우가 있으면 소수가 아니고, 나누어 떨어지는 경우가 없으면 소수라는 것이다.
|
|
위 코드는 주어진 수가 소수인지 판별하는 c++
코드이다.
2부터 검사하기에, 1이 들어오는 경우를 예외처리 해 주어야 한다.
위 코드는 $2$부터 $N-1$ 까지의 모든 수를 계산해 본다. 따라서 시간복잡도는 $O(N)$이다.
소수 판별 알고리즘 최적화
합성수 $N$에서 $1$을 제외한 가장 작은 약수를 $d$라고 하자. $\frac{N}{d}$도 $1$이 아닌 $N$의 약수이므로, $d \leq\frac{N}{d}$ 이다. 우변의 $d$를 이항시키면 $d^2 \leq N$이고, 둘 다 자연수이므로 $d \leq \sqrt{N}$이다.
즉, 소수를 판별할 때 2부터 $\sqrt{N}$ 까지만 검사 해 주어도 충분하다.
반복문의 범위를 (int i = 2; i <= sqrt(n); i++)
로 해도 괜찮지만, sqrt
함수는 실수연산이기에 오차가 발생할 수 있다. 따라서 for (int i = 2; i*i <= n; i++)
으로 작성하는 것을 권장한다.
|
|
위 코드의 시간복잡도는 $O(\sqrt{N})$이다.
소수 판별 예제 문제
약수 구하기 알고리즘
소수 판별 알고리즘을 조금만 응용하면 약수를 구할 수 있다.
$1$부터 $\sqrt{n}$까지 나누어 떨어질 때마다 벡터에 약수를 넣어 주면 된다.
자연수 $d$가 $n$의 약수라면, $n/d$ 역시 $n$의 약수라는 것을 잊지 말자. 이 때, $n=m^2$이 제곱수인 경우 $\sqrt{n}=m$이 약수로 두 번 세지는 경우를 조심하자.
|
|
$O(\sqrt{N})$의 시간복잡도 내에 약수를 모두 구할 수 있다.
약수를 구한 이후 정렬을 해 주고 있다. STL 정렬의 시간복잡도가 $O(NlogN)$이라는 것은 모두 알고 있을 것이다.
long long
범위 내 약수의 개수의 상한선은 $\sqrt[3]{N}$개라고 어림짐작해볼 수 있다.
따라서 약수의 개수가 아무리 많아도 정렬에 쓰이는 시간복잡도는 $O(\sqrt[3]{N}log({\sqrt[3]{N}}))$이하이다.
양수 범위에서 $\sqrt[3]{N}log({\sqrt[3]{N}}) <\sqrt{N}$임이 자명하므로, 위 코드의 시간복잡도는 $O(\sqrt{N})$이다.
약수 구하기 알고리즘 최적화
위 코드를 조금만 개선하면 정렬을 할 필요가 없다.
v.push_back(d);
대신 다른 벡터에 담아둔 뒤, 역순으로 넣어주면 정렬된 약수를 구할 수 있다.
|
|
약수 구하기 예제 문제
범위 내 소수를 모두 구하는 알고리즘
기존 방법에서 반복문을 돌려 소수를 모두 찾으려 한다면, 범위가 조금만 커져도 시간초과가 뜰 것이다. 범위 내 소수를 모두 구해야 한다면, 에라토스테네스의 체를 활용하면 된다.
에라토스테네스의 체 알고리즘과 최적화에 대해서는 여기를 참고하고, 이번에는 간단하게 코드만 보고 넘어가자.
|
|
소인수 분해 알고리즘
여태까지 잘 따라왔다면, 소인수분해도 간단하게 할 수 있을 것이다.
factorize
함수는 주어진 $N$을 효율적으로 소인수분해 하는 함수이다.
|
|
번외 알고리즘
정수론 파트에는 어렵지만 재미있는 알고리즘들이 많다. 굳이 알 필요는 없지만, 번외로 준비해 봤다.
폴라드 로 소인수분해
int
범위 까지는 괜찮은데, long long
범위의 수를 위 소인수분해 알고리즘으로 계산하면 TLE가 난다.
이런 문제들은 범위가 $1 \leq N \leq 10^{18}$인데, 밀러-라빈 소수 판정법을 이용하여 폴라드 로 소인수분해 알고리즘을 구현하여 소인수분해한 후, DFS 등으로 약수를 구해주어야 한다.
나중에 시간이 되면 위 알고리즘도 다룰 예정이다.
소수 계량 함수
소수 계량 함수 $\pi(n)$은 $n$ 이하의 소수 개수를 나타내는 함수이다. 이 함수에 대해 다음 식이 성립한다.
$$\pi(n) \approx \frac{n}{\ln(n)}$$
예를 들어 $\pi(10^6)$의 근삿값은 $72382$고, 정확한 값은 $78498$이다.
이 함수를 이용하면 소수의 개수와 관련된 문제에서 시간복잡도를 간접적으로 구할 수 있다.