Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

12-suhyun113 #42

Merged
merged 4 commits into from
Sep 9, 2024
Merged

12-suhyun113 #42

merged 4 commits into from
Sep 9, 2024

Conversation

suhyun113
Copy link
Collaborator

@suhyun113 suhyun113 commented Jul 25, 2024

🔗 문제 링크

나무 자르기 =>🌳🪓

✔️ 소요된 시간

1시간 + @

✨ 수도 코드

🌳1. 문제 선정 이유🌳

저번 준서님 PR을 보고 이분탐색에 대해 공부해볼 수 있었습니다. 원래도 어떤 자료구조인지는 알고있었지만... 문제를 풀어본 적은 없어서 한번 풀어보고 싶었습니다. 그래서 이번에 나무 자르기 라는 문제로 풀어보게 되었습니다.

🌳2. 문제 설명🌳

1) 🪓설명🪓

나무 M미터가 필요
목재 절단기 -> 절단기에 높이 H지정 필요(양의 정수 또는 0)
톱날이 땅으로부터 H미터 위로 올라감, 한 줄에 연속해 있는 나무 모두 절단
=> 높이 H보다 큰 나무 -> H 위의 부분 잘림
높이 H보다 작은 나무 -> 나무 잘리지 않음

나무를 필요한 만큼만 집으로 가져가려고 할 때,
적어도 M미터의 나무를 집에 가져가기 위해 절단기에 설정할 수 있는 높이의 최댓값?

2)🪓문제 예시🪓

나무의 개수 N = 4
나무의 높이 : 20, 15, 10, 17
절단기에 지정한 높이 H = 15
-> 자른 나무 길이 : 15, 15, 10(15보다 작으므로 잘리지 않음), 15
자르고 남은 나무 길이 => 5(20-15), 0(15-15), -5(10-15) 2(17-15)
=> 5, 2 인 나무 가져갈 수 있음 => M = 총 7미터

3)🪓입출력🪓

  • 입력 : 나무의 수 N, 집에 가져가려고 하는 나무의 총 길이 M
    N개의 나무들의 길이
  • 출력 : 절단기에 설정한 높이 H(적어도 M미터의 나무 집에 가져가기 위해 절단기에 설정할 수 있는 높이의 최댓값)

4)🪓조건🪓

나무의 개수 N(1 <= N <= 1,000,000)
가져가고 싶은 총 나무의 길이 M(1 <= M <= 2,000,000,000)
절단기에 지정한 나무의 높이 H(0 <= H <= 1,000,000,000)

🌳3. 이분 탐색🌳

이분 탐색 : 전체 범위를 지정한 뒤, 처음 중간의 값을 임의의 값으로 선택해, 그 값과 찾고자 하는 값을 비교하는 방식

-> 선택한 중앙값을 기준으로, 찾고자 하는 값(H의 최대 길이)보다 크다면 기준의 왼쪽 범위에서 찾고, 찾고자 하는 값보다 작다면 기준의 오른쪽 범위에서 다시 중앙값을 선택하여 반복합니다.

  • 이분 탐색의 성능
    검색이 반복될 때마다 목푯값을 찾을 확률이 2배가 되기 때문에 속도가 빠릅니다.
    한 번 비교할 때마다 범위가 1/2로 줄어듭니다.
    0~1,000,000,000의 이미 정렬된 데이터에서 사용할 수 있습니다.
    이분 탐색의 최악의 시간 복잡도는 **O(logn)**으로 로그함수의 기울기가 매우 완만해, 데이터의 크기가 아무리 커져도 탐색에 필요한 시간은 매우 완만하게 증가합니다.

🌳4. 문제 풀이 방법🌳

1) 🪓N, M 입력 받기, N개의 나무들의 각각 높이 입력 받기🪓

2) 🪓can_cut함수 구현🪓

  • binary_search 함수에서 얻은 "절단기에 지정한 나무의 높이 H" 값에 따라, can_cut 함수가 실행됩니다.
  • 지정한 높이 H와 입력 받은 나무의 길이를 비교해 더 큰 경우에만 각 나무를 자릅니다. 즉, 나무의 길이에서 H를 뺀 값을 구합니다.
  • N개의 나무들을 모두 반복하며, 각각의 나무의 길이에서 H를 뺀 값을 계속해서 더해주어 total 값을 구합니다
    -> 이때, total은 자료형이 int형이 아닌 long long형 입니다.
    • int형
      image
    • long long형
      image

=> total값은 N개의 나무들 각각의 높이에서 H의 길이 만큼을 뺀 값들이 더해져 있는 값입니다. 그런데, 문제에서 나와있듯이 나무의 높이의 합 total은 항상 M보다 크거나 같습니다. 따라서 M의 값이 최대값인 2,000,000,000이라면 total의 값은 2,000,000,000보다 크거나 같으므로 범위가 -2,147,483,648~2,147,483,647인 int형은 total의 자료형으로 적합하지 않습니다. 따라서 범위가 훨씬 더 큰 long long형으로 해주어야 합니다.

  • 그때 구한 total값이 집에 가져가려고 하는 나무의 총 길이 M보다 커야하므로, 그 조건을 만족하면 true를 반환합니다.

