[PS] BOJ 17383 / 옥토끼는 통신교육을 풀어라!!

[PS] BOJ 17383 / 옥토끼는 통신교육을 풀어라!!
Thumbnail: Photo by Racim Amr (Unsplash)
문제 링크: https://www.acmicpc.net/problem/17383

아이디어만 떠올리면 구현은 어렵지 않은 문제입니다.

풀이

최적화 문제 생각해보기

문제를 처음 보면 각 문제를 어떻게 배치해 풀어야 하는지 고민이 되지만, 문제가 아닌 시간 간격에 초점을 둬봅시다.

tncks0121는 항상 각 문제가 풀리는 시간의 차이에만 신경을 씁니다. 문제를 어떻게 푸는지는 상관 없이, 한 문제를 푼 뒤 다음 문제가 풀리기까지의 시간 간격을 최소한으로 만드는 문제로 해석할 수 있습니다. 이 시간 간격을 $M$이라 합시다.

$M$의 최솟값을 찾는 문제는 최적화 문제입니다. 매개변수 탐색을 활용해 풀 수 있으니, 이제 결정 문제를 생각해 봅시다.

저는 "임의의 시간 간격 $M$에 대해 한 칸의 시간 간격 동안에는 두개의 문제만 풀 수 있을 때, 각 문제별로 풀기 위해 필요한 시간 간격의 총 개수가 $2 \times N$을 넘지 않는가?" 를 결정 문제로 사용했습니다.
문제의 예제 데이터에서 영감을 받았는데, $N$개의 문제를 시간 간격 한 칸당 두 문제씩 배치하려면, $2 \times N$개가 넘어가는 경우는 시간 간격을 더 크게 잡을 수 있는 경우가 존재한다고 생각했습니다. 따라서, $2 \times N$ 이하의 칸을 사용해 모든 문제를 배치할 수 있는 경우를 정답, 그렇지 않은 경우는 오답으로 두고 탐색을 진행했습니다.

이 결정 문제를 통해, 시간 간격 $M$은 NNNNN...NYYYYY 꼴로 나타나며 우리가 필요한 $M$의 최솟값을 결정 문제를 통한 이분 탐색, 즉 매개변수 탐색으로 찾을 수 있습니다.

전체 코드

input = open(0).readline

N = int(input())
times = list(map(int, input().split()))

# 매개변수 탐색의 시작 범위로 사용하기 위한 최대/최소 시간 찾기
min_time = 100000
max_time = 1

for t in times:
    min_time = min(min_time, t)
    max_time = max(max_time, t)

# 매개변수 탐색
left = min_time
right = max_time

while left <= right:
    mid = (left + right) // 2

    # 결정 문제 풀기
    required_time_sectors = 0
    for t in times:
        required_time_sectors += (t + mid - 1) // mid  # ceil(t / mid)

    # 이분탐색 범위 조정
    if required_time_sectors < 2 * N: # N
        right = mid - 1
    else: # N
        left = mid + 1

print(left)

solution.py

시행착오

이분 탐색의 범위 설정 문제

앞서 결정 문제로 "임의의 시간 간격 $M$에 대해 한 칸의 시간 간격 동안에는 2개의 문제만 풀 수 있을 때, 각 문제별로 풀기 위해 필요한 시간 간격의 총 개수가 $2 \times N$을 넘지 않는가?"를 사용했다고 설명했습니다. 그 말 그대로, $2 \times N$ 이하일 때를 정답으로 보고 범위를 조정할 시 일부 입력에서 오답이 나오는 점을 확인했습니다.

따라서, 결정 문제를 "임의의 시간 간격 $M$에 대해 한 칸의 시간 간격 동안에는 2개의 문제만 풀 수 있을 때, 각 문제별로 풀기 위해 필요한 시간 간격의 총 개수가 $2 \times N$을 작은가?" ($< 2 \times N$)로 변경해 정답을 받았습니다.

while left <= right:
    mid = (left + right) // 2
    required_time_sectors = 0
    for t in times:
        required_time_sectors += (t + mid - 1) // mid  # ceil(t / mid)
    if required_time_sectors <= 2 * N: # 결정 문제의 답이 Y
        right = mid - 1
    else:
        # 결정 문제의 답이 N
        # 찾고자 하는 답은 [mid + 1, end]에 있다.
        left = mid + 1

print(left)

처음에 생각한 코드. 이대로는 오답을 받습니다.