정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


 

 입력으로 들어오는 \(n\)의 범위가 \(1 \leq n \leq 10,000\)이라 에라토스테네스의 체가 필요없을 수도 있다고 생각할 수 있다.

 하지만 테스트 케이스의 수가 존재하고 문제에서 언급은 안되어 있지만 대략 \(T\)의 범위가 \(1 \leq T \leq 10,000\)라고 가정했을 때, 우리는 최악의 상황에서 \((10,000 * 10,000)\)번의 나누기 연산이 필요하다.

 TLE의 위험이 도사리고 있으니 안전하게 에라토스테네스의 체를 사용하자.  그냥 소수 문제에선 에라토스테네스의 체가 거의 정답이다.

 

 에라토스테네스의 체를 사용하여 \(1 ~ 10,000\) 사이에 있는 모든 소수를 구했다고 가정하자.

 어떤 입력 \(n\)이 들어왔을 때, 우리는 \(x + y = n, (x \leq y)\)을 만족하는 두 소수 \(x, y\)가 있다고 가정하면 \(x\)는 \(2 \leq x \leq \frac{n}{2}\)을 만족한다.

 결국, \(2\)부터 \(\frac{n}{2}\)까지 for문을 돌면서 \(x\)와 \(n - x\)가 둘 다 소수인 경우를 구해주면 된다.

 \(2\)부터 for문을 돌 때 두 수의 차가 가장 적은 것을 찾아야하므로, 찾을 때마다 답을 update해주면 마지막으로 나온 답은 자동적으로 두 수의 차가 가장 적은 경우가 된다.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 1e4 + 1;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[MAX_N];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));

    isPrime[1] = false;
    for (int i = 2; i < MAX_N; ++i) {
        if (isPrime[i]) {
            for (ll j = i * 1LL * i; j < (ll) MAX_N; j += i * 1LL) {
                isPrime[j] = false;
            }
        }
    }

    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        int left = 2, right;

        for (int x = 2; x <= (n / 2); x++) {
            if (isPrime[x] && isPrime[n - x]) {
                left = x;
                right = n - x;
            }
        }
        cout << left << ' ' << right << '\n';
    }

    return 0;
}

 

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


 \(n\)의 범위가 \(1 \leq n \leq 123,456\)이므로, 에라토스테네스의 체로 해결하는게 가장 빠른 시간안에 해결할 수 있을 것으로 보인다. 에라토스테네스의 체를 사용해보자.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 123457 * 2;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[MAX_N];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));

    isPrime[1] = false;
    for (int i = 2; i < MAX_N; ++i) {
        if (isPrime[i]) {
            for (ll j = i * 1LL * i; j < (ll) MAX_N; j += i * 1LL) {
                isPrime[j] = false;
            }
        }
    }

    while (true) {
        int n, cnt = 0;
        cin >> n;
        if (n == 0) break;
		
        for (int i = n + 1; i <= 2 * n; i++) {
            if (isPrime[i]) {
                cnt++;
            }
        }
        cout << cnt << '\n';
    }

    return 0;
}

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


 

 입력의 범위가 \( 1 \leq M \leq N \leq 1,000,000\) 이므로 숫자 하나하나를 naive한 방법이나 naive하게 \(\root N\)까지 보는 방법으로 소수를 판별해서 시간 안에 통과하기는 쉽지 않아 보인다. 따라서 에라토스테네스의 체를 쓰자.

 

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 10e+6 + 1;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[MAX_N];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));

    int M, N;
    cin >> M >> N;

    isPrime[1] = false;
    for (int i = 2; i <= N; ++i) {
        if (isPrime[i]) {
            for (ll j = i * 1LL * i; j <= (ll) N; j += i * 1LL) {
                isPrime[j] = false;
            }
        }
    }

    for (int cur = M; cur <= N; ++cur) {
        if (isPrime[cur]) {
            cout << cur << '\n';
        }
    }

    return 0;
}

한가지 재미있는 점은 isPrime 배열을 매번 1,000,000개에 대해 초기화 해주면 아래와 같이 64ms이 걸리고,

위 코드와 같이 N의 범위에 맞춰서 초기화 해주면 16ms가 걸린다.

 

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. 기본

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int num;
    int divisor = 2;

    cin >> num;
    while (num != 1) {
        // 나누어 떨어지지 않으면
        if (num % divisor != 0) {
            divisor++;
        }
        else {
            cout << divisor << '\n';
            num /= divisor;
        }
    }
    return 0;
}

 

2. 개선 ver.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int num;
    int divisor = 2;

    cin >> num;
    while (num != 1) {
        if (num % divisor != 0) {
            // divisor가 짝수면 + 1, 홀수면 + 2
            divisor = divisor % 2 == 0 ? divisor + 1 : divisor + 2;
        }
        else {
            cout << divisor << '\n';
            num /= divisor;
        }
    }
    return 0;
}

 

3. \(\sqrt n\)까지 보기

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll num;
    ll divisor = 2;

    cin >> num;

    for (ll div = 2LL; div * div <= num; div = div % 2 == 0 ? div + 1LL : div + 2LL) {
        while (num % div == 0) {
            cout << div << '\n';
            num /= div;
        }
    }

    if (num > 1) {
        cout << num << '\n';
    }
    return 0;
}

 

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. naive

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int M, N;
    cin >> M >> N;

    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (cur == 1) continue;

        bool isPrime = true;
        for (int div = 2; div < cur; ++div) {
            if (cur % div == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
	
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;
	

    return 0;
}

 

2. naive(개선)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int M, N;
    cin >> M >> N;

    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (cur == 1) continue;

        bool isPrime = true;
        for (int div = 2; div <= (int)sqrt(cur); ++div) {
            if (cur % div == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
	
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;
	

    return 0;
}

 

3. 에라토스테네스의 체

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 10e+4 + 1;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[10001] = { 0, };
int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	
    memset(isPrime, 1, sizeof(isPrime));
    isPrime[1] = false;
    for (int i = 2; i < 10001; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j < 10001; j += i) {
                isPrime[j] = false;
            }
        }
    }
    
    int M, N;
    cin >> M >> N;
    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (isPrime[cur]) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;

    return 0;
}

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. naive

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (num == 1) continue;

        bool isPrime = true;
        for (int i = 2; i < num; ++i) { // num - 1까지 다 돌기
            if (num % i == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) ++ans;
    }
    cout << ans;

    return 0;
}

 

2. naive(개선)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (num == 1) continue;

        bool isPrime = true;
        for (int i = 2; i <= (int) sqrt(num); ++i) {
            if (num % i == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) ++ans;
    }
    cout << ans;

    return 0;
}

 

3. 에라토스테네스의 체

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
  
ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }
bool isPrime[1001] = { 0, };
int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));
    isPrime[1] = false;
    for (int i = 2; i < 1001; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j < 1001; j += i) {
                isPrime[j] = false;
            }
        }
    }
    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (isPrime[num]) ans++;
    }
    cout << ans;

    return 0;
}

 

소수 문제에서 사용가능한 기법은 세가지 정도이다.

 

첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다.

// naive하게 소수판별
bool isPrime = true;
int num = 101;
for(int i = 2; i < num; ++i){
    if(num % i == 0){
    	isPrime = false;
        break;
    }
}
if(isPrime) printf("%d is prime.\n", num);
else printf("%d is not prime.\n", num);

 첫번째 방법을 좀 더 개선해보자. 수학적으로 어떤 한 수가 소수인지 판별하기 위해 그 수의 \(\sqrt n \)까지만 나누어주면 된다. 

// naive하게 소수판별(개선)
bool isPrime = true;
int num = 101;
for(int i = 2; i <= (int)sqrt(num); ++i){
    if(num % i == 0){
    	isPrime = false;
        break;
    }
}
if(isPrime) printf("%d is prime.\n", num);
else printf("%d is not prime.\n", num);

첫번째 방법도 소수를 판별하는 것에 나쁘지 않아 보인다.

그러나 어떤 구간 안에 있는 수 중에서 소수를 찾는 문제가 나오고, 그 구간 안에 너무 많은 숫자가 있거나 구간 안에 숫자들이 너무 크다면 이 방법은 가망이 없다.

왜냐하면 구간 안에 숫자 하나하나를 판별하는 건 시간 낭비가 심하기 때문이다.

그렇다면 한번에 연산으로 소수 판별이 가능한 방법이 있을까 ?

 

 

 

답은 "있다"이다. 바로 소수를 찾는 두번째 방법으로 소개할 '에라토스테네스의 체'라는 방법이 있다.

방법은 간단하다. 2부터 모든 소수들의 배수들을 배제하면 된다. 다시 말하면, 어떤 숫자 \(x\)가 배제되어있지 않다면 \(x\)는 소수일 것이다. 이게 어떻게 가능한지 보자.

 

여기 2부터 21까지 숫자가 있다. 

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

2부터 하나씩 숫자를 확인해보자. 2는 아직 배제된 적이 없다. 따라서 소수이기 때문에 2의 배수들을 모조리 배제해버리자. (배수들은 취소선이 긁히고, 소수는 파란색이 된다.)

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

2에 대한 처리를 끝냈다. 이제 다음 숫자 3을 보자. 3 역시 아직 배제되지 않았다. 3을 소수로 취급하고 3의 배수들은 모조리 배제하자.

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

3에 대한 처리를 끝냈다. 이어서 4를 봤더니 4는 이미 배제되어있다.

배제되었다는 뜻은 이 숫자가 어떤 숫자의 배수라는 것이기 때문에 소수일 수가 없다. 따라서 무시하고 지나간다.

다음으로 5는 배제되지 않았기 때문에, 소수이다. 5의 배수들을 처리해주자.

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

위 표의 상황이 5까지 처리를 끝냈을 때의 상황이다. 계속 같은 처리를 진행해준다면 결과는

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 이렇게 된다. 이 방법을 코딩만 해주면 우리는 에라토스테네스의 체를 구현할 수 있다.

// 에라토스테네스의 체
// 2부터 10000 사이에 있는 소수 확인
bool isPrime[10001]; // true면 소수 false면 소수가 아니다.

// 처음엔 모두 소수로 가정하기 위해
// 2~10000을 모두 true로 초기화, 1번 or 2번 중 취향에 맞게 사용
// 1번 memset 사용 (memset은 boolean 배열만 1로 초기화 가능)
memset(isPrime, 1, sizeof(isPrime));
// 2번 for문
for(int i = 2; i < 10001; ++i) isPrime[i] = true;

// standard
for(int current = 2; current < 10001; ++current){
    // 이 숫자가 소수라면.
    if(isPrime[current]){
    	// next는 current의 배수이기 때문에 loop를 돌 때마다 +1 아니라 +current를 해주어야한다.
    	for(int next = current * 2; next < 10001; next += current){
            isPrime[next] = false; // 방금 찾은 소수의 배수들은 모두 소수가 아니다.
        }
    }
}

// Faster
for(int current = 2; current < 10001; ++current){
    if(isPrime[current]){
    	// next * 2, next * 3 , ... next * (current - 1)은 이미 이전에 처리되었기 때문에
        // 좀 더 빠른 속도를 원한다면 current의 제곱부터 true로 바꿔주어도 된다.
    	for(int next = current * current; next < 10001; next += current){
    	    isPrime[next] = false; 
        }
    }
}

isPrime의 size만 조절해주면 코더가 원하는 범위 안에 있는 소수들을 판별할 수 있다.

한가지 주의 사항은 4만 이상의 범위에서 faster 버전을 사용할 때 current * current가 int 범위를 초과하는 overflow가 날 수 있으니, 그 때는 long long 타입을 사용하자.

 

알고리즘이 나온 기념으로 시간복잡도를 얘기해보면 2~x까지 범위 안에 소수를 찾는데 걸리는 시간은 isPrime[]를 채우는 시간과 같다.

그럼 이중 for문을 사용해서 시간복잡도가 \(O(n^2)\) 아닌가요? 라고 물을 수 있는데 수학적으로 증명했을 땐 \(O(n log log n)\)이고, 평상시에 나는 그냥 isPrime[]을 한번 도는 시간 \(O(n)\)으로 생각하고 문제를 푼다.

 

 

마지막으로 소인수 분해 방법에 대해 알아보자.

소인수 분해 그거 대충 그냥 에라토스테네스의 체를 이용해서 찾은 소수들로 쭉 나누면 안되나요 ? 라고 물으면 가능하다라고 답할 수 있다. 다시 물어보자.

그럼 언제든 에라토스테네스의 체를 이용하면 소인수분해 문제를 풀 수 있네요? 라고 물으면 불가능하다라는 답변이 나온다.

 

 왜일까?

 여기 987,654,321를 소인수 분해하는 문제가 있다.

 에라토스테네스의 체를 배웠으니 콧노래를 흥얼거리며 나는 bool isPrime[987654322]; 을 선언한다.

 선언해보고 뭔가 이상함을 느낀다. 곧이어 이게 메모리를 버틸 수 있나? 라는 생각이 든다. 당연히 못버틴다. 

 혹시 모르니 백준에 제출해봤지만, 풀릴 리가 없다. 

 

 어떻게 할까 ?

 방법은 이렇다. 꼭 소수로 나눌 필요가 없다.

 

 어떤 수 x가 있으면 2로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 3으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 4으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 5으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 6으로 나누어지지 않을 때까지 나누자.

 ...

 나누어지는 수가 1이면 break.

 

 아니 소인수 분해인데 소수로 안나누면 어떡합니까 ? ㅋㅋㅋㅋ 어이없네 

 어이없는 건 나다. 과정을 다시 생각해보자. 

 

 어떤 수 x가 있으면 2로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 3으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 4으로 나누어지지 않을 때까지 나누자.

 ( 2로 이미 나누어지지 않을 때까지 나누었는데, 4로 나눠질까 ? 그럴 리가 없다. )

 나누어지지 않으면, 5으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 6으로 나누어지지 않을 때까지 나누자.

( 2랑 3으로 이미 나누어지지 않을 때까지 나누었는데, 6으로 나눠질까 ? 그럴 리가 없다. )

 ... 

 나누어지는 수가 1이면  break.

 

앞에서 먼저 등장한 소수들로 이미 다 나눴기 때문에, 뒤에서 나오는 합성수들로 백날 나눠봤자 아무 일도 안일어난다. 이게 핵심이다.

 

아니 그러면 소인수 분해할 수(n)가 소수면 n번 돌아야하는거잖아요 ㅡㅡ 

어쩔 수 없다. 메모리가 못 버틴다. 이럴 땐 시간을 버리자.

// 987654321 소인수 분해
int num = 987654321;
int divisor = 2;
while (num != 1) {
	// 나누어 떨어지지 않으면
    if (num % divisor != 0) {
    	divisor++;
    }
    else{
    	cout << divisor << '\n';
    	num /= divisor;
    }
}

코드를 적고 나니 개선점이 하나 떠올랐다.

2로 소인수 분해를 하고 나면 divisor가 짝수가 될 필요가 있을까 ? 없다. 2가 유일한 짝수인 소수이다.

그렇다면 3부터는 divisor에 1을 더하는 게 아니라 2를 더하여 홀수일 때만 확인해줘도 될까 ? 문제 없다. 개선하자.

// 987654321 소인수 분해
int num = 987654321;
int divisor = 2;
while (num != 1) {
    if (num % divisor != 0) {
    	// divisor가 짝수면 +1, 홀수면 +2
    	divisor = divisor % 2 == 0 ? divisor + 1 : divisor + 2;
    }
    else{
    	cout << divisor << '\n';
    	num /= divisor;
    }
}

개선이 끝났다. 라고 생각할 때쯤 한가지 생각이 더 든다.

위에서 소수 판별할 때 \(2\) ~ \(\sqrt n\)까지만 봐주면 된다는 점을 이용할 수 있을까 ? 있다.

 

어떤 정수 \(n\)가 있다고 가정해보자. 

이 수를 \(2\)~\(\sqrt n\) 사이에 있는 정수로 나누어봤더니 그 어느 수와도 나누어 떨어지지 않았다.

이 때, \(\sqrt n\) 보다 큰 정수로 나누었을 때  \(n\)이 나누어 떨어질 확률은? 0이다.

 

왜? Why? 생각해보자.

만약 \(2\)~\(\sqrt n\) 사이에 어떤 정수 \(x\)로 \(n\)나누었더니 몫이 \(q\)이고 나머지가 0이다.

이 때 q의 범위는 ? 당연히 \(\sqrt n\) 이상이다. \(2\)~\(\sqrt n\)사이에 정수가 \(n\)을 나누어 떨어지게 할 수 있으면 그 몫은 자명하게도 \(\sqrt n\)보다 크다.

 

위 사실을 토대로 \(2\)~\(\sqrt n\) 사이에 어떤 정수로도 \(n\)을 나누어 떨어지게 할 수 없는데, \(\sqrt n\) ~ \(n\)사이에 있는 정수 중 \(n\)을 나누어 떨어지게 할 수 있는 수가 존재할까 ? 존재하지 않는다.

 

 따라서 소인수 분해 코드를 다시 한번 개선하자.

// 987654321 소인수 분해
long long num = 987654321;

for(long long div = 2LL; div * div <= num; div = div % 2 == 0 ? div + 1LL : div + 2LL){
    while(num % div == 0){
    	cout << div << '\n';
        num /= div;
    }
}

// for문이 끝나고 나온 num은 1이거나 소수이다.
if(num > 1){
    cout << num << '\n';
}

 

지금까지 9단계 1~6번을 풀기 위한 초석이 될 방법들을 소개했다.

9단계 소수 문제들 뿐만 아니라 대부분의 소수 문제는 위 틀을 벗어나지 않기 때문에(최소 공배수, 최대 공약수 제외) 잘 습득해서 소수 문제를 풀 때마다 이 글이 생각나면 매우 기분이 좋을 것 같다.

 

오타나 이해가 안되는 점은 댓글로 부탁드린다.

+ Recent posts