본문 바로가기
PS/BOJ

[자바] 백준 27988 - 리본 (Hard) (java)

by Nahwasa 2023. 6. 16.

목차

    문제 : boj27988

     

     

    필요 알고리즘

    • 그리디 알고리즘, 정렬
      • 문제를 좀 더 간단히 생각해보면 동일한 규칙을 적용시켜서 풀 수 있다.

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

     

     

    풀이

      생각 과정은 다음과 같다.

     

    1. 일단 입력으로 들어온 데이터부터 좀 더 이해하기 편하게 바꿔보자.

      X위치에 달린 길이 L의 구부릴 수 있는 리본이라 함은 결국 [X-L, X+L] 에서 다른 색상을 만나기만 하면 되는 리본이라 볼 수 있다. [X-L, X+L] 은 이후 [s, e] 라고 생각하겠다.

     

     

    2. 우선 답이 없는 'NO'를 출력해야 하는 경우를 생각해보자.

      이 경우 당연하게도 [s, e]의 모든 리본이 만나는 경우는 동일한 색상인 경우밖에 없다.

    이하의 예시를 생각해보자.

    5
    1 2 5 10 11
    1 1 1 2 2
    R R B Y Y

     

      차례대로 [s, e]는 [0, 2], [1, 3], [4, 6], [8, 12], [9, 13] 이 될 것이고 그려보면 이하와 같다.

    이 경우 NO가 될 것이다.

     

     

    3. 그렇다면 반대로 생각해서, 서로 겹쳐있는 리본들의 집합에서 색상이 다른게 하나라도 존재한다면 그게 답이다.

      이하는 2번째 리본의 L값만 3으로 바뀐 경우이다.

    5
    1 2 5 10 11
    1 3 1 2 2
    R R B Y Y

     

      이 경우  [0, 2], [-1, 5], [4, 6], [8, 12], [9, 13] 가 되고, 마찬가지로 그려보면 이하와 같다.

      이 경우 서로 겹쳐있는 리본들의 집합중에 서로 색상이 다른게 존재하게되고, 답이 존재하게 된다.

     

     

    4. 그럼 어떻게 구현할까?

      우선 입력 그대로 [X-L, X+L]을 가지고 처리해도 가능하겠지만, 생각한바를 코드로 잘 옮기기 위해 우선 생각해둔 [s, e] 형태로 변경하는게 편할 것 같다.

    class Ribbon implements Comparable<Ribbon> {
        int s, e, num;
        char color;
    
        public Ribbon(int num) {
            this.num = num;
        }
    
        public void setup(int len) {
            int tmp = s;
            s = tmp-len;
            e = tmp+len;
        }
    
    	...
    }

     

      그리고 s와 e값을 기준으로 정렬해준다. 그 이유는 서로 겹쳐있는 리본들의 집합을 쉽게 찾기 위해서이다.

    class Ribbon implements Comparable<Ribbon> {
        int s, e, num;
        char color;
    
        public Ribbon(int num) {
            this.num = num;
        }
    
        public void setup(int len) {
            int tmp = s;
            s = tmp-len;
            e = tmp+len;
        }
    
        @Override
        public int compareTo(final Ribbon o) {
            if (this.s == o.s)
                return this.e-o.e;
            return this.s-o.s;
        }
    }

     

      이제 그리디 규칙을 정해보면 다음과 같다.

    A. s와 e를 기준으로 정렬해둔 데이터를 순차적으로 보면서 이하를 체크한다.

    B. 현재까지 기록해두었던 가장 마지막 e값보다 현재 보고 있는 리본의 s값이 더 크다면 -> 겹쳐진 리본의 집합이 끝난거다. 새로운 집합이 시작된다! 새로운 집합이 시작될 때 색상을 기록해둔다.

    C. 이후 다른 색상이 동일 집합에 나온다면 답이 나온거다.

    D. 동일한 집합이긴한데, e값이 증가했다면 해당 리본으로 번호를 바꿔둬야 한다. 어차피 '답이 여러 개 존재한다면 아무거나 출력해도 상관없다.' 라는 조건이 있으므로, e값이 증가할 때 리본의 번호를 바꿔둔다면 항상 답이 될 수 있다.

    char curColor = 0;
    int lastNum = -1;
    int lastE = Integer.MIN_VALUE;
    for (Ribbon cur : arr) { // [A]
        if (cur.s > lastE) { // [B]
            curColor = cur.color;
            lastNum = cur.num;
            lastE = cur.e;
            continue;
        }
    
        if (cur.color != curColor) { // [C]
            System.out.printf("YES\n%d %d\n", lastNum, cur.num);
            return;
        }
    
        if (cur.e > lastE) { // [D]
            lastE = cur.e;
            lastNum = cur.num;
        }
    }

     

     

    코드 : github

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.Arrays;
    import java.util.StringTokenizer;
    
    public class Main {
    
        static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    
        public static void main(String[] args) throws Exception {
           new Main().solution();
        }
    
        private void solution() throws Exception {
            int n = Integer.parseInt(br.readLine());
            Ribbon[] arr = new Ribbon[n];
            for (int i = 0; i < n; i++) arr[i] = new Ribbon(i+1);
    
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int i = 0; i < n; i++) arr[i].s = Integer.parseInt(st.nextToken());
            st = new StringTokenizer(br.readLine());
            for (int i = 0; i < n; i++) arr[i].setup(Integer.parseInt(st.nextToken()));
            st = new StringTokenizer(br.readLine());
            for (int i = 0; i < n; i++) arr[i].color = st.nextToken().charAt(0);
    
            Arrays.sort(arr);
    
            char curColor = 0;
            int lastNum = -1;
            int lastE = Integer.MIN_VALUE;
            for (Ribbon cur : arr) {
                if (cur.s > lastE) {
                    curColor = cur.color;
                    lastNum = cur.num;
                    lastE = cur.e;
                    continue;
                }
    
                if (cur.color != curColor) {
                    System.out.printf("YES\n%d %d\n", lastNum, cur.num);
                    return;
                }
    
                if (cur.e > lastE) {
                    lastE = cur.e;
                    lastNum = cur.num;
                }
            }
    
            System.out.println("NO");
        }
    }
    
    class Ribbon implements Comparable<Ribbon> {
        int s, e, num;
        char color;
    
        public Ribbon(int num) {
            this.num = num;
        }
    
        public void setup(int len) {
            int tmp = s;
            s = tmp-len;
            e = tmp+len;
        }
    
        @Override
        public int compareTo(final Ribbon o) {
            if (this.s == o.s)
                return this.e-o.e;
            return this.s-o.s;
        }
    }

     

    댓글