본문 바로가기

생각정리/코딩테스트

[JAVA 알고리즘]BAEKJOON 3020번 개똥벌레

개똥벌레


문제 설명

개똥벌레 한 마리가 장애물(석순과 종유석)로 가득찬 동굴에 들어갔다. 동굴의 길이는 N미터이고, 높이는 H미터이다. (N은 짝수) 첫 번째 장애물은 항상 석순이고, 그 다음에는 종유석과 석순이 번갈아가면서 등장한다.

아래 그림은 길이가 14미터이고 높이가 5미터인 동굴이다. (예제 그림)

이 개똥벌레는 장애물을 피하지 않는다. 자신이 지나갈 구간을 정한 다음 일직선으로 지나가면서 만나는 모든 장애물을 파괴한다.

위의 그림에서 4번째 구간으로 개똥벌레가 날아간다면 파괴해야하는 장애물의 수는 총 여덟개이다. (4번째 구간은 길이가 3인 석순과 길이가 4인 석순의 중간지점을 말한다)

하지만, 첫 번째 구간이나 다섯 번째 구간으로 날아간다면 개똥벌레는 장애물 일곱개만 파괴하면 된다.

동굴의 크기와 높이, 모든 장애물의 크기가 주어진다. 이때, 개똥벌레가 파괴해야하는 장애물의 최솟값과 그러한 구간이 총 몇 개 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 H가 주어진다. N은 항상 짝수이다. (2 ≤ N ≤ 200,000, 2 ≤ H ≤ 500,000)

다음 N개 줄에는 장애물의 크기가 순서대로 주어진다. 장애물의 크기는 H보다 작은 양수이다.

출력

첫째 줄에 개똥벌레가 파괴해야 하는 장애물의 최솟값과 그러한 구간의 수를 공백으로 구분하여 출력한다.

제한사항

  • 없음

입출력 예

input return
6 7
1
5
3
3
5
1
2 3
14 5
1
3
4
2
2
4
3
4
3
3
3
2
3
3
7 2

문제 분석

N은 항상 짝수로 나오기 때문에 석순과 종유석에 묶어서 사용할 수 있다.
석순과 종유석의 높이 조건이 다르기 때문에 각각 배열로 사용해서 이분탐색을 사용해야 한다.

높이 마다 장애물의 개수를 확인 해야하기 때문에 높이를 순차적으로 반복을 하면서 해당 높이에 해당하는 석순과 종유석을 찾아야 한다.

석순은 밑에서 부터 높이를 비교해서 개수를 헤아려야 하고 종유석은 위에서 부터 높이를 비교해서 개수를 헤아려야 한다.

  • 앞부분만 봤을 때, 첫 번째 석순은 1, 종유석은 3에서 1의 높이의 장애물을 확인 중이라면 석순은 1 보다 크면 해당 위치에 장애물이 있지만, 종유석은 3으로 숫자는 크지만 1의 높이에 장애물이 존재하지 않는다.
  • 종유석에서 보면 1의 높이는 5의 높이와 동일하므로 총 높이에서 해당 높이를 빼주고 1을 더해주면 종유석의 높이로 비교할 수 있다.

장애물의 최솟값과 같으면 횟수를 증가하고 쵯솟값이 더 작으면 갱신해 준다.

손으로 풀기

  1. 석순과 종유석을 한쌍으로 생각하고 N의 크기의 반만큼 반복을 하면서 각각의 배열에 입력값을 넣어준다.
  2. 이분 탐색을 하기 위해 각각의 배열을 오름차순으로 정렬해 준다.
  3. 장애물의 최솟값을 총 장애물 개수로 하고 1부터 주어진 높이까지 반복을 하면서 장애물의 개수를 헤아린다.
  4. 석순과 종유석 배열 안에서 이분 탐색을 하기 위해 0 ~ 배열의 크기인 N / 2 사이에서 반복을 한다.
  5. 석순의 수와 종유석의 수를 더해 장애물의 개수가 최솟값이면 그 수를 헤아리고 작으면 최솟값을 변경해 새롭게 헤아린다.

나의 문제풀이

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());

        // N은 항상 짝수 이다.
        int N = Integer.parseInt(stringTokenizer.nextToken());
        int H = Integer.parseInt(stringTokenizer.nextToken());

        // 석순과 종유석을 한쌍으로 나오기 때문에 반복을 줄일 수 있다.
        int[] bot = new int[N / 2];
        int[] top = new int[N / 2];
        for (int i = 0; i < N / 2; i++) {
            bot[i] = Integer.parseInt(bufferedReader.readLine());
            top[i] = Integer.parseInt(bufferedReader.readLine());
        }
        bufferedReader.close();
        Arrays.sort(bot);
        Arrays.sort(top);

        int min = N;
        int cnt = 0;
        for (int i = 1; i <= H; i++) {
            int left = 0;
            int right = N / 2;
            int wall = 0;
            // 해당 높이의 석순 헤아리기
            while (left < right) {
                int mid = left + (right - left) / 2;

                // 해당 높이에 지나는 석순의 수 출력
                if (bot[mid] >= i) {
                    right = mid;
                } else {
                    left = mid + 1;
                }
            }
            wall += bot.length - right;

            left = 0;
            right = N / 2;
            // 해당 높이의 종유석 헤아리기
            while (left < right) {
                int mid = left + (right - left) / 2;

                // 종유석은 위에서 시작하기 때문에 높이를 줄여가며 비교한다.
                if (top[mid] >= H - i + 1) {
                    right = mid;
                } else {
                    left = mid + 1;
                }
            }
            wall += top.length - right;

            // 장애물의 최솟값과 같으면 횟수를 증가하고 쵯솟값이 더 작으면 갱신해준다.
            if (min == wall) {
                cnt++;
            } else if (min > wall) {
                min = wall;
                cnt = 1;
            }
        }

        System.out.println(min + " " + cnt);
    }
}

이분 탐색은 어떤 수치를 기준으로 탐색을 할지와 어떤 조건으로 값을 변경할지가 가장 중요한 것 같다. 매번 풀 때마다 기준을 잘 못 잡아 오류를 범하는 부분이 많았다. 
거기다가 이번에는 이분 탐색 자체가 한 개가 아닌 2개를 이용하는데 두 조건이 다른 점 때문에 많이 헷갈려 다른 사람들의 풀이를 참고를 해보았다.
다른 알고리즘에 비해 형태가 일정할 줄 알았는데, 생각보다 오히려 더 다양하게 알고리즘을 구현하는 방식들이 개개인마다 많이 달랐다.
지금은 이분 탐색 자체가 어색한 느낌이라 바로바로 구상이 안되지만 이것 또한 하다 보면 기본적인 문제에서는 너비, 깊이 탐색처럼 보면 대략적인 구상이 될 정도로는 익힐 수 있도록 해야겠다.

알고리즘 분류

  • 이분 탐색
  • 누적 합

문제 출처 - https://www.acmicpc.net/problem/3020

 

3020번: 개똥벌레

개똥벌레 한 마리가 장애물(석순과 종유석)로 가득찬 동굴에 들어갔다. 동굴의 길이는 N미터이고, 높이는 H미터이다. (N은 짝수) 첫 번째 장애물은 항상 석순이고, 그 다음에는 종유석과 석순이

www.acmicpc.net