BOJ_1092 배
- TOC {:toc}
이 글은 백준 온라인 저지의 1092번 문제를 파이썬(Python)으로 풀이한 것을 모아놓은 글입니다.
일종의 연습 기록이며 제가 정답을 받은 코드와 참고할만한 다른 코드를 같이 기록합니다. 필요한 경우 코드에 대한 해설을 기록합니다만 코드는 통과했어도 해설은 틀릴 수 있기 때문에 가볍게 참고해주시길 부탁드립니다. 피드백은 편하신 방법으로 자유롭게 주시면 감사하겠습니다.
2021.07.12
메모리 | 시간 | 코드 길이 |
---|---|---|
30220 KB | 76 ms | 599 B |
단계 | 시작 시각 | 끝난 시각 | 걸린 시간 |
---|---|---|---|
문제 이해 | 12:26:12 | 12:28:02 | |
풀이 생각 1 | 12:28:41 | 12:33:00 | |
풀이 생각 2 | 15:35:55 | 15:39:54 | |
코딩 | 15:41:39 | 16:39:23 | |
디버깅 1 | 19:38:29 | 20:18:08 | |
디버깅 2 | 20:19:35 | 20:27:28 |
import sys
N = int(input())
crane = list(map(int, input().split()))
M = int(input())
boxes = list(map(int, input().split()))
crane.sort(reverse=True)
boxes.sort(reverse=True)
if boxes[0] > crane[0]:
print(-1)
sys.exit()
# 한 크레인이 박스를 옮기는 데 걸린 최대 시간
res = 0
# 각 크레인이 박스를 옮기는 데 걸린 시간
tmp_count = [0] * N
i, j = 0, 0
while i < M:
# 박스의 무게가 무게 제한 보다 작을 때
if boxes[i] <= crane[j]:
# 현재 크레인이 res만큼 박스를 옮겼으면
if tmp_count[j] == res:
# 다음 크레인으로 넘어간다.
j += 1
# 마지막 크레인이면
if j == N:
# 처음 크레인으로 넘어가고
j = 0
# res를 1 증가시킨다.
res += 1
continue
# 해당 크레인으로 박스를 옮기고
tmp_count[j] += 1
# 다음 박스로 넘어간다
i += 1
# 박스의 무게가 더 무거우면
else:
# res를 증가시키고
res += 1
# 앞의 모든 크레인으로
for k in range(j):
# 박스를 옮긴다.
tmp_count[k] += 1
# j개의 박스가 옮겨진다.
i += j
print(res)
아이디어 & 풀이
박스와 크레인 무게 제한 모두 내림차순으로 정렬한 뒤 박스의 무게와 크레인의 무게 제한을 비교한다.
- 무게 제한이 큰 크레인부터 자신이 옮길 수 있는 박스를 옮기되
- 최대한 모든 크레인이 균일한 수의 박스를 옮기는 것이 목표이다.
- 이는 한 크레인이 너무 많은 박스를 옮기지 않아야 한다는 것을 의미한다.
- 이를 위해 한 크레인이 박스를 옮기는 데 걸린 최대 시간
res
를 따로 관리한다. - 모든 크레인이 이 값보다 적거나 같은 시간 동안 박스를 옮기도록 한다.
우선 입력받자마자 박스와 크레인의 첫 값을 비교한다.
- 박스의 무게가 크레인의 최대 무게 제한보다 크면 ‘모든 박스’를 옮기는 게 불가능하므로
- -1을 출력하고
sys.exit()
을 이용해 실행을 종료한다.
박스를 옮기는 과정은 다음과 같다.
우선 현재 박스의 무게와 크레인 무게 제한을 비교한다. 박스의 무게가 무게 제한보다 작거나 같아서 박스를 옮길 수 있으면
- 현재 크레인으로 박스를 옮기고 현재 크레인이 박스를 옮기는 데 걸린 시간
tmp_count[i]
를 1 증가시킨 뒤 - 다음 박스로 이동하면 된다.
다만 박스를 옮기기 전에, 현재 tmp_count[i]
가 최대 시간보다 작은지 확인해야 한다.
- 현재 크레인이 박스를 옮기는 데 걸린 시간이 최대 시간보다 같아졌으면 해당 크레인으로 박스를 더 옮기면 안 되기 때문에
- 다음 크레인으로 넘어간다.
- 현재 크레인이 마지막 크레인이었으면
- 처음 크레인으로 이동하고
- 첫 크레인부터 다시 박스를 추가로 쌓아야 하므로
res
를 1 증가시킨다.
박스의 무게가 무게 제한보다 커서 박스를 옮길 수 없으면
- 앞의 크레인 중 하나가 이를 대신 옮겨야 한다.
- 앞의 크레인들은 이미
res
만큼 박스를 옮긴 상태이므로 이 중 하나의tmp_count
가 현재res
보다 1 늘어나게 된다. res
역시 1 늘어나게 된다.
- 앞의 크레인들은 이미
- 이 경우, 앞의 모든 크레인이 남은 박스를 1개씩 더 옮기는 것이 무거운 박스를 미리 옮기게 되므로 효율적이다.
- 정리하면,
- 우선
res
를 1 증가시키고 - 첫 크레인(
0
)부터 현재 크레인(j
)의 직전 크레인(j - 1
)까지tmp_count
를 1 증가시킨 뒤 - 그 수(
j
)만큼 다음 박스로 넘어간다.
- 우선
모든 박스를 다 옮길 때까지 위의 과정을 반복한다.
디버그
- 처음에 시간초과가 떴다.
- 케이스 만들어서 테스트해 보니 무한루프가 돌았다.
- 처음에 -1을 출력한 뒤 종료를 안 해서 다음 코드가 실행됐고, while 문에서 무한루프가 돈 것이었다.
- -1을 출력한 이후에 실행을 끝나는 코드를 추가해서 해결.
tmp_count
단일 정수로 관리했는데 처음 크레인으로 돌아갈 때tmp_count
를 0으로 초기화시킨 것 때문에 틀렸다.res
가 늘어난 만큼 현재 각 크레인의tmp_count
도 증가한 상태로 처음 크레인으로 돌아가야 하는데 0으로 초기화시킨 것.tmp_count
를 크레인 별로 관리해서 해결했다.
피드백
- 두 번째 문제점의 원인을 파악 못 해서 틀림 처리한 뒤 다른 방식으로 푼 정답을 확인해 제출했는데, 오답 정리의 느낌으로 틀린 풀이의 ‘아이디어 & 풀이’ 부분을 정리하다 보니 틀린 부분을 찾게 되었다. 앞으로도 비슷한 경우에 꼼꼼히 풀이 과정을 정리하면서 문제점을 파악해보면 좋을 것 같다.
- 참고 답안 1처럼 초깃값을 설정하면
- 모든 크레인을 돌아서 처음으로 돌아오는 과정을 없애고
- 위에서 문제가 됐던
tmp_count
도 리스트가 아니라 단일 정수 변수로 사용할 수 있다.
참고 답안 1
import sys
input = sys.stdin.readline
def solution(crane, boxes):
res = 0
if crane[0] < boxes[0]:
return -1
# 모든 크레인이 동시에 움직일 경우 최솟값
min_count = M // N if M % N == 0 else M // N + 1
res = min_count
i, j, count = 0, 0, 0
while i < M:
# 해당 크레인이 할당량을 채울 경우
if count >= res:
# 다음 크레인으로 이동한다.
j += 1
count = 0
# 박스 무게가 크레인의 무게 제한보다 클 경우
if boxes[i] > crane[j]:
# 앞의 크레인 모두가 박스를 대신 옮기고
i += j
# 목표(최대 시간)를 1 늘린다.
res += 1
continue
# 현재 크레인의 할당량 1 증가
count += 1
# 다음 박스로 이동한다.
i += 1
return res
N = int(input())
crane = list(map(int, input().split()))
M = int(input())
boxes = list(map(int, input().split()))
crane.sort(reverse=True)
boxes.sort(reverse=True)
print(solution(crane, boxes))
아이디어 & 풀이
위의 풀이와 전개는 동일하다.
M // N
혹은 M // N + 1
의 값으로 res
의 초깃값을 설정한 점이 가장 큰 차이점이다.
- 크레인이 박스를 옮기는 데 걸린 최대 시간은 모든 크레인이 거의 동일한 수의 박스를 옮길 때 최소가 된다.
- 모든 크레인이 동일한 수의 박스를 옮기면 각 크레인이 할당하는 박스의 수는
M // N
또는M // N + 1
이 된다. - 그러므로 박스를 옮기는 데 걸리는 시간은 이 값보다 작아질 수 없다.
이처럼 초깃값을 설정하고 시작하면 모든 크레인을 돌아서 처음으로 돌아오는 과정을 없애 코드를 더 간결하게 작성할 수 있다.
참고 답안 2
import sys
input = sys.stdin.readline
N = int(input())
crane = list(map(int, input().split()))
M = int(input())
box = list(map(int, input().split()))
crane.sort(reverse=True)
box.sort(reverse=True)
if box[0] > crane[0]:
print(-1)
sys.exit()
pos = [0] * N
moved = [False] * M
count = 0
res = 0
while count < M:
for i, x in enumerate(crane):
while pos[i] < M:
if not moved[pos[i]] and x >= box[pos[i]]:
moved[pos[i]] = True
count += 1
pos[i] += 1
break
else:
pos[i] += 1
res += 1
print(res)
아이디어 & 풀이
박스와 크레인 무게 제한 모두 내림차순으로 정렬한 뒤 박스의 무게와 크레인의 무게 제한을 비교하는 것은 위와 동일하다.
위와 다른 점은 박스 중점으로 순회하는 것이 아니라 ‘각 크레인’ 순회하면서 현재 남은 박스 중 옮길 수 있는 것을 옮긴다.
- 이를 위해 각 크레인이 옮기려는 박스의 번호(
box
의 인덱스)를 나타내는pos
리스트와box
의 인덱스 번째 원소가 해당 박스의 무게box[pos[크레인 인덱스]]
가 옮기려는 박스의 무게이다.
- 각 박스가 옮겨졌는지 아닌지를 나타내는
moved
리스트를 관리한다.
박스를 옮기는 과정은 다음과 같다.
크레인을 순회하면서 각 크레인과 그 무게 제한에 대해 다음을 반복한다.
- 박스를 처음부터 순회하면서
- 현재 가리키는 박스가 아직 옮겨지지 않았고, 현재 크레인의 무게 제한보다 그 박스의 무게가 작거나 같은 경우 해당 박스를 옮긴다.
- 해당 박스의
moved
를True
로 바꾸고 - 옮긴 박스의 수
count
를 1 증가시킨 뒤 - 다음 박스로 넘어간 후
break
한다.
- 해당 박스의
- 위의 조건을 만족하지 않으면 다음 박스로 넘어간다.
- 옮기려는 박스의 번호를 1씩 증가시키는 것이므로 현재
pos
값을 1 올리면 된다.
- 옮기려는 박스의 번호를 1씩 증가시키는 것이므로 현재
모든 크레인을 순회하면 모든 크레인이 1분 동안 박스를 옮긴 것이므로 걸린 시간인 res
를 1 증가시킨다.
옮긴 박스의 수 count
가 전체 박스의 수가 될 때까지 반복한다.
참고 답안 3
import sys
input = sys.stdin.readline
N = int(input())
cranes = sorted(map(int, input().split()), reverse=True)
M = int(input())
boxes = sorted(map(int, input().split()), reverse=True)
if cranes[0] < boxes[0]:
print(-1)
sys.exit()
def move(count):
# 크레인의 수에 count를 곱한 값이
# 박스 수보다 적으면
# 해당 크레인이 count 번 옮겨서는 박스를 모두 옮길 수 없기 때문에
if N * count < M:
# False를 반환
return False
# 첫 번째 크레인부터 M // count 번째 크레인까지
for i in range(1, M // count):
# i번째 크레인의 무게 제한이 i * count 번째 박스의 무게보다 작으면
if boxes[i * count] > cranes[i]:
# False를 반환
return False
# 그 외면 True를 반환
return True
def binary_search(fnc, start, end):
while start <= end:
mid = (start + end) // 2
if fnc(mid):
ans = mid
end = mid - 1
else:
start = mid + 1
return ans
print(binary_search(move, 1, M))
아이디어 & 풀이
각 크레인이 동일한 횟수로 박스를 옮긴다고 가정하고 이진 탐색으로 모든 박스를 옮길 수 있는 횟수를 찾는다.
이진 탐색에서는 특정 함수가 반환한 결과를 기준으로 start
, end
를 재설정한다.
- 함수 실행의 결과가
True
이면end
를mid
앞으로 가져와 더 작은 값의 범위에서 탐색하고 False
이면start
를mid
뒤로 가져가 더 큰 값의 범위에서 탐색한다.- 가장 일반적인 이진 탐색에서는 주어진 값과 해당 값을 단순 비교하는 것이 이에 해당한다.
이 문제에서는 move
함수를 정의해 이용한다.
- 모든 크레인이 동일한 횟수로 박스를 옮긴다는 가정하에 각 크레인이 박스를 옮기는 횟수
count
를 받아온다.- 이진 탐색에서
mid
값을 이 인자로 넘긴다.
- 이진 탐색에서
move
함수가 동작하는 방식은 다음과 같다.
우선 크레인의 수 N
에 count
를 곱한 값과 박스의 수 M
을 비교한다.
N * count
는 모든 크레인이 옮기게 되는 박스의 총 개수로- 이 값이
M
보다 작으면 해당count
로는 모든 박스를 옮길 수 없다. - 그러므로
False
를 반환해count
를 더 큰 값의 범위에서 탐색해야 한다.
다음으로, 각 크레인이 분담한 박스의 무게를 감당할 수 있는지 확인해야 한다.
- 각 크레인은 동일한 수(i.e.,
count
)의 박스를 옮긴다. - 크레인과 박스 모두 내림차순으로 정리되어 있기 때문에 앞의 크레인부터 가장 무거운
count
개의 박스를 옮기는 것이 효율적이다.- 어차피 각자
count
개를 옮겨야 하면 무게 제한이 가장 큰 크레인이 가장 무거운 박스count
개를 옮기는 게 제한에 걸릴 가능성이 작다.
- 어차피 각자
- 그러면 각 크레인이 옮기게 되는 가장 무거운 박스의 무게는
boxes[i * count]
이다.i
번째 크레인이 옮기게 되는 박스의 번호는i * count
~(i + 1) * count - 1
이다.- e.g., 0번 크레인이
0
~count - 1
까지count
개의 박스를 옮김.
- e.g., 0번 크레인이
boxes
가 내림차순으로 정렬되어 있으므로 가장 첫 번째 박스가 가장 무겁다.
- 즉, 크레인을 순회하면서
i
번째 크레인의 무게 제한이i * count
번째 박스의 무게보다 작으면 해당count
로는 박스를 전부 옮길 수 없다. - 그러므로 역시
False
를 반환한다.
그 외는 해당 count
로 박스를 모두 옮길 수 있기 때문에 True
를 반환한다.
참고 답안 4
# 풀이 1-1
N = int(input())
crane = list(map(int, input().split()))
M = int(input())
boxes = list(map(int, input().split()))
crane.sort(reverse=True)
boxes.sort(reverse=True)
count = [0 for _ in range(N)]
if boxes[0] > crane[0]:
print(-1)
else:
# 각 box에 대해서
for box in boxes:
idx = 0
for i in range(N):
# 박스 무게가 현재 크레인의 무게 제한보다 작거나 같고
# 현재 크레인이 옮긴 박스의 수가 타깃 인덱스 번쨰 크레인에 비해 작으면
if box <= crane[i] and count[idx] > count[i]:
# 타깃 인덱스를 현재로 바꾼다.
idx = i
# 박스 무게가 현재 크레인의 무게 제한보다 크면
elif box > crane[i]:
# 크레인 탐색을 끝낸다.
break
# 타깃(현재) 인덱스의 크레인이 옮긴 박스의 수를 증가시킨다.
count[idx] = count[idx] + 1
print(max(count))
# 풀이 1-2
import sys
from heapq import heappush, heappop
input = sys.stdin.readline
def solve(cranes, boxes):
if max(cranes) < max(boxes):
return -1
# 크레인이 옮긴 박스의 수도 같이 관리할 heap
# 1-1의 count 역할
crane_heap = []
for box in boxes:
# 현재 박스보다 무게 제한이 큰 크레인들을
while cranes and cranes[-1] >= box:
# count를 0으로 crane_heap에 넣는다.
heappush(crane_heap, (0, cranes.pop()))
# count가 최소이면서
# 동일 count에서 무게 제한이 가장 작은 크레인을 pop 한 뒤
count, crane = heappop(crane_heap)
# count를 1 증가 시켜 다시 넣는다.
heappush(crane_heap, (count + 1, crane))
# crane_heap에서 최대 count 값을 갖는 원소의
# 첫 번째 값(count)를 반환한다.
return max(crane_heap)[0]
input()
cranes = list(map(int, input().split()))
input()
boxes = list(map(int, input().split()))
cranes.sort()
boxes.sort(reverse = True)
print(solve(cranes, boxes))
아이디어 & 풀이
박스를 순회하면서 해당 박스를 옮길 수 있으면서, 지금까지 가장 적은 박스를 옮긴 크레인을 찾는다.
- 각 크레인이 옮긴 박스의 수를 담은
count
를 먼저 정의한다. - 내림차순으로 정렬한 박스를 순회하면서 각 박스를 옮기기에 가장 적절한 크레인을 찾아
idx
값을 해당 크레인의 번호로 지정한다. count[idx]
의 값을 1 증가시키고- 다음 박스로 넘어간다.
적절한 idx
값을 찾는 과정은 다음과 같다.
idx
를 0으로 초기화시킨다.- 각 크레인을 순회하면서 조건에 맞는 크레인의 번호(
i
)를idx
로 지정한다.- 박스 무게는 현재 크레인의 무게 제한보다 작거나 같고
- 현재 크레인이 옮긴 박스의 수가 현재
idx
크레인이 옮긴 박스의 수보다 작아야 한다.
- 크레인을 순회하면서 최대한 뒤쪽의(무게 제한이 작은)
idx
를 찾는다. - 해당 크레인의 무게 제한이 박스의 무게보다 작아지면 순회를 종료한다.
현재 idx
의 count
값을 1 증가시킨 뒤 다음 박스에 대해서도 위의 과정을 반복한다.
마지막으로 count
중 최댓값을 출력하면 된다.
풀이 1-2부터는 전개 자체는 비슷한데 가장 적은 박스를 옮긴 크레인을 찾고, count
를 관리하는 방식이 조금씩 다르다.
- 풀이 1-2: 힙을 이용한다.
- 현재 박스의 무게를 감당할 수 있는 크레인만 힙에 집어넣은 뒤
box
는 내림차순 정렬되어있기 때문에 앞의box
에서 넣은 크레인은 현재box
의 무게도 감당할 수 있다.
- 힙 안의 원소 중에서
count
값이 가장 작은 원소를 pop 해count
1 증가 시켜 다시 넣는다.
- 현재 박스의 무게를 감당할 수 있는 크레인만 힙에 집어넣은 뒤
-
ps-python