본문 바로가기
카테고리 없음

[파이썬, 자바] 백준 - 2343 (이분 탐색)

by devault121 2024. 11. 28.

https://www.acmicpc.net/problem/2343

 

해당 문제는 이진 탐색을 활용하는 문제다.

 

먼저 이진 탐색이 무엇인지 부터 알아보자.

 

이분 탐색

- 정렬된 데이터에서 원하는 값을 탐색할 때 사용하는 알고리즘

- 대상 데이터의 중앙값과 찾고자 하는 값을 비교해 데이터의 크기를 절반씩 줄이변서 대상을 찾는다.

- 한 번의 연산에 탐색할 데이터의 크기가 절반씩 줄기 때문에 O(logN)의 시간복잡도를 가진다.

ex) 데이터의 수가 16개라면, 4(log16)번의 연산으로 원하는 값을 찾을 수 있다.

 

<이분 탐색 과정>

1. 현재 데이터 셋의 중앙값을 선택한다.

2. 중앙값 > 타깃값 : 중앙값 기준으로 오른쪽 데이터셋은 버린다. (중앙값 기준 오른쪽에는 타깃값이 존재하지 않기 때문)

3. 중앙값 < 타깃값 : 중앙값 기준으로 왼쪽 데이터셋은 버린다.

4. 과정 1~3을 반복하다가 중앙값 == 타깃값일 때 탐색을 종료한다.

 

출처 : 나무위키

위 그림은 타깃값은 77, 탐색 범위는 1 ~ 100으로 설정하여 탐색을 진행한 경우이다.

 

중앙값 50이 77보다 작으므로, 중앙값 기준 왼쪽 데이터셋은 버리고, 오른쪽 데이터셋을 가지고 다시 탐색하는 것을 볼 수 있다.

 

 

그럼 이를 바탕으로 문제를 풀어보겠다.

 

문제에서 M개의 블루레이에 강의를 녹화하겠다고 했고, 이 때 가능한 블루레이의 크기 중 최소를 구하라고 했다.

강의는 중간에 잘리거나 순서가 바뀌면 안 되기 때문에 주어진 순서 그대로 블루레이에 녹화되어야 한다.

 

여기서 이분 탐색을 어떻게 활용할 것인가?

 

문제 예제를 통해 생각해보자.

9 3 # N, M
1 2 3 4 5 6 7 8 9

녹화할 강의 수는 9개, 강의를 저장할 블루레이 수는 3개이다.

 

즉, 3개의 블루레이에 / 9개의 강의를 순서대로 녹화할 수 있도록 하는 / 최소의 블루레이 크기를 구하는 것이다.

 

구하고자 하는 값은 블루레이의 크기이기 때문에, 탐색할 데이터셋도 블루레이의 크기로 이루어져야 한다.

 

<데이터셋 범위 구하기>

시작값 : 9 (블루레이 크기가 적어도 9는 되어야 모든 강의를 녹화할 수 있기 때문, 이 때 필요한 블루레이의 개수는 6개가 된다.)

끝값 : 45 (모든 강의를 하나의 블루레이에 녹화하겠다고 한다면, 그 때가 블루레이 크기의 최댓값이다.)

 

따라서 9 ~ 45 사이의 데이터셋을 가지고 이분 탐색을 진행하면 된다.

 

Code(Python - 오답)

import sys
input = sys.stdin.readline

N, M = map(int, input().split())
video = list(map(int, input().split()))

start = max(video)
end = sum(video)
result = sum(video) # 가능한 블루레이 크기 중 최소

