이 포스트에서는 프로그래밍 문제 사이트 백준 Online Judge(BOJ)의 10816번 : '숫자 카드 2', 1654번 : '랜선 자르기', 2805번 : '나무 자르기' 문제의 풀이 코드와 해설을 다루고 있습니다.
문제 난이도는 Solved.ac 기준 Silver 티어에 해당하며, 문제를 풀이하기 위해 이분 탐색 알고리즘에 대한 이해가 필요합니다. (다만 일부 문제는 이분 탐색으로 분류되어 있음에도 더 쉽게 풀이할 수 있는 방법이 있다면 이분 탐색 알고리즘을 사용하지 않고 풀이한 것도 있습니다.)
10816번 : 숫자 카드 2
N개의 수가 주어지고 그 다음 M개의 수가 주어질 때 각각의 수가 몇 개 있었는지를 찾아서 출력하는 문제입니다.
정렬을 해준 뒤 그 수가 몇 개 있는지 찾아주면 되는데, N이 최대 50만이므로 이분 탐색 또는 log N 시간에 탐색할 수 있는 알고리즘을 사용하여 문제를 풀이해주어야 합니다.
다만 C++의 경우 algorithm 헤더 파일에 유용한 함수들이 많으므로 이들을 활용하여 쉽게 해결할 수 있습니다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
int N; cin >> N;
vector<int> arr(N);
for(int i=0; i<N; i++) cin >> arr[i];
sort(arr.begin(), arr.end());
int M; cin >> M;
while(M--) {
int val; cin >> val;
cout << upper_bound(arr.begin(), arr.end(), val) - lower_bound(arr.begin(), arr.end(), val) << " ";
}
}
upper_bound 함수와 lower_bound 함수를 이용하여 두 주소를 빼면 val 값에 해당하는 원소가 몇 개 있는지를 쉽게 구해줄 수 있으므로 이를 활용하여 위와 같이 간단하게 풀이할 수 있습니다.
1654번 : 랜선 자르기
K개의 랜선들을 적절히 잘라 N개의 랜선을 만들려고 할 때 각 랜선의 길이로 가능한 최댓값을 구하는 문제입니다.
이분 탐색으로 해결하는 것을 처음부터 떠올리기는 힘들지만, 문제 분류가 이분 탐색으로 되어있기 때문에 바로 이분 탐색으로 접근해보도록 하겠습니다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
long long K, N; cin >> K >> N;
vector<long long> arr(K);
long long Max = 0;
for(int i=0; i<K; i++) {
cin >> arr[i];
Max = max(Max, arr[i]);
}
long long Left = 1, Right = Max, Mid, ans = 0;
while(Left <= Right) {
Mid = (Left + Right)/2;
long long cnt = 0;
for(int i=0; i<K; i++) cnt += arr[i]/Mid;
if(cnt >= N) {
Left = Mid + 1;
ans = max(ans, Mid);
}
else Right = Mid - 1;
}
cout << ans;
}
주어진 K개의 길이를 가지고 최대 랜선의 길이를 한 번에 구하는 방법은 찾기 힘들어 보입니다.
따라서 1 ~ (모든 랜선 중 최대 길이) 사이의 정수 하나가 답이 될 것이므로 이 구간에서 이분 탐색을 사용하여 문제를 풀이할 수 있습니다.
N <= 1,000,000이므로 log N 시간에 충분히 해결이 가능합니다.
적절히 Mid를 잡고 범위 안에서 해당 Mid를 단위 길이로 했을 때 몇 개의 랜선이 나오는지를 기록하여 최대 랜선의 수를 가지는 Mid 값을 찾고, 그 때의 Mid를 출력해주면 됩니다.
그리고 int형으로 변수들을 두면 Mid = (Left + Right)/2를 계산하는 과정에서 Left + Right에서 오버플로우가 발생하므로 웬만하면 모든 자료형을 long long과 같이 여유있게 잡아주는 것이 좋습니다.
개인적으로 이 문제는 int형의 overflow를 모두 고려하여 문제를 푼다면 상당히 어려울 수 있으므로 Silver III보다는 조금 더 높게 난이도를 쳐줘도 되지 않나 싶습니다.
2805번 : 나무 자르기
문제 자체는 위의 랜선 문제와 거의 비슷하며, 접근 방식도 똑같이 하면 풀릴 것으로 보이는 문제입니다.
N개의 나무 길이 중 최댓값을 찾은 뒤 1 ~ 최댓값 사이에서 이분 탐색으로 적어도 M미터의 나무를 가져가기 위해 설정할 수 있는 최대 높이를 찾아주면 됩니다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
long long N, M; cin >> N >> M;
vector<long long> arr(N);
for(int i=0; i<N; i++) cin >> arr[i];
sort(arr.begin(), arr.end(), greater<int>());
long long Left = 1, Right = arr[0], ans = 0;
while(Left <= Right) {
long long Mid = (Left + Right)/2;
long long sum = 0;
for(int i=0; i<N && arr[i]>Mid; i++) sum += arr[i] - Mid;
if(sum < M) Right = Mid - 1;
else {
ans = Mid;
Left = Mid + 1;
}
}
cout << ans;
}
위의 문제와 풀이 코드도 거의 비슷하며, 다른 점이 있다면 vector를 sort 해준 뒤 나무 길이가 절단 길이보다 높아 절단하고도 나무가 남을 수 있는 것들에 한해서만 연산을 해주도록 코드를 작성해야 깔끔하다는 것입니다.
그 과정에서 내림차순으로 정렬하기 위해 sort 함수에서 greater<int>()를 사용해주며, 아래의 반복문에서도 arr[i] > Mid라는 조건을 추가해주어 탐색 시간을 최소화해주도록 합니다.
'알고리즘 > 백준(BOJ) 문제풀이' 카테고리의 다른 글
[백준/BOJ] 11286번 : 절댓값 힙, 1655번 : 가운데를 말해요 (우선순위 큐) (0) | 2022.03.05 |
---|---|
[백준/BOJ] 2110번 : 공유기 설치, 1300번 : K번째 수 (이분 탐색) (0) | 2022.03.05 |
[백준/BOJ] 11444번 : 피보나치 수 6 (분할정복 거듭제곱, 행렬 식 조작) (0) | 2022.03.05 |
[백준/BOJ] 11401번 : 이항 계수 3, 10830번 : 행렬 제곱 (분할 정복, 빠른 거듭제곱) (0) | 2022.03.05 |
[백준/BOJ] 1992번 : 쿼드트리, 1780번 : 종이의 개수, 1629번 : 곱셈 (분할 정복) (0) | 2022.03.04 |