약수관련하여 총정리해본다.
약수관련해서는 다음과 같은 내용을 알아야 한다.
번호 | 항목 | 설명 |
1 | 어떤 수 n에 대한 약수 구하기. | - 약수는 어떤 수 n을 나누는 모든 수 |
2 | 어떤 수 n에 대한 소인수분해 하기. | - 소인수분해는, 어떤 수 n을 나눌 수 있는 소수들의 지수승의 곱으로 나타낸 것 |
3 | 어떤 수 n에 대한 약수의 개수 구하기 | - 약수의 개수는, 모든 약수를 구해서 그 개수를 셀수도 있으나, 소인수분해를 한 후 그 지수승을 이용해서 구하는게 정석 - 예를들어 $12=2^2 \times 3$, 약수의 개수는 (3+1)(1+1)=8개 |
4 | 어떤 수 n에 대한 약수의 합 구하기 | - 약수를 모두 구해서 합을 구하는 것보다, 소인수분해를 한 후 구하는게 정석 - n=12의 경우 약수의 합은 (1+2+4)(1+3)=28 |
5 | 어떤 수 n에 대한 약수의 곱 구하기 | - n=12의 경우 약수의 곱은 $1 \times (2 \times 4) \times 3 \times (2 \times 4) = 8 \times 24 = 192$ |
6 | 최대 공약수 | |
7 | 최소 공배수 | |
8 | 친화수 | |
9 | 완전수, 과잉수, 부족수 |
1. 어떤 수 n에 대한 약수 구하기
어떤 수 n에 대한 약수를 i라고 한다면 $n % i == 0$이다.
약수를 구하는 가장 간단한 방법은, n보다 작은 정수 i에 대해 $n % i == 0$이 되는지 확인해 보는 것이다.
이때, $2..=\sqrt{n}$ 까지만 조사하면 된다.
예를 들어 12의 약수를 구한다면 $\sqrt{12}=3$이다. 실제 제곱근을 floor한 값이다.
약수는 $2..=3$ 까지만 조사해 보면 된다.
- 12 % 2 = 0 --> {2, 6}이 약수
- 12 % 3 = 0 --> {3, 4}가 약수
여기에 1과 자기 자신이 12를 합쳐서 {1,2,6,3,4,12}가 약수가 된다.
//1-1. 약수 구하기: brute-force
#[cfg(test)]
//n에 대한 모든 약수를 구한다.
fn get_divisors(n:usize) -> Vec<usize>{
let mut v:Vec<usize> = Vec::new();
v.push(1); //1은 모둔 수의 약수
let sqrt_n = (n as f64).sqrt() as usize; //sqrt(n)까지만 조사해도 충분
for i in 2..=sqrt_n {
if n % i == 0 {
v.push(i); v.push(n/i);
}
}
if n == sqrt_n * sqrt_n {v.pop();} //n=25의 경우 5가 2번 들어가 있음. 해서, 마지막 5를 제거
v.push(n); //자기자신도 약수
return v;
}
코드에서 for 루프를 벗어난 후, $n == sqrt_n * sqrt_n $ 인지 확인했다. 이는 n=25와 같은 경우는, for 루프에서 {5, 5}가 두 번 약수인 것처럼 벡터에 들어가기에, 이 중 하나를 빼기 위해서이다.
2. 소인수 분해
정수 n을 소수들의 지수승의 곱으로 표현하는 것을 소인수분해라 한다.
소인수분해를 하고 나면, 이를 통해 약수의 개수도 알 수 있고, 약수의 합과 곱도 쉽게 알 수 있다.
어떤 수에 대해 이 수가 어떤 소수의 곱인지를 알아내는 특별한 알고리즘은 없다. 모두 다 대입을 해보는 수밖에 없다.
즉, 어떤 수 n에 대해, 그 보다 작은 소수들로 나눠가면서 $n % p == 0$이 되는지 확인해 가며 구할 수밖에 없다.
먼저, 소수가 아닌 그냥 2부터 시작되는 정수들을 이용해서 $n % i == 0$이 되는지 확인하면 소인수분해를 하는 코드를 짜 보겠다. 이처럼 소수가 아닌 그냥 2부터 나눠가면서 해도, 끝내는 소수의 곱만 남게 된다.
//2-1. 소인수 분해: 2보다 큰 일반 정수들로 나눠가면서 구한다.
#[cfg(test)]
//n에 대해 소인수 분해
fn factorization(mut n:usize) -> std::collections::HashMap<usize,usize> {
use std::collections::HashMap;
let mut f_map:HashMap<usize, usize> = HashMap::new();
let sqrt_n = (n as f64).sqrt() as usize;
for i in 2..=sqrt_n {
while n % i == 0 {
if f_map.contains_key(&i){
f_map.insert(i, f_map[&i]+1);
}else{
f_map.insert(i, 1);
}
n /= i;
}
}
if n != 1 {f_map.insert(n, 1);}
return f_map;
}
작은 수 n에 대해서는 위와 같이 2보다 큰 정수들을 차례로 나눠보면서 구해도 큰 속도 문제없으나, 아주 큰 수에 대해서는 모든 정수에 대해서 검사해야 해서 속도가 느려진다.
소수에 대해서만 나눠지는지 확인하면 큰 수에 대해서도 꽤 빠르게 소인수 분해할 수 있다.
소수는 $\sqrt{n}$ 이하까지만 구하면 되고, 소수를 구하는 것은 에라토스테네스의 체를 이용해서 비교적 빨리 구할 수 있다.
//2-2. 소인수분해: 소수구한 수 이용
#[cfg(test)]
//n에 대해 소인수 분해
fn factorization1(mut n:usize) -> std::collections::HashMap<usize,usize> {
use std::collections::HashMap;
//1. 소수 확보
let sqrt_n = (n as f64).sqrt() as usize;
let primes = find_primes(sqrt_n);
//2. 소인수분해
let mut f_map:HashMap<usize, usize> = HashMap::new();
for p in primes {
while n % p == 0 {
if f_map.contains_key(&p){
f_map.insert(p, f_map[&p]+1);
}else{
f_map.insert(p, 1);
}
n /= p;
}
}
if n != 1 {f_map.insert(n, 1);}
return f_map;
}
#[cfg(test)]
//find all primes <= n
fn find_primes(n:usize) -> Vec<usize> {
//1. sieve
let mut sieve:Vec<usize> = vec![1; n+1];
sieve[0]=0; sieve[1]=0;
let sqrt_n = (n as f64).sqrt() as usize;
for i in 2..=sqrt_n {
for j in (2*i..=n).step_by(i) {
sieve[j] = 0;
}
}
//2. primes
let mut primes:Vec<usize> = Vec::new();
for i in 2..sieve.len() {
if sieve[i] == 1 {primes.push(i);}
}
return primes;
}
3. 약수의 개수 구하기
약수의 개수는, 모든 약수를 구해서 그 숫자를 세면 되나, 큰 수에 대해서는 소인수분해를 한 후 그 지수를 이용해서 구하는 것이 속도가 빠르다. 소인수분해는 $\sqrt{n}$보다 작은 소수에 대해서만 조사하면 되기 때문이다.
어떤 수 n에 대해 소인수분해한 것이 $n = p^a \times q^b$라면, n에 대한 약수의 개수는 $(a+1)(b+1)$이다.
즉, 소인수분해 했을 때 각 소인수에 대한 (지수+1)을 곱한 것이 약수의 개수이다.
//3. 약수의 개수: 소수 이용한 소인수 분해 후 개수 구한다.
#[cfg(test)]
fn get_divisors_count(mut n:usize) -> usize {
let sqrt_n = (n as f64).sqrt() as usize;
let primes = find_primes(sqrt_n);
let mut cnt = 1;
for p in primes {
let mut e = 1;
while n % p == 0 {
e += 1;
n /= p;
}
if e > 1 {cnt *= e;}
if n == 1 {break;}
}
if n != 1 {cnt *= 2;}
return cnt;
}
4. 약수의 합 구하기
어떤 수 n의 모든 약수의 합은, 약수를 모두 구한 후 합을 해도 되나, 큰 수에 대해서는 소인수분해를 한 후 구하는 것이 빠르다.
12를 소인수 분해하면 $12 = 2^2 \times 3$이로, 12의 약수의 합은 $(2^0+2^1+2^2)(3^0+3^1)=7 \times 4=28$
#[cfg(test)]
// calculate sum of all divisios of n
// n = 12, 12=2^2 x 3, sum=(1+2+4)(1+3)=28 {1,2,3,4,6,12}
// n = 10, 10=2 x 5, sum=(1+2)(1+5)=18 {1,2,5,10}
// n = 4, 4 = 2 x 2, sum = (1+2+4) = 7 {1,2,4}
fn get_divisors_sum(num:usize) -> usize{
let mut n = num;
let sqrt_n = (n as f64).sqrt() as usize;
let primes = find_primes(sqrt_n);
let mut ans = 1;
for i in primes {
let mut sum = 1;
let mut ii = i;
while n % i == 0 {
sum += ii;
ii *= i;
n /= i;
}
ans *= sum;
if n == 1 {break;}
}
if n != 1 {ans *= 1+n; } //n=10의 경우, n=5에서 벗어남
return ans;
}
-끝-
'Programming > Rust로 Euler 문제 풀이' 카테고리의 다른 글
024. Lexicographic Permutations (사전식 조합에서 k번째 항목 구하기) (0) | 2024.09.02 |
---|---|
023. Non-Abundant Sums (두 과잉수의 합으로 표현할 수 없는 수 구하기) (2) | 2024.09.02 |
022. Names Scores (이름 문자열에 대해 소팅하고, 점수 구하기) (3) | 2024.09.02 |
021. Amicable Numbers (n보다 작은 모든 친화수 찾기) (3) | 2024.09.02 |
020. Factorial Digit Sum (큰 수에 대한 팩토리얼 구하기) (1) | 2024.09.02 |