백준 2805번 나무 자르기

honeyricecake·2022년 3월 3일
0

백준

목록 보기
23/30
post-thumbnail

Prologue

처음에 후보에 1씩 더하거나 1씩 빼다가 잘 안돼서
어떻게 풀어야할지 생각이 안 나 잠이 들었는데
그 다음날 일어나면서 "이진탐색"하고 소리지르며 일어났습니다. ㅋㅋ

1. 내 코드

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int array[1000000];

int BSearch(long long left, long long right, int N, int M)
{
	int i;
	long long tree = 0;
	long long mid = (left + right) / 2;

	for (i = 0; i < N; i++)
	{
		if (array[i] > mid) tree += (array[i] - mid);
	}

	if (tree >= M)
	{
		tree = 0;

		for (i = 0; i < N; i++)
		{
			if (array[i] > (mid + 1)) tree += array[i] - (mid + 1);
		}

		if (tree < M) return mid;

		else return BSearch(mid + 1, right, N, M);
	}

	else return BSearch(left, mid - 1, N, M);
}

int main()
{
	int i, N, M;
	int max = 0;
	scanf("%d %d", &N, &M);

	for (i = 0; i < N; i++)
	{
		scanf("%d", &array[i]);
		max = max > array[i] ? max : array[i];
	}

	long long left = 1;
	long long right = max;

	printf("%d", BSearch(left, right, N, M));

	return 0;
}

하지만 엄청 빠른 사람은 20ms, 일반적으로도 200ms초반이 걸리는 반면에 나는 260ms가 걸려
그 이유를 찾아보고자 다른 사람들의 코드를 좀 읽어보기로 했다.

  1. injoon0617님의 코드 (https://www.acmicpc.net/user/injoon0617)

212ms가 걸린 C언어 전체 7등의 코드이다.
코드도 446바이트인데 지나친 숏코딩 없이 깔끔해보였다.

#include <stdio.h>

int main() {
    int n, m, i, p, q, r, t, a[1000000];
    scanf("%d %d", &n, &m);
    p = q = 0;//p,q는 뭘까?
    for (i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
        if (q < a[i]) q = a[i];//q는 max값이네!
    }
    while (p < q) {
        t = m, r = (p + q) >> 1; -> r은 mid
        //아하 그러면 p는 left, q는 right네!
        for (i = 0; i < n && t > 0; ++i)
            if (a[i] > r) t -= (a[i] - r);
        if (t > 0) q = r;//자르는 나무의 길이의 합이 m보다 크면 left = r
        else p = r + 1;
    }
    printf("%d", p - 1);
    return 0;
}

마지막에 left,right가 같거나 교차하면 left - 1을 출력하는데

이게 어떤 원리인지 이진탐색에서 값을 못 찾을 때를 일반화해보자.

코드에 따르면 left가 right 이상이 되는 순간 left가 가리키는 값은 찾고자 하는 값보다 커야하는데 그렇지 않은 경우가 있는데 어떻게 해야하지?

수정한 나의 코드

f52895님의 코드를 통해
left == right인 순간, 구하고자 하는 값과 mid는 최대 한칸차라는 걸 배웠고 이를 이용하여 시간을 줄여보았다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int array[1000000];

int BSearch(long long left, long long right, int N, int M)
{
	int i;
	long long tree = 0;
	long long mid = (left + right) / 2;

	for (i = 0; i < N; i++)
	{
		if (array[i] > mid) tree += (array[i] - mid);
	}

	if (tree >= M) // tree는 M개 이상 이어야 하고 이 경우는 충분 즉, 이가 최대인지 알아야 한다.
	{
		if (left >= right) //이거 왜 == 하면 안 되지? 한번은 같을 때가 생길텐데?? -> 뒤에서 해결
		{
			tree = 0;

			for (i = 0; i < N; i++)
			{
				tree += (array[i] - (mid + 1));
			}
			
			if (tree < M) return mid;
			else return (mid + 1);
		}
		return BSearch(mid + 1, right, N, M);
	}

	else return BSearch(left, mid - 1, N, M);
}

int main()
{
	int i, N, M;
	int max = 0;
	scanf("%d %d", &N, &M);

	for (i = 0; i < N; i++)
	{
		scanf("%d", &array[i]);
		max = max > array[i] ? max : array[i];
	}

	long long left = 1;
	long long right = max;

	printf("%d", BSearch(left, right, N, M));

	return 0;
}

의문점

(1) if (left >= right)

이 부분을 처음에는 left == right로 했었다.
무조건 한번은 그렇게 되리라 생각했기 때문이다.
그런데 이게 무한루프가 걸린다.
왜지??

이렇게 L과 R이 만나지 않는 경우도 생긴다.

(2) left와 right가 교차하고 tree < M인 경우는 어떡하지?

이러면 바로 찾아야 하는 값으로 가게 되어 조건문 안에서 알맞은 값을 찾게 된다.

0개의 댓글