오늘 동빈이는 여행 가신 부모님을 대신해서 떡집 일을 하기로 했다. 오늘은 떡볶이 떡을 만드는 날이다. 동빈이네 떡볶이 떡은 재밌게도 떡볶이 떡의 길이가 일정하지 않다. 대신에 한 봉지 안에 들어가는 떡의 총 길이는 절단기로 잘라서 맞춰준다.
절단기에 높이 H를 지정하면 줄지어진 떡을 한 번에 절단한다. 높이가 H보다 긴 떡은 H 위의 부분이 잘릴 것이고, 낮은 떡은 잘리지 않는다.
예를 들어 높이가 19, 14, 10, 17cm인 떡이 나란히 있고 절단기를 높이를 15cm로 지정하면 자른 뒤 떡의 높이는 15, 14, 10, 15cm가 될 것이다. 잘린 떡의 길이는 차례대로 4, 0, 0, 2cm 이다, 손님은 6cm만큼의 길이를 가져간다.
손님이 왔을 때 요청한 총 길이가 M일때, 적어도 M만큼의 떡을 얻기 위해 절단기에 설정할 수 있는 높이의 최댓값을 구하는 프로그램을 작성하시오.
시간 제한: 2초, 메모리 제한: 128MB
입력
첫째 줄에 떡의 개수 N과 요청한 떡의 길이 M이 주어진다. (1 <= N <= 1000000, 1 <= M <= 2000000000)
둘째 줄에는 떡의 개별 높이가 주어진다. 떡 높이의 총합은 항상 M 이상이므로, 손님은 필요한 양만큼 떡을 사갈 수 있다. 높이는 10억보다 작거나 같은 양의 정수 또는 0이다.
4 6
19 15 10 17
출력
적어도 M만큼의 떡을 집에 가져가기 위해 절단기에 설정할 수 있는 높이의 최댓값을 출력한다.
15
적절한 높이를 찾을 때까지 이진 탐색을 수행하여 높이 H를 반복해서 조정
현재 이 높이로 자르면 조건을 만족할 수 있는가?를 확인
→ 조건의 만족 여부('예' or '아니오')에 따라 탐색 범위를 좁혀서 해결
범위를 좁힐 때는 이진 탐색의 원리를 이용해 절반씩 좁혀나가기
절단기의 높이가 0~10억까지의 정수 중 하나로 매우 큰 수
→ 선형 탐색을 수행하면 시간 초과가 날 수 있으므로 이진 탐색을 수행
큰 수를 보면 당연하다는 듯이 가장 먼저 이진 탐색을 떠올려야 함!
문제 풀이
조건: 떡을 잘랐을 때 잘린 떡의 합 >= 손님이 요구하는 떡의 길이
1) 가장 긴 떡의 길이가 19이므로, 시작점은 0 끝점은 19 중간점은 9로 설정
→ 중간점이 현재 우리가 자르고자 하는 높이 H
각각 떡에 대해서 잘린 떡의 길이가 나오게 되고, 모두 합했을 때 25로 M보다 크기 때문에 손님이 요구하는 최소한의 길이 조건을 만족 → 결과 저장
2) 높이를 더 높였을 때도 조건을 만족할 수 있는지 확인: 시작점 = 이전 중간점 + 1
시작점이 10이고 끝점이 19이므로 중간점(H)은 14가 되고, 잘린 떡의 길이를 모두 합했을 때 9로 M보다 크기 때문에 손님이 요구하는 최소한의 길이 조건을 만족 → 결과 저장
3) 높이를 더 높였을 때도 조건을 만족할 수 있는지 확인: 시작점 = 이전 중간점 + 1
시작점이 15이고 끝점이 19이므로 중간점(H)은 17이 되고, 잘린 떡의 길이를 모두 합했을 때 2로 M보다 작기 때문에 손님이 요구하는 최소한의 길이 조건을 만족하지 못함 → 결과 저장하지 않음
4) 이전 단계에서 조건을 만족하지 못했으므로 높이 줄이기: 끝점 = 이전 중간점 - 1
시작점이 15이고 끝점이 16이므로 중간점(H)은 15가 되고, 잘린 떡의 길이를 모두 합했을 때 6으로 M과 같기 때문에 손님이 요구하는 최소한의 길이 조건을 만족 → 결과 저장, 탐색 종료
과정 정리
이진 탐색을 수행해서 더이상 탐색 범위를 줄일 수 없을 때까지 시작점과 끝점의 위치를 바꿔가며 조건을 만족하는지 체크하는 방식으로 최적의 해를 구할 수 있음
중간점의 값은 시간이 지날수록 '최적화된 값'이 되기 때문에, 과정을 반복하면서 얻을 수 있는 떡의 길이의 합이 필요한 떡의 길이보다 크거나 같을 때마다 중간점의 값을 기록하면 됨
이때, 떡의 양을 줄이기 위해 절단기의 높이를 높였는데 떡의 양이 부족해질 수 있기 때문에 중간점의 값을 기록하는 것임
n, m = map(int, input().split())
a = list(map(int, input().split()))
# 시작점, 끝점 초기화
start = 0
end = max(a)
# 절단기 높이(H) 기록용
result = 0
while start <= end:
total = 0 # 잘린 떡의 길이 총합
mid = (start + end) // 2 # 중간점(H)
# 모든 떡을 높이 H로 잘랐을 때 남은 떡의 길이 계산
for x in a:
if x > mid: # 떡이 남을 때만 자를 수 있음
total += x - mid
# 남은 떡의 길이 < 요구 사항 -> 조건 만족 X
# => 높이 낮추기: 끝점 낮추기 (왼쪽 탐색)
if total < m:
end = mid - 1
# 남은 떡의 길이 >= 요구 사항 -> 조건 만족 O
# => 높이 기록 후 높이기: 시작점 높이기 (오른쪽 탐색)
else:
result = mid
start = mid + 1
print(result)
N개의 원소를 포함하고 있는 수열이 오름차순으로 정렬되어 있습니다. 이때 이 수열에서 x가 등장하는 횟수를 계산하세요. 예를 들어 수열 {1,1,2,2,2,2,3}이 있을 때 x = 2라면, 현재 수열에서 값이 2인 원소가 4개이므로 4를 출력합니다.
단, 이 문제는 시간 복잡도 O(logN)으로 알고리즘을 설계하지 않으면 '시간 초과' 판정을 받습니다.
시간 제한: 1초, 메모리 제한: 128MB
입력
첫째 줄에 N과 x가 정수 형태로 공백으로 구분되어 입력됩니다. (1 <= N <= 1,000,000, -10e9 <= x < 10e9)
둘째 줄에 N개의 원소가 정수 형태로 공백으로 구분되어 입력됩니다. (-10e9 <= 각 원소의 값 <= 10e9)
7 2
1 1 2 2 2 2 3
7 4
1 1 2 2 2 2 3
출력
수열의 원소 중에서 값이 x인 원소의 개수를 출력합니다. 단, 값이 x인 원소가 하나도 없다면 -1을 출력합니다.
4
-1
O(logN) 요구 → 일반적인 선형 탐색 불가
데이터가 정렬되어 있으므로 이진 탐색 수행 가능
원소들은 모두 정렬되어 있기 때문에, 수열 내에 x가 존재한다면 연속적으로 나열되어 있을 것으로 예상할 수 있음
특정 값이 등장하는 첫 번째 위치와 마지막 위치를 찾아 위치 차이를 계산해 문제 해결 가능
bisect 모듈 기반의 count_by_range() 함수 정의
from bisect import bisect_left, bisect_right
# 값이 [left_value, right_value]인 데이터의 개수를 반환하는 함수
def count_by_range(array, left_v, right_v):
right_idx = bisect_right(array, right_v)
left_idx = bisect_left(array, left_v)
return right_idx - left_idx
n, x = map(int, input().split())
a = list(map(int, input().split()))
# 값이 [x, x] 범위에 있는 데이터 개수 계산
count = count_by_range(a, x, x)
if count == 0:
print(-1)
else:
print(count)
구현해야 할 함수
데이터가 존재한다면 가장 첫 번째 위치를 찾는 이진 탐색 함수
데이터가 존재한다면 가장 마지막 위치를 찾는 이진 탐색 함수
위의 두 함수를 이용해 데이터의 값으로 개수를 계산하는 함수
# 정렬된 배열에서 값이 x인 데이터의 개수를 반환하는 함수
def count_by_value(array, x):
n = len(array)
# x가 처음 등장한 인덱스 계산
a = first(array, x, 0, n-1)
# 수열에 x 존재하지 않는 경우
if a == None:
return 0
# x가 마지막으로 등장한 인덱스 계산
b = last(array, x, 0, n-1)
return b - a + 1
# 처음 등장한 인덱스를 찾는 이진 탐색 함수
def first(array, target, start, end):
if start > end:
return None
mid = (start + end) // 2
# 해당 값을 가지는 원소 중에서 가장 왼쪽에 있는 경우에만 인덱스 반환
if (mid == 0 or target > array[mid - 1]) and array[mid] == target:
return mid
# 중간점의 값보다 찾고자 하는 값이 작거나 같은 경우 왼쪽 확인
elif array[mid] >= target:
return first(array, target, start, mid - 1)
# 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
else:
return first(array, target, mid + 1, end)
# 마지막 위치를 찾는 이진 탐색 메서드
def last(array, target, start, end):
if start > end:
return None
mid = (start + end) // 2
# 해당 값을 가지는 원소 중에서 가장 오른쪽에 있는 경우에만 인덱스 반환
if (mid == n-1 or target < array[mid + 1]) and array[mid] == target:
return mid
# 중간점의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
elif array[mid] > target:
return last(array, target, start, mid - 1)
# 중간점의 값보다 찾고자 하는 값이 크거나 같은 경우 오른쪽 확인
else:
return last(array, target, mid + 1, end)
n, x = map(int, input().split())
a = list(map(int, input().split()))
# 값이 [x, x] 범위에 있는 데이터 개수 계산
count = count_by_value(a, x)
if count == 0:
print(-1)
else:
print(count)
Source
이코테 2021 강의 몰아보기