while start <= end:
    bluSize = (start + end) // 2 # 블루레이 크기

    subSize = 0 # 블루레이 하나당 녹화 가능한 비디오 길이
    m = 0 # 필요한 블루레이 개수
    for size in video:
        if subSize + size > bluSize: # 이번 비디오까지 녹화했을 때, 블루레이 크기를 초과한다면,
            m += 1 # 블루레이 개수 늘리고,
            subSize = 0 # 비디오 개수 초기화(다음 블루레이로 넘어가니까)

        subSize += size # 블루레이에 현재 비디오 길이 추가

    if subSize != 0: # 남은 비디오가 있다면,
        m += 1 # 이 또한 블루레이 하나에 담아야 하므로, 블루레이 개수 추가

    if m < M: # 현재 블루레이 크기(bluSize)일 때 필요한 블루레이의 개수가 강토가 녹화하고자 하는 블루레이의 개수(M)보다 작거나 같을 경우,
        end = bluSize - 1 # 블루레이의 크기를 줄여야 하므로, end 변경
    elif m > M: # 필요한 블루레이의 개수가 강토가 녹화하고자 하는 블루레이의 개수보다 많을 경우,
        start = bluSize + 1 # 블루레이의 크기를 늘려야 하므로, start 변경
    else: # 필요한 블루레이의 개수가 강토가 녹화하고자 하는 블루레이의 개수와 동일할 경우,
        result = min(result, bluSize) # 기존에 저장된 블루레이의 크기와 현재 구한 블루레이의 크기 중 최솟값 저장
        end = bluSize - 1 # 현재 구한 블루레이의 크기보다 더 작은 경우가 있을 수 있으므로, end 변경

print(result)

 

이렇게 제출했으나 오답 처리가 됐다.

 

문제를 잘못 이해하고 있었다.

 

M개의 블루레이를 무조건 다 쓰면서 최소의 블루레이 크기를 구하는 줄 알고 마지막 조건문을 작성했는데,

 

알고 보니 M개 이상만 쓰지 않으면 되는 것이었다.

 

즉, "M개보다 더 적게 써도 되니까 최소의 블루레이 크기만 구해~" 이다..

 

이를 반영하여 다시 코드를 짰다.

 

Code(Python - 정답)

import sys
input = sys.stdin.readline

N, M = map(int, input().split())
video = list(map(int, input().split()))

start = max(video)
end = sum(video)
result = sum(video) # 가능한 블루레이 크기 중 최소

while start <= end:
    bluSize = (start + end) // 2 # 블루레이 크기

    subSize = 0 # 블루레이 하나당 녹화 가능한 비디오 길이
    m = 0 # 필요한 블루레이 개수
    for size in video:
        if subSize + size > bluSize: # 이번 비디오까지 녹화했을 때, 블루레이 크기를 초과한다면,
            m += 1 # 블루레이 개수 늘리고,
            subSize = 0 # 비디오 개수 초기화(다음 블루레이로 넘어가니까)

        subSize += size # 블루레이에 현재 비디오 길이 추가

    if subSize != 0: # 남은 비디오가 있다면,
        m += 1 # 이 또한 블루레이 하나에 담아야 하므로, 블루레이 개수 추가

    if m <= M: # 현재 블루레이 크기(bluSize)일 때 필요한 블루레이의 개수가 강토가 녹화하고자 하는 블루레이의 개수(M)보다 작거나 같을 경우,
        result = min(result, bluSize) # 기존에 저장된 블루레이의 크기와 현재 구한 블루레이의 크기 중 최솟값 저장
        end = bluSize - 1 # 현재 구한 블루레이의 크기보다 더 작은 경우가 있을 수 있으므로, end 변경
    else: # 필요한 블루레이의 개수가 강토가 녹화하고자 하는 블루레이의 개수보다 많을 경우,
        start = bluSize + 1 # 블루레이의 크기를 늘려야 하므로, start 변경

print(result)

 

마지막 조건문만 변경해줬다.

 

현재 구한 블루레이 개수가 M보다 작더라도 블루레이 크기가 최소가 된다면, 결과값을 바꾸도록 했다.

 

Code(Java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());
        int[] video = new int[N];

        int start = 0;
        int end = 0;
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < N; i++) {
            video[i] = Integer.parseInt(st.nextToken());

            end += video[i];
            start = Math.max(start, video[i]);
        }

        int result = end;
        while (start <= end) {
            int bluSize = (start + end) / 2;

            int subSize = 0;
            int m = 0;
            for (int size : video) {
                if (subSize + size > bluSize) {
                    m++;
                    subSize = 0;
                }
                subSize += size;
            }

            if (subSize != 0) {
                m++;
            }

            if (m <= M) {
                result = Math.min(result, bluSize);
                end = bluSize - 1;
            } else {
                start = bluSize + 1;
            }
        }

        System.out.println(result);
    }
}