본문 바로가기
PS/BOJ

[자바] 백준 2343 - 기타 레슨 (java)

by Nahwasa 2023. 7. 17.

문제 : boj2343

 

 

필요 알고리즘

  • 매개 변수 탐색 (parametric search), 이분 탐색 (binary search)
    • 이분 탐색을 응용한 매개 변수 탐색을 사용해 푸는 문제이다. 이런류는 매개 변수 탐색할 값에 대한 정의만 잘 세우면 갑자기 문제가 쉬워진다.

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

 

 

풀이

  일단 순차적으로 생각해보자.

 

1. 우선 isPossible(int limit) 이라는 함수를 생각해보자. 이건 'limit' 이라는 값 이하로 M개로 값을 쪼갤 수 있는지 판단하는 함수이다.

 

2. 그렇다면 limit 값의 최소치는 어떻게 될까? 우선 강의 1개는 원자적이므로, 강의 1개 중 최대의 강의 길이보다는 커야한다. 또한 N/M(올림) 보다도 커야 한다.

for (int i = 0; i < n; i++) max = max(max, arr[i] = Integer.parseInt(st.nextToken()));

int start = max(max, n/m+(n%m==0?0:1));

 

3. 자 그럼 저 start라는 값부터 시작해서 +1씩 증가시키면서 isPossible(start)를 보다가, true가 뜨는 순간이 답이 된다!! 이론적으론 그런데, 문제는 isPossible의 시간복잡도가 O(N) 이다. 그리고 이론상 나올 수 있는 limit의 최대치는 N*10000 이다. 따라서 O(10000 * N^2) 이 되므로 시간초과가 된다.

 

4. 그러므로 limit이라는 값을 찾기 위해 매개 변수 탐색을 사용해준다. 위에서 정한 start 값부터, 이론상 최대치인 10억까지 진행해주면 된다. '10억 = N의 최대치 100000 * 각 강의의 길이 최대치 10000' 이다. 참고로 실제 최대치는 N개의 입력값의 합이긴 한데, 어차피 매개 변수 탐색을 할 것이므로 시간 복잡도에 영향은 딱히 없다. 실제로도 제출해보니 1ms도 차이 안났다. isPossible이 true면 end를 낮추고, false면 start를 높이는 식으로 답을 찾을 때 까지 이분 탐색을 진행하는거다.

int start = max(max, n/m+(n%m==0?0:1));
int end = 1000000000;
while (start<=end) {
    int mid = (start+end)/2;
    if (!isPossible(mid))
        start = mid+1;
    else
        end = mid-1;
}

 

 

코드 : github

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

import static java.lang.Math.*;

public class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1<<16);

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

    int n, m;
    int[] arr;

    private void solution() throws Exception {
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        st = new StringTokenizer(br.readLine());
        arr = new int[n];
        int max = 0;
        for (int i = 0; i < n; i++) max = max(max, arr[i] = Integer.parseInt(st.nextToken()));

        int start = max(max, n/m+(n%m==0?0:1));
        int end = 1000000000;
        while (start<=end) {
            int mid = (start+end)/2;
            if (!isPossible(mid))
                start = mid+1;
            else
                end = mid-1;
        }

        System.out.println(start);
    }

    private boolean isPossible(final int limit) {
        int idx = 0;
        for (int i = 0; i < m; i++) {
            int sum = 0;
            while (idx<n && sum <= limit) {
                if (sum+arr[idx] > limit) break;
                sum += arr[idx++];
            }
        }
        return idx == n;
    }
}