생각하는 아져씨

[BOJ] 2805 - 나무 자르기 본문

Study/Algorithm

[BOJ] 2805 - 나무 자르기

azeomi 2023. 4. 13. 01:49
2805번: 나무 자르기
https://www.acmicpc.net/problem/2805

문제 정의


상근이는 M미터의 나무가 필요해서 나무를 벌목하려고 한다.

절단기의 높이를 설정하면, 높이만큼 나무를 잘라주는데, 나무들의 높이가 주어졌을 때 적어도 M미터의 나무를 가져가려면 절단기의 높이를 최대 몇까지 설정할 수 있을까?

접근 방법


  • 나이브한 접근방법
    • 나무리스트 [20, 15, 10, 17] 가 주어졌을 때, 절단기의 높이를 1~최대 나무 높이(=20)까지 설정해보면서, (나무-절단기높이)의 합이 M미터 이상인지 확인하면 된다.
    • 즉, sum(각 나무높이 - 절단기 높이) ≥ M (적어도 M 미터이므로, M이상과 비교하면 된다.)
    • 하지만, N은 최대 2,000,000,000 이므로, 이중 for문을 사용하면 시간 초과가 발생한다.
    • 따라서 순차적 탐색보다는 좀 더 효율적인 탐색이 필요하다.

  • 효율적인 탐색: 이진탐색 활용하기
    • 절단기에 설정할 수 있는 높이를 탐색할 때, 1 ~ 최대 나무 높이 까지 모두 확인하지 않아도 된다.
    • [20, 15, 10, 17] 나무 리스트가 있을 때, 상근이가 적어도 7M의 나무를 원한다면, sum(각 나무높이 - 절단기 높이)7M 미만이 되는 절단기 높이는 보지 않아도 되기 때문이다.
    • 즉, 절단기 높이가 15일 때, 상근이는 총 7m를 들고 갈 수 있는데, 예를 들어 절단기 높이가 16이라면, (4+1 = 5)로 7M보다 더 적은 나무를 얻게 되므로 16부터는 계산하지 않아도 된다.
    • 따라서 적절한 기준을 설정한 후 그 기준에 따라서 탐색할 범위를 좁혀나간다면 시간 복잡도를 O(logN)O(logN) 으로 줄일 수 있게된다.

문제 해설


이진 탐색을 사용할 때는 start와 end를 설정하는 것이 중요하다.

이 문제에서는 start는 1, end는 최대 나무 길이로 설정할 수 있다.

또한 이진 탐색을 할 땐 반드시 탐색하고자 하는 배열을 정렬해야한다.

정렬이 완료된 배열을 이진탐색하면 된다.

이 때 찾고자 하는 타겟을 mid로 설정하고 sum(각 나무높이 - mid) 로 얻을 수 있는 나무를 계산한 뒤에 이 값이 상근이가 원하는 M보다 크거나 같으면, 그 mid가 답이 된다.

[주의할 점]

  • 문제에서 상근이는 적어도 M미터 이상의 나무를 가져간다고 했다. 따라서 자른 나무의 합이 꼭 M이 아닐 수도 있다는 이야기이다. (=M이상이라는 말)
  • 이 점에 유의해서 이진 탐색의 조건을 잘 설정해야 한다.
  • 구체적인 예시
4 8
20 15 10 17
# 답은 14

풀이


시간 초과 코드

  • 이분 탐색을 구현했지만, mid가 리턴되는 조건을 잘못 설정해 시간 초과가 떴다.
  • 그리고 M이어야만 mid를 출력하는 것으로 구현했기 때문에 예외 상황이 있었다.
import sys
input = sys.stdin.readline

N, M = map(int, input().split())    # N(나무의 수), M(가져가려고 하는 나무 길이)

tree = sorted(list(map(int, input().split())))  # 나무 리스트

# 1~20(나무 최대 길이)
# 10(10+5+7 = 22) -> M 보다 크면, 더 많이 잘라야 하므로, 10 이후에서 다시 찾기
# 15(5+2 = 7) 

def binary_search(tree, M, start, end):
    sum = 0
    if start > end:
        return binary_search(tree, M+1, end-1, start-1)
    mid = (start+end) // 2
    for t in tree:
        if t-mid >= 0:  # 잘라진다면,
            sum += (t-mid)
    if sum == M:
        return mid
    elif sum > M:
        return binary_search(tree, M, mid+1, end)
    else:
        return binary_search(tree, M, start, mid-1)

result = binary_search(tree, M, 0, max(tree))
print(result)

올바른 코드

  • 예외 상황을 포함해 조건문을 ≥ 로 수정하니 시간 초과도 나오지 않았다.
import sys
input = sys.stdin.readline

N, M = map(int, input().split())    # N(나무의 수), M(가져가려고 하는 나무 길이)

tree = sorted(list(map(int, input().split())))  # 나무 리스트

def binary_search(tree, M, start, end):
    sum = 0
    mid = (start+end) // 2
    if start > end:
        return mid
    
    # 나무 자르기
    for t in tree:
        if t-mid >= 0:  # 잘라진다면,
            sum += (t-mid)
            
    if sum >= M:
        return binary_search(tree, M, mid+1, end)
    else:
        return binary_search(tree, M, start, mid-1)

result = binary_search(tree, M, 0, max(tree))
print(result)

정리


  • 시간 복잡도는 O(logN)O(logN) 이다.
  • 이분 탐색을 공부했다면 쉽게 아이디어를 파악할 수 있는 문제였다.
  • 하지만 이분 탐색 구현에서 자주 실수 할 수 있는 시작점, 끝점 설정과 리턴 조건을 꼼꼼하게 확인할 필요가 있었다.


Uploaded by N2T

'Study > Algorithm' 카테고리의 다른 글

[BOJ] 9461 - 파도반 수열  (0) 2023.04.13
[Programmers] N으로 표현  (0) 2023.04.13
[BOJ] 10816 - 숫자 카드2  (0) 2023.04.11
[BOJ] 1018 - 체스판 다시 칠하기  (0) 2023.04.11
[Programmers] 소수 찾기  (0) 2023.04.11