라떼는말이야
[solved.ac 실버3] 18111_마인크래프트 본문
문제
팀 레드시프트는 대회 준비를 하다가 지루해져서 샌드박스 게임인 ‘마인크래프트’를 켰다. 마인크래프트는 1 × 1 × 1(세로, 가로, 높이) 크기의 블록들로 이루어진 3차원 세계에서 자유롭게 땅을 파거나 집을 지을 수 있는 게임이다.
목재를 충분히 모은 lvalue는 집을 짓기로 하였다. 하지만 고르지 않은 땅에는 집을 지을 수 없기 때문에 땅의 높이를 모두 동일하게 만드는 ‘땅 고르기’ 작업을 해야 한다.
lvalue는 세로 N, 가로 M 크기의 집터를 골랐다. 집터 맨 왼쪽 위의 좌표는 (0, 0)이다. 우리의 목적은 이 집터 내의 땅의 높이를 일정하게 바꾸는 것이다. 우리는 다음과 같은 두 종류의 작업을 할 수 있다.
- 좌표 (i, j)의 가장 위에 있는 블록을 제거하여 인벤토리에 넣는다.
- 인벤토리에서 블록 하나를 꺼내어 좌표 (i, j)의 가장 위에 있는 블록 위에 놓는다.
1번 작업은 2초가 걸리며, 2번 작업은 1초가 걸린다. 밤에는 무서운 몬스터들이 나오기 때문에 최대한 빨리 땅 고르기 작업을 마쳐야 한다. ‘땅 고르기’ 작업에 걸리는 최소 시간과 그 경우 땅의 높이를 출력하시오.
단, 집터 아래에 동굴 등 빈 공간은 존재하지 않으며, 집터 바깥에서 블록을 가져올 수 없다. 또한, 작업을 시작할 때 인벤토리에는 B개의 블록이 들어 있다. 땅의 높이는 256블록을 초과할 수 없으며, 음수가 될 수 없다.
입력
첫째 줄에 N, M, B가 주어진다. (1 ≤ M, N ≤ 500, 0 ≤ B ≤ 6.4 × 107)
둘째 줄부터 N개의 줄에 각각 M개의 정수로 땅의 높이가 주어진다. (i + 2)번째 줄의 (j + 1)번째 수는 좌표 (i, j)에서의 땅의 높이를 나타낸다. 땅의 높이는 256보다 작거나 같은 자연수 또는 0이다.
출력
첫째 줄에 땅을 고르는 데 걸리는 시간과 땅의 높이를 출력하시오. 답이 여러 개 있다면 그중에서 땅의 높이가 가장 높은 것을 출력하시오.
나의 풀이
아이디어
문제를 잘 읽어보면 답이 여러 개 나올 수 있다는 것을 유추할 수 있다.
이것을 힌트로 현재 맵의 블록 최소 높이부터 최대 높이까지 모두 확인해보는 방법을 선택했다.
n, m, b = map(int, input().split())
minH, maxH = 300, 0 # 최소 높이, 최대 높이
totalBlocks = 0 # 현재 총 블록 수
graph = [] # 맵
answer = [] # 시간, 높이
우선 n, m, b를 입력 받고, minH를 300, maxH를 0으로 초기화했다. (높이의 최대값은 256이라고 문제에 기재되어 있다. 그래서 256보다 큰 300으로 설정했다.)
graph는 맵이 저장될 리스트이다. (이중리스트)
answer은 (시간, 높이) 로 구성된 튜플 형태의 정답이 저장되는 리스트이다.
for _ in range(n):
row = list(map(int, input().split()))
graph.append(row)
totalBlocks += sum(row)
minH, maxH = min(minH, min(row)), max(maxH, max(row))
사용자에게 각 행 단위로 입력을 받는다.
graph에 append해서 맵을 만들어주고, totalBlocks에는 맵의 블록 개수를 저장하기 위해 sum으로 추가해준다.
minH와 maxH도 찾아준다.
for h in range(minH, maxH+1):
block = totalBlocks - h*n*m # 추가 블록 개수 (부족하면 음수)
if block >= 0:
# 블록이 더 많은 경우
...
else:
# 블록이 더 적은 경우
if block+b >= 0: # 블록을 채울 수 있다면
...
h를 minH~maxH까지 차례대로 증가시키며 h에서의 시간, 블록을 찾는다.
맵의 블록 수에서 (너비 * 높이)를 뺐을 때 그 값이 양수라면 블록이 충분한 것이고, 음수라면 블록이 부족한 것이다.
만약 부족한 블록 수에 인벤토리에 있던 블록의 수를 합쳤을 때 양수라면 마찬가지로 블록을 채울 수 있는 것이고, 음수라면 해당 높이에서 땅을 고르게 할 수 없는 것이다.
for h in range(minH, maxH+1):
block = totalBlocks - h*n*m # 추가 블록 개수 (부족하면 음수)
if block >= 0 or block+b >= 0:
# 블록이 더 많은 경우
...
block >= 0 인 경우와 block+b >= 0인 경우 모두 같은 동작을 행하기 때문에 위 코드처럼 한 번에 처리할 수 있다.
이제 위 코드에서 내부 구현을 하면 아래와 같다.
def flatten(graph, n, m, h):
time = 0
for r in range(n):
for c in range(m):
if graph[r][c] > h:
time += (graph[r][c] - h) * 2 # 블록 제거
elif graph[r][c] < h:
time += h-graph[r][c] # 블록 추가
return time
for h in range(minH, maxH+1):
block = totalBlocks - h*n*m # 추가 블록 개수 (부족하면 음수)
if block >= 0 or block+b >= 0: # 블록이 더 많은 경우
time = flatten(graph, n, m, h)
answer.append((time, h))
flatten 함수는 맵을 순회하며 h보다 높이 쌓였다면 h를 초과한 만큼 블록을 제거한다. 이때 소요되는 시간은 블록 당 2이므로 2를 곱해서 time에 더해준다.
h보다 낮게 쌓였다면 h에서 부족한 만큼 블록을 채워야한다. 블록을 채울 때 소요되는 시간은 블록당 1이다.
소요된 시간을 return한다.
return된 시간을 받아서 현재 높이인 h와 묶어서 answer에 추가한다.
이 과정이 완료되면 answer에는 답이 될 수 있는 후보들이 저장되어 있을 것이다.
answer = min(answer, key=lambda x: (x[0],-x[1]))
for i in answer: print(i, end=' ')
시간이 적게 걸린 것, 높이가 높은 것을 기준으로 최솟값을 구한다.
이후 이 값을 형태에 맞게 출력하면 된다.
전체 코드
def flatten(graph, n, m, h):
time = 0
for r in range(n):
for c in range(m):
if graph[r][c] > h:
time += (graph[r][c] - h) * 2 # 블록 제거
elif graph[r][c] < h:
time += h-graph[r][c] # 블록 추가
return time
''' --- [main] --- '''
n, m, b = map(int, input().split())
minH, maxH = 300, 0 # 최소 높이, 최대 높이
totalBlocks = 0 # 현재 총 블록 수
graph = [] # 맵
answer = [] # 시간, 높이
for _ in range(n):
row = list(map(int, input().split()))
graph.append(row)
totalBlocks += sum(row)
minH, maxH = min(minH, min(row)), max(maxH, max(row))
for h in range(minH, maxH+1):
block = totalBlocks - h*n*m # 추가 블록 개수 (부족하면 음수)
if block >= 0 or block+b >= 0: # 블록이 더 많은 경우
time = flatten(graph, n, m, h)
answer.append((time, h))
answer = min(answer, key=lambda x: (x[0],-x[1]))
for i in answer: print(i, end=' ')
'알고리즘 > 코딩 테스트' 카테고리의 다른 글
[solved.ac 실버3] 2579_계단 오르기 (DP 풀이) (0) | 2021.09.18 |
---|---|
[solved.ac 실버3] 1003_피보나치 함수 (DP 풀이) (0) | 2021.09.18 |
[solved.ac 실버4] 17626_Four Squares (DP풀이) (0) | 2021.09.18 |
[solved.ac 실버4] 1764_듣보잡 (0) | 2021.09.17 |
[solved.ac 실버3] 2805_나무 자르기 (0) | 2021.09.17 |
[solved.ac 실버3] 1966_프린터 큐 (0) | 2021.09.17 |
[solved.ac 실버5] 체스판 다시 칠하기 (3) | 2021.09.14 |
[프로그래머스 위클리 챌린지] 7주차_입실 퇴실 (2) | 2021.09.13 |