ProblemSolving/Binary Search

백준 2110 공유기 설치 (파이썬)

OSNIM 2022. 5. 19. 01:42
반응형

이분 탐색

문제: https://www.acmicpc.net/problem/2110

문제

도현이의 집 N개가 수직선 위에 있다. 각각의 집의 좌표는 x1, ..., xN이고, 집 여러개가 같은 좌표를 가지는 일은 없다.

도현이는 언제 어디서나 와이파이를 즐기기 위해서 집에 공유기 C개를 설치하려고 한다. 최대한 많은 곳에서 와이파이를 사용하려고 하기 때문에, 한 집에는 공유기를 하나만 설치할 수 있고, 가장 인접한 두 공유기 사이의 거리를 가능한 크게 하여 설치하려고 한다.

C개의 공유기를 N개의 집에 적당히 설치해서, 가장 인접한 두 공유기 사이의 거리를 최대로 하는 프로그램을 작성하시오.

입력

첫째 줄에 집의 개수 N (2 ≤ N ≤ 200,000)과 공유기의 개수 C (2 ≤ C ≤ N)이 하나 이상의 빈 칸을 사이에 두고 주어진다. 둘째 줄부터 N개의 줄에는 집의 좌표를 나타내는 xi (0 ≤ xi ≤ 1,000,000,000)가 한 줄에 하나씩 주어진다.

출력

첫째 줄에 가장 인접한 두 공유기 사이의 최대 거리를 출력한다.

힌트

공유기를 1, 4, 8 또는 1, 4, 9에 설치하면 가장 인접한 두 공유기 사이의 거리는 3이고, 이 거리보다 크게 공유기를 3개 설치할 수 없다.

첫 번째 제출 코드 (오답) 

n, c = map(int, input().split())
LANs = [int(input()) for _ in range(n)]
LANs.sort()
left = 0 # 각 공유기의 위치에서 떨어진 거리를 재는 기준
right = LANs[-1] #공유기 위치가 left에서 떨어진 최대 거리

while left <= right:
    # 인접한 두 공유기 사이의 최대 거리라고 가정
    mid = (left+right)//2
    cnt = 0
    cur = LANs[0] #인접한 공유기 위치
    for i in range(1, len(LANs)):
        dist = LANs[i] - cur
        # 답이라고 가정한 mid보다 크거나 같은 경우 공유기 설치 가능
        if dist >= mid:
            cnt += 1 #선택된 공유기
        else:
            cur = LANs[i] # 현재 LAN의 위치를 기준으로 다음의 LAN과의 거리를 잼
    if cnt >= c: #최대 거리를 적당하게 잡거나 짧게 잡아서 공유기를 많이 선택한 경우
        left = mid + 1
        ans = mid
    else:   #최대 거리(mid)를 길게 잡아서 공유기가 남는 경우
        right = mid -1
print(mid)

문제가 프로그래머스 징검다리 문제와 매우 유사하여 비슷하게 풀어보려고 노력했습니다.

그래서 cur를 dist가 mid보다 작을 때 초기화했습니다. 하지만 문제가 비슷하다고 계산의 과정을 차분히 생각하지 않고 문제를 풀어 계속 오답이 나왔고 점점 생각이 스스로 꼬이기 시작했습니다.

계속 오답이 나오자 질문 검색의 질문들을 통해 답을 유추할 수 있었습니다.

백준 질문 중 이분탐색을 활용하여 접근하는 방법을 간단 명료하게 알려준 답변

이분 탐색을 하기 위한 질문에 힌트를 얻어 생각의 범위를 좁혀나갈 수 있었습니다. 

두번째 제출 코드 (시간 초과)

n, c = map(int, input().split())
LANs = [int(input()) for _ in range(n)]
LANs.sort()
left = 0 #이분 탐색 범위
right = LANs[-1] #공유기 위치가 left에서 떨어진 최대 거리
ans = 0
while left <= right:
    # 인접한 두 공유기 사이의 최대 거리라고 가정
    mid = (left+right)//2
    cnt = 0
    cur = LANs[0] #인접한 공유기 위치
    for i in range(1, len(LANs)):
        dist = LANs[i] - cur
        # 답이라고 가정한 mid보다 크거나 같은 경우 공유기 설치 가능
        if dist >= mid:
            cnt += 1  # 선택된 공유기
            cur = LANs[i]
            if cnt+1 >= c:
                break
    if cnt+1 >= c: #최대 거리를 적당하게 잡거나 짧게 잡아서 공유기를 많이 선택한 경우
        left = mid + 1
        ans = mid
    else:  #최대 거리(mid)를 길게 잡아서 공유기가 남는 경우
        right = mid - 1

print(ans)

바위의 위치를 초기화하는 과정은 dist를 가정한 답 mid 보다 작은 경우가 아니라 큰 경우에 초기화해야 했습니다.

하지만 위 코드는 시간 초과가 났습니다. 제 코드가 문제가 없다고 생각했는데 시간초과가 발생하니 화가 나고 어디서부터 손 봐야 할지 막막했습니다.

하지만 문제는 간단하게 해결할 수 있었습니다.

input함수가 아닌 readline으로 빠르게 값을 가져와 시간을 절약할 수 있었습니다    

세 번째 제출 코드 (정답)

import sys
input = sys.stdin.readline
n, c = map(int, input().split())
LANs = [int(input().strip()) for _ in range(n)]
LANs.sort()
left = 0 #이분 탐색 범위
right = LANs[-1] #공유기 위치가 left에서 떨어진 최대 거리
ans = 0
while left <= right:
    # 인접한 두 공유기 사이의 최대 거리라고 가정
    mid = (left+right)//2
    cnt = 0
    cur = LANs[0] #인접한 공유기 위치
    for i in range(1, len(LANs)):
        dist = LANs[i] - cur
        # 답이라고 가정한 mid보다 크거나 같은 경우 공유기 설치 가능
        if dist >= mid:
            cnt += 1  # 선택된 공유기
            cur = LANs[i]
            if cnt+1 >= c:
                break
    if cnt+1 >= c: #최대 거리를 적당하게 잡거나 짧게 잡아서 공유기를 많이 선택한 경우
        left = mid + 1
        ans = mid
    else:  #최대 거리(mid)를 길게 잡아서 공유기가 남는 경우
        right = mid - 1

print(ans)

결과 및 정리

이분 탐색의 문제를 집중적으로 풀어보니 문제의 스타일을 파악할 수 있었습니다.

하지만 그 안의 디테일한 구현 과정은 알고리즘보다는 개인적인 코드 짬이나 실력이 요구되는 것 같습니다.

사실 이게 코테를 통과하냐 마냐를 결정짓는 가장 중요한 요소인 것 같습니다.  

요즘 코테를 주 1회 볼 정도로 자주 보고 있는데 이러한 능력이 아직 부족하여 원하는 점수를 얻지 못해 답답하지만 계속 공부하고 훈련해서 조금씩 보완해 나가야 할 것 같습니다.

 

반응형