이번 문제는 백준의 실버 1 문제 보석 상자 문제이다.
차근차근 문제를 읽어보도록 하자.
선생님이 최대 10^9
명의 아이들에게 보석을 나누어 주려고 한다.
보석은 여러 색으로 구성되어 있는데, 최대 300,000
가지의 색상이 가능하다.
이 때, 각 학생들은 동일한 색의 보석만 들고갈 수 있다.
가장 많이 가져가는 학생이 최소로 보석을 들고 가는 경우는 어떻게 될까?
문제 분석
우선 아이들의 수가 굉장히 많은 것을 눈여겨 봐야한다.
아이들의 최대 수가 무려 10억
이나 된다.
이는 int
의 최대 값을 넘어가는 값으로, 매우 큰 값이다.
즉 배열을 만들거나 해서 직접 동작을 구현하는 문제는 아니라는 말이 된다.
보통 이렇게 매우 큰 값에 대해서 탐색을 진행할 때는 이분탐색을 사용한다.
보통 Parametric search
라고 하며 최소, 최대 값 탐색에 있어 유용하다.
이분 탐색
과연 어떤 Parameter
를 기준으로 이분 탐색을 진행해야 할까?
보통은 매우 큰 수를 기준으로 이분 탐색을 진행하게 되는데,
그럼 학생들의 수를 줄였다 늘였다 하며 탐색을 진행해야 할까?
그건 아니라고 생각한다.
왜냐하면 학생들의 수는 고정되어 있는 값으로 주어지기 때문이다.
우리가 판단해야 할 부분은, 초콜릿을 최대한 많이 분포시키는 것이다.
만약 학생의 수가 작았다면 Greedy
하게 풀 수 있었을 것이다.
내 생각에는 가장 많이 받는 초콜릿의 값을 기준으로 하는게 좋을 것 같다.
즉, 어차피 답으로 구해야 하는 질투심
의 값을 기준으로 하는 것이다.
질투심
을 기준으로 특정 색상을 나누면, 해당 색을 받을 수 있는 학생의 수가 나온다.
이 때 최종적으로 받을 수 있는 모든 학생의 수가 주어진 학생의 수보다 많다면?
우리는 질투심
을 더 높게 설정할 수 있게 되는 것이다.
반대로 최종적으로 받을 수 있는 모든 학생의 수가 주어진 학생 수보다 적다면?
우리는 질투심
을 더 낮추어 분포도를 높일 필요가 있다.
위의 논리대로 코드를 구현해보도록 하자!
while (left_v <= right_v) {
int mid = (left_v + right_v) / 2;
int kids = 0;
for (int i = 0; i < colors; i++) {
kids += (color_jewels[i] / mid);
// mid 값이 질투심이라면, 보석 / mid 명이 받을 수 있다.
if (color_jewels[i] % mid != 0)
kids++;
// 나머지가 0이 아니라면 한명 더 받을 수 있다.
}
if (kids <= num) {
right_v = mid - 1;
answer = mid;
}
else
left_v = mid + 1;
}
이분 탐색을 진행할 때, 가장 많은 보석이 있는 색상을 기준으로 잡는다.
해당 값에서부터 질투심
값을 조정하며 최소값을 찾는 것이다.
모든 보석 수를 돌며 질투심
값으로 나누었을 때 받을 수 있는 학생 수를 구한다.
나누었는데 나머지가 남는다면, 1
명의 학생이 나머지를 받을 수 있는 것이다.
계산된 학생 수가 주어진 학생 수보다 많다면, 질투심
을 높일 수 있다.
반대로 학생 수보다 적다면, 질투심
을 낮추어야 한다.
이 때 왜 right
를 올릴 때 답을 갱신해야 하는 것일까?
만약 학생 수에 대해서 모자랄 때 갱신을 한다고 생각해보자.
그럼 못받는 학생이 생긴다는 말이 되는데, 내가 계산한 방식을 다시 생각해보자.
질투심
만큼 동일하게 모든 학생에게 나누고, 남은 것을 남는 학생에게 주었다.
그래도 모든 학생에게 나누어 줄 수 없다면, 사실 다른 학생이 나누어줘도 무방하다.
왜냐하면 질투심
만큼 가진 학생은 최소한 1명만 있으면 되기 때문이다!
결국 어차피 모든 학생에게 나누어 줄 수 있게 되는 것이다.
그리고 우리는 최대한 질투심
을 낮추는 것이 목적이다.
즉, 분포도를 가장 고르게 만들어야 하는 것이다.
따라서 우리는 학생 수와 계산된 학생 수가 같거나 클 때 정답을 갱신해야 한다!