본문 바로가기

백준 문제풀이

BOJ 5615 아파트 임대[C++]

먼저 문제에서 주어진 식을 변형해야 한다.

 

 

아파트의 면적을 n이라 놓으면

2xy + x + y = n.

 

양변에 2를 곱하고 1을 더하면

4xy + 2x + 2y + 1 = 2n + 1.

 

좌변을 인수분해하면

(2x + 1)(2y + 1) = 2n + 1.

문제에서는 불가능한 아파트 면적을 찾는 것이다. x,y의 조건에서 2x+1, 2y+1모두 3이상의 홀수이고

이때 가능한 2n + 1 값은 3이상의 홀수 두개로 소인수분해된다.

 

즉 불가능한 2n + 1값은 3이상의 홀수 두개로 소인수분해 되지 않는 수인데, 

2x + 1 과 2y + 1은 짝수가 나올 수 없으므로 둘 중 하나가 3보다 작은 홀수, 즉 1이다.

따라서 2n +1 의 값이 1 x (1이 아닌 홀수) 로만 인수분해 할 수 있는 수 즉 소수가 될때,

이를 만족하는 양의 정수 n값은 존재하지 않게 되는 것이다. 

 

즉 문제를 요약하면 아파트 면적 n에 대해 2n + 1이 소수인 것의 개수를 세서 출력하면 되는 것이다.

 

따라서 소수 판별 알고리즘을 사용해야 하는데, 소수 판별 알고리즘 중 가장 유명한 것은 에라토스테네스의 체이다.

최적화된 에라토스테네스 체의 시간복잡도는 N * log (log N)으로 판별하려는 소수의 최대값인 2^32 - 1 을 대입하면 시간초과가 날 것이다. (100,000개의 쿼리에 대해 각각 소수판별을 해도 시간초과가 날 것이다.)

 

그래서 더욱 빠른 소수판별 알고리즘인 밀러 라빈 소수 판별법을 적용해야한다.

밀러 라빈 소수 판별법의 시간복잡도는 O(log^3 N)에 불과해 시간내에 소인수분해가 가능하다.

밀러-라빈 소수판별법에 관한 이론과 자세한 내용은 아래를 참고해서 공부했다.

 

https://goodbyefin.tistory.com/47 

https://rebro.kr/46

 

소수 판별법 - 에라토스테네스의 체, 밀러-라빈(Miller-Rabin) 소수판별법

어떠한 자연수 N이 소수인지를 판별하는 방법은 여러 가지 방법이 있다. 그중에서 너무 난도 높은 것은 제외하고 충분히 PS에서 쓸만한 방법을 알아보자. 우선, N이 소수인지를 판별하는 경우와 N

rebro.kr

 

밀러 - 라빈 소수 판별법 (Miller - Rabin primality test)

컴퓨터 공학에 입문한 뒤 대부분 처음으로 배우는 소수 판별 알고리즘은 에라토스테네스의 체 알고리즘이다. 에라토스테네스의 체는 소수를 판별하고 싶은 범위 안의 소수인 수의 모든 배수를

goodbyefin.tistory.com

 

아래는 밀러-라빈 소수판별법을 사용해 AC를 받은 코드이다.

#include <bits/stdc++.h>
#define ulld unsigned long long int

using namespace std;



ulld fastPow(ulld a, ulld b, ulld mod) {

    ulld ret = 1;
    a %= mod;

    while (b) {
        if (b % 2) ret = (ret * a) % mod;
        b /= 2;
        a = (a * a) % mod;
    }

    return ret;
}



bool isPrime(ulld num, ulld test) {
    if (test % num == 0) return true;

    ulld s = num - 1;

    while (true) {
        ulld temp = fastPow(test, s, num);
        if (temp == num - 1) return true;
        if (s % 2) return (temp == 1 || temp == num - 1);
        s /= 2;
    }

}


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

    int n, cnt = 0;
    ulld buf;
    vector<ulld> tests = {2,7,61};

    cin >> n;

    while (n--) {
        cin >> buf;
        buf = 2 * buf + 1;

        
        int i = 0;
        while (true) {
            
            if (i == tests.size()) {
                cnt++;
                break;
            }
            if (! isPrime(buf, tests[i]))
                break;
            i++;
        }
    }

    cout << cnt;



    return 0;
}

 

long long int가 아닌 unsigned long long int를 사용했다.

long long int를 쓰면 5%정도에서 틀렸습니다가 나온다.

 

overflow나는 부분은 실험해보진 않았지만 fastPow함수 내부인 것 같다.

mod로 들어올 수 있는 수의 범위는 2^32 - 1 이하이다.(2n + 1)

그런데 아래에서 a는 mod로 나눈 나머지 값이므로 mod 미만이다.

그런 수 a를 제곱하는데, 그 최댓값은 (2^32 -2)^2  는 long long int의 범위인 2^63 - 1을 넘어설 수 있다.

따라서 계산 중 overflow가 발생할 수 있어 unsinged long long int를 사용해야한다.

 

'백준 문제풀이' 카테고리의 다른 글

BOJ 3665 최종 순위 [C++]  (0) 2024.03.19
BOJ 16566 카드 게임 [C++]  (1) 2024.03.16
BOJ 9328 열쇠 [C++]  (0) 2024.02.26
BOJ 17387 선분 교차 2 [C++]  (0) 2024.02.16
BOJ 1918 후위 표기식 [C++]  (1) 2024.02.14