백준 2805: 나무 자르기와 이분 탐색, 그리고 long
백준 2805번 나무 자르기는 가장 높은 절단 높이를 찾는 문제입니다. 그 높이로 나무를 잘랐을 때 필요한 길이 M 이상을 얻을 수 있어야 합니다.
높이를 하나씩 낮추는 방식은 느렸습니다
처음에는 가장 높은 나무부터 시작해 절단 높이를 1씩 낮췄습니다. 높이마다 모든 나무를 잘라 보고 잘린 길이의 합이 M 이상이면 멈췄습니다. 단순한 이중 반복문이었고 시간 초과가 났습니다.
import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
st = new StringTokenizer(br.readLine(), " ");
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine(), " ");
int[] trees = new int[N];
for (int i = 0; i < N; i++) {
trees[i] = Integer.parseInt(st.nextToken());
}
// 시작은 나무들 중에 가장 큰 값으로 시작한다.
for (int i = Arrays.stream(trees).max().getAsInt(); i > 0; i--) {
// 계속해서 나무를 잘랐을 때의 총 합을 초기화 한다.
int sum = 0;
// 나무의 개수만큼 반복한다.
for (int tree : trees) {
// 만약 나무가 자르려는 높이보다 크다면 잘라보고 그 높이를 합에 추가하고
// 아니라면 그냥 넘어간다.
if (tree > i) {
sum += (tree - i);
} else continue;
// 자르고 난 후의 나무의 높이가 원하는 길이를 얻었거나 더 얻었으면 루프 탈출
if (sum >= M) {
bw.write(String.valueOf(i));
break;
}
}
// 루프 탈출
if (sum >= M) {
break;
}
}
bw.flush();
}
}높이의 범위를 절반씩 줄였습니다
절단 높이가 높아지면 얻는 나무의 양은 줄어듭니다. 이 성질을 이용해 높이를 이분 탐색했습니다.
중간 높이 mid에서 얻는 합이 M보다 작으면 더 낮게 잘라야 하므로 max = mid로 범위를 줄입니다. M 이상이면 더 높은 위치도 가능한지 확인하기 위해 min = mid + 1로 이동합니다. 반복이 끝난 지점 바로 아래인 min - 1이 조건을 만족하는 가장 높은 절단 높이입니다.
그런데 탐색 방법을 바꾼 뒤에도 합계를 int로 두었을 때는 오답이 나왔습니다. 문제의 큰 입력 범위를 다시 읽은 뒤 sum을 long으로 바꿔 해결했습니다. 나무 한 그루의 높이와 여러 나무에서 얻은 길이의 합은 따로 생각해야 했습니다.
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
st = new StringTokenizer(br.readLine(), " ");
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int[] trees = new int[N];
int max = 0;
int min = 0;
st = new StringTokenizer(br.readLine(), " ");
for (int i = 0; i < N; i++) {
trees[i] = Integer.parseInt(st.nextToken());
if (trees[i] > max) {
max = trees[i];
}
}
while (min < max) {
int mid = (max + min) / 2;
long sum = 0;
for (int height : trees) {
if ((height - mid) > 0) {
sum += height - mid;
}
}
if (sum < M) {
max = mid;
} else {
min = mid + 1;
}
}
bw.write(String.valueOf(min - 1));
bw.flush();
}
}