https://www.acmicpc.net/problem/17951
문제 설명
Parametric Search (매개변수 탐색) 문제이다.
주어진 시험지 점수를 나누어 여러 부분합을 만들 때, 최소 부분합이 최대가 되도록 나누는 문제이다.
누적합을 이용해 각 구간의 합을 계산하고, 구간의 합이 현재 mid 값보다 크거나 같으면 해당 구간을 하나로 묶고 구간의 개수를 증가시킨다.
그 후, mid 값을 변경하며 원하는 개수의 구간이 나오도록 하고, 이때 최소 부분합이 최대가 되는 mid 값을 찾아내면 된다.
문제 풀이
8 3
12 7 19 20 17 14 9 10
k값이 3인경우를 생각해보자
첫 번째 이진 탐색:
- start = 0, end = 108, mid = (0 + 108) / 2 = 54
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 + 20 = 58 (54 이상)
- 세 번째 구간: 17 + 14 + 9 + 10 = 50 (54 미만)
구간을 3개 이상의 구간을 만들 수 없으므로, mid = 54는 적합하지 않다.
mid 값을 줄여야 한다.
두 번째 이진 탐색:
- start = 0, end = 53, mid = (0 + 53) / 2 = 26
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 = 38 (26 이상)
- 두 번째 구간: 20 + 17 = 37 (26 이상)
- 세 번째 구간: 14 + 9 + 10 = 33 (26 이상)
이때는 3개의 구간을 만들 수 있다. mid = 26은 유효하다. 최소 부분합은 33이다.
mid 값을 더 크게 설정하여 최소 부분합을 키워보자.
세 번째 이진 탐색:
- start = 27, end = 53, mid = (27 + 53) / 2 = 40
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 + 20 = 58 (40 이상)
- 두 번째 구간: 17 + 14 + 9 = 40 (40 이상)
- 세 번째 구간: 10 (40 미만)
구간을 3개 이상의 구간을 만들 수 없으므로, mid = 40은 적합하지 않다.
mid 값을 줄여야 한다.
네 번째 이진 탐색:
- start = 27, end = 39, mid = (27 + 39) / 2 = 33
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 = 38 (33 이상)
- 두 번째 구간: 20 + 17 = 37 (33 이상)
- 세 번째 구간: 14 + 9 + 10 = 33 (33 이상)
이때도 3개의 구간을 만들 수 있다. mid = 33은 유효하다. 최소 부분합은 33이다.
mid 값을 더 크게 설정하여 최소 부분합을 키워보자.
다섯 번째 이진 탐색:
- start = 34, end = 39, mid = (34 + 39) / 2 = 36
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 = 38 (36 이상)
- 두 번째 구간: 20 + 17 = 37 (36 이상)
- 세 번째 구간: 14 + 9 + 10 = 33 (36 미만)
구간을 3개 이상의 구간을 만들 수 없으므로, mid = 36은 적합하지 않다.
mid 값을 줄여야 한다.
여섯 번째 이진 탐색:
- start = 34, end = 35, mid = (34 + 35) / 2 = 34
부분합 계산:
- 첫 번째 구간: 12 + 7 + 19 = 38 (34 이상)
- 두 번째 구간: 20 + 17 = 37 (34 이상)
- 세 번째 구간: 14 + 9 + 10 = 33 (34 미만)
구간을 3개 이상의 구간을 만들 수 없으므로, mid = 34은 적합하지 않다.
mid 값을 줄여야 한다.
일곱 번째 이진 탐색:
- start = 34, end = 33
- start <= end 가 성립하지 않아서 탐색이 종료된다.
☆ 이진탐색 수행시 등호가 어디에 들어가야 하는지 ☆
이진 탐색을 수행할 때 등호가 어디에 들어가야하는지 헷갈렸다.
if (cnt >= k) {
start = mid + 1;
} else {
end = mid - 1;
}
- cnt >= k 조건은 cnt가 k와 같거나 클 때를 포함한다.
- cnt == k일 때도 start를 증가시켜 탐색 범위를 오른쪽으로 좁힌다.
- 따라서, cnt == k일 때도 mid를 무시하고 오른쪽 영역을 계속 탐색한다.
- 즉, 최대값을 구하거나 특정 조건을 만족하는 값의 상한(upper bound)을 찾는 경우 사용한다.
if (cnt > k) {
start = mid + 1;
} else {
end = mid - 1;
}
- cnt > k 조건은 cnt == k일 때 end를 감소시켜 탐색 범위를 왼쪽으로 좁힌다.
- 이 방식은 최소값을 구하거나 특정 조건을 만족하는 값의 하한(lower bound)을 찾는 경우 적합하다.
전체 코드
import java.io.*;
import java.util.*;
public class Main {
static BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
static StringBuilder output = new StringBuilder();
static StringTokenizer token;
static int n, k;
static int[] scores;
static int sum;
static int start, end, mid;
static long ans, tempAns;
public static void sol() {
start = 0;
end = sum;
while (start <= end) {
mid = (start + end) / 2;
int cnt = 0, temp = 0;
tempAns = Integer.MAX_VALUE;
for (int i = 0; i < n; i++) {
temp += scores[i];
if (temp >= mid) {
cnt++;
tempAns = Math.min(tempAns, temp);
temp = 0;
}
}
// 등호는 어디에 들어가야 하는지
if (cnt >= k) { // 점수의 최대값 -> mid 의 상한
start = mid + 1;
ans = Math.max(ans, tempAns);
} else {
end = mid - 1;
}
}
output.append(ans);
}
public static void init() throws Exception {
sum = 0;
ans = 0;
token = new StringTokenizer(input.readLine());
n = Integer.parseInt(token.nextToken());
k = Integer.parseInt(token.nextToken());
token = new StringTokenizer(input.readLine());
scores = new int[n];
for (int i = 0; i < n; i++) {
scores[i] = Integer.parseInt(token.nextToken());
sum += scores[i];
}
}
public static void main(String[] args) throws Exception {
init();
sol();
System.out.println(output);
}
}
'중요한건 꾸준함! > 자료구조, 알고리즘' 카테고리의 다른 글
[알고리즘] LIS - 최장 증가 부분 수열(dp,이진탐색,수열 복원) (1) | 2025.04.21 |
---|---|
[BOJ] 백준 6443 애너그램 (Java) (1) | 2024.12.12 |
[C++ STL] next_permutaion (0) | 2024.04.16 |
순열(15649), 조합(15650) (c++) (0) | 2023.04.28 |
[c++ STL]vector sort() 함수와 compare 함수 (0) | 2023.04.11 |
댓글