본문 바로가기
PS/BOJ

[자바] 백준 2981 - 검문 (java)

by Nahwasa 2024. 5. 22.

문제 : boj2981

 

 

필요 알고리즘

  • 유클리드 호제법, 정수론
    • gcd를 구해서 풀 수 있는 문제이다.

※ 제 코드에서 왜 main 함수에 로직을 직접 작성하지 않았는지, 왜 Scanner를 쓰지 않고 BufferedReader를 사용했는지 등에 대해서는 '자바로 백준 풀 때의 팁 및 주의점' 글을 참고해주세요. 백준을 자바로 풀어보려고 시작하시는 분이나, 백준에서 자바로 풀 때의 팁을 원하시는 분들도 보시는걸 추천드립니다.

 

 

풀이

  N개의 숫자를 M으로 나누었을 때, 나머지가 모두 같게 되는 2이상의 M을 모두 찾는 문제이다. 이 경우, 우선 가장 큰 M만 찾으면 된다. 왜냐면 나머지 M은 가장 큰 M의 약수들을 찾아주면 되기 때문이다. 그럼 문제는 N개의 숫자를 M으로 나누었을 때 나머지가 모두 같게 되는 가장 큰 M을 찾는 문제로 바뀐다. 따라서 백준 1684와 동일한 문제인데 +@인 문제라고 보면 된다. 이게 더 쉬우므로 잘 모르겠다면 1684부터 풀고 다시 풀어보자.

 

  그럼 이제 나머지가 같게 되도록 하는 가장 큰 M을 찾아야 한다.

입력으로 받은 N1과 N2가 있다고 하자. M으로 나눈 나머지가 같다는 점을 활용해 찾아보자.

N1의 나머지 R1 = N1 - Q1 * M

N2의 나머지 R2 = N2 - Q2 * M

이라고 할 수 있다.

 

  여기서 R1== R2 여야 하므로,

N1 - Q1 * M = N2 - Q2 * M 이고 따라서

N1-N2 = (Q1-Q2)M 이다.

 

  즉, 두 수 N1과 N2를 M으로 나눈 나머지인 R1과 R2가 동일한 경우 두 수의 차 또한 M으로 나눌 수 있다. Q1-Q2가 어떤 값이 나올진 모르겠지만, 아무튼 Q1-Q2도 정수이고 M도 정수이니 그냥 어떻게든 M을 가장 크도록만 만들면 된다. 그 말인즉, N1과 N2만 있다고 하면 그냥 Q1-Q2가 1이라고 치고, M은 N1-N2 자체가 되면 된다(즉, N=2 였다면 M은 두 수의 차이를 그대로 출력하면 된다는 얘기이다). 한마디로 Q1-Q2가 어떻든 상관없다.

 

  그럼 N이 3 이상일땐 어떻게 하면 될까? 식의 수를 더 늘리면 된다.

N1-N2 = (아무튼 대충 정수)M 일꺼고, N1-N3 = (아무튼 대충 정수) M 일꺼고, N2-N3 = (아무튼 대충 정수) M 일꺼다. 즉, N1-N2와 N1-N3, N2-N3 들의 최대공약수를 그냥 M이라고 치면 된다.

 

  근데 모든쌍을 보려면 O(N^2)이 필요할꺼다. 굳이 그럴필요까진 없고, 그냥 모든 수에 대한 차이만 반영되면 되므로, 값 하나 정해두고 그걸로 나머지 N-1개 전부 빼봐도 된다. 즉, N1만 하나 정해두고 무조건 N1으로만 빼도 상관없다. 근데 음수의 나머지 처리는 상당히 곤란해지므로, 가장 작은 수 하나 구해두고 그걸로 나머지 수 전부 빼면서 그 차이들의 최대공약수만 구하면 된다. (아니면 그냥 정렬해두고 arr[i+1] - arr[i] 처럼 처리해도 상관 없다.)

 

  아무튼 이렇게 M을 구했다면, 그 약수들을 구한 후 오름차순으로 출력해주면 되는 문제이다. 약수의 경우 sqrt(M) 까지만 확인해보면 되고, 입력값의 최대치가 10억이므로 sqrt(10억)은 얼마 안되서 그냥 무지성으로 찾아줘도 된다. sqrt(M) 까지만 보면 되는 이유는 '에라토스테네스의 체 혹은 소수판정 시 제곱근 까지만 확인하면 되는 이유' 글에 있다. 좀 더 효율적으로 찾으려면 '에라토스테네스의 체를 활용한 소인수분해' 글을 참고해보자.

 

private void solution() throws Exception {
    int n = Integer.parseInt(br.readLine());
    int min = Integer.MAX_VALUE;	// 입력받으면서 최소값 찾는다.
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) min = min(min, arr[i] = Integer.parseInt(br.readLine()));
    int s = 0;
    while (arr[s] == min) s++;
    int gcd = arr[s]-min;
    for (int i = ++s; i < n; i++) gcd = gcd(gcd, arr[i]-min);
	// 최소값으로 나머지 수들 전부 빼면서 그 차이들의 gcd를 구한다.

    TreeSet<Integer> res = new TreeSet<>();
    res.add(gcd);
    int limit = (int) ceil(sqrt(gcd));
    for (int i = 2; i <= limit; i++) {	// sqrt(gcd) 까지 확인하며 gcd의 약수를 찾는다.
        if (gcd%i!=0) continue;

        res.add(i);
        res.add(gcd/i);
    }

    StringBuilder sb = new StringBuilder();
    for (Integer cur : res) sb.append(cur).append(' ');	// 정렬 후 출력한다. TreeSet으로 정렬했다.
    System.out.println(sb);
}

private int gcd(int a, int b) {	// 유클리드 호제법
    if (b==0) return a;

    int r = -1;
    while (r!=0) {
        r = a%b;
        a = b;
        b = r;
    }
    return a;
}

 

 

코드 : github

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.TreeSet;

import static java.lang.Math.*;

public class Main {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1 << 16);

    public static void main(String[] args) throws Exception {
        new Main().solution();
    }

    private void solution() throws Exception {
        int n = Integer.parseInt(br.readLine());
        int min = Integer.MAX_VALUE;
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) min = min(min, arr[i] = Integer.parseInt(br.readLine()));
        int s = 0;
        while (arr[s] == min) s++;
        int gcd = arr[s]-min;
        for (int i = ++s; i < n; i++) gcd = gcd(gcd, arr[i]-min);

        TreeSet<Integer> res = new TreeSet<>();
        res.add(gcd);
        int limit = (int) ceil(sqrt(gcd));
        for (int i = 2; i <= limit; i++) {
            if (gcd%i!=0) continue;

            res.add(i);
            res.add(gcd/i);
        }

        StringBuilder sb = new StringBuilder();
        for (Integer cur : res) sb.append(cur).append(' ');
        System.out.println(sb);
    }

    private int gcd(int a, int b) {
        if (b==0) return a;
        
        int r = -1;
        while (r!=0) {
            r = a%b;
            a = b;
            b = r;
        }
        return a;
    }
}