3) 🪓binary_search 함수 구현🪓

  • H의 최대 길이를 구해야 하기 때문에, H 값의 범위인 0 <= H <= 1,000,000,000 를 만족하는 모든 H에 대해서 탐색해야 합니다.
    -> 그러나, 범위 내의 모든 H를 탐색하게 되면, 시간이 매우 오래 걸리기 때문에, _이분 탐색_을 이용할 것입니다.
  • H 값의 범위로 지정한 lo, hi의 사이에 있는 값 중 중앙값을 임의로 정합니다.
  • can_cut 함수를 통해 나무를 잘라 total을 구합니다.
  • true가 반환 된다면, H의 값이 더 클 수 있는지 확인해 봐야 하기 때문에, 현재의 H값을 범위의 최소값인 lo에 저장합니다.
  • 그러나, false가 반환 된다면, H의 값이 찾고자 하는 값보다 작은 것이므로 더 낮은 높이의 값 중 최대 길이를 찾아야 합니다. 따라서, 현재의 H값을 범위의 최대값인 hi에 저장합니다.
  • 이 과정에 따라 H의 값이 계속 변경되어 can_cut함수의 결과가 계속 바뀔 것 입니다.
  • 결과적으로 모든 비교를 끝낸 후, can_cut함수의 조건을 만족시키는 lo의 값이 최대 길이 H로써 반환됩니다.

4) 🪓binary_search 함수를 통해 반환된 lo의 값을 result 변수에 저장 후, 출력합니다.🪓

🌳4. 최종 코드🌳

최종 코드
#include <iostream>
#include <vector>
using namespace std;

// 절단기에 지정한 나무의 높이 H로 나무를 자름
// H보다 크면 자르기(작으면, 값이 0 또는 음수로 나옴)
bool can_cut(vector<int>& trees, int H, int M) {
	long long total = 0; // 자른 나무들의 총 합
	for (int tree : trees) {
		if (tree > H) {
			total += (tree - H);
		}
	}
	return total >= M; // 총 합이 M 보다는 커야 함
}

int binary_search(vector<int>& trees, int M) {
	int lo = 0;
	int hi = 1000000000;

	while (lo + 1 < hi) {
		int mid = (lo + hi) / 2; // 중앙값 저장

		if (can_cut(trees, mid, M)) { // 절단기 높이 변경
			lo = mid; // 현재의 절단기 높이를 가능한 최소 높이로 저장(중앙값 더 커짐)
		}
		else {
			hi = mid; // 현재 절단기 높이 안되면, 더 낮은 높이로 확인
		}
	}

	return lo; // canCut을 만족해야 하므로
}

int main() {
	int N, M;
	cin >> N >> M;

	// N개의 나무들의 길이 목록 만들기
	vector<int> trees(N);
	for (int i = 0; i < N; i++) {
		cin >> trees[i];
	}

	int result = binary_search(trees, M);
	cout << result << endl;

	return 0;
}

📚 새롭게 알게된 내용

long long자료형을 사용해야 할 때가 있다는 것을 알게 되었습니다. 자료형의 범위를 글로만 배워보았지 실제 알고리즘 문제를 풀면서 적용해본 적이 없어 감이 잘 오지 않았는데, 언제 사용해야 하는지 알게되었습니다.
리스트를 입력받았을 때, 정렬되어 있어야 이분 탐색을 사용할 수 있다고 배웠어서 그런지, 문제에서 lo, hi를 제가 직접 정해주고 이분 탐색을 이용해 풀려고 하니 이해가 잘 되지 않아 처음에 힘들었지만, 이런 식으로 문제에 적용할 수 있다는 것을 알게되었습니다.

Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 PR보고 직접 풀어보셨다니 약간은 뿌듯하네요🤗
저도 풀었었는데 PR로 올린 랜선자르기와 거의 똑같은 로직으로 풀면 됐습니다.
가능한(T) 경우의 최댓값을 구하는 방식이죠!!
T, F 특정 조건을 만족하는 최소, 최댓값을 구하는 문제에 이분탐색을 적용한다면 효율적으로 풀 수 있습니다.
저도 바로바로 적용할 수 있게 더 많이 풀어봐야겠네요
깔끔한 풀이 잘 봤습니다👏

Copy link
Collaborator

@pu2rile pu2rile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 수현님은 can_cut 이라는 헬퍼 함수를 사용해서 푸셨네요!
저는 따로 함수를 정의하지 않고 메인에서 직접 잘린 나무 길이를 계산했어요.

코드
N, M = map(int, input().split())
tree = list(map(int, input().split()))
start, end = 1, max(tree)

while start <= end:
    mid = (start+end) // 2
    
    log = 0    # 벌목된 나무 총합
    for i in tree:
        if i >= mid:
            log += i - mid
    
    # 이분탐색
    if log >= M:
        start = mid + 1
    else:
        end = mid - 1
print(end)

이분 탐색의 검색 범위를 startend 로 설정하고 이분 탐색이 끝난 뒤 최대 벌목 높이인 end 값을 출력합니다. 가독성 좋은 코드 잘 봤습니다. pr 수고하셨어요!

@suhyun113 suhyun113 merged commit 72fc116 into main Sep 9, 2024
@suhyun113 suhyun113 deleted the 12-suhyun113 branch September 9, 2024 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants