https://www.acmicpc.net/problem/23290
23290번: 마법사 상어와 복제
첫째 줄에 물고기의 수 M, 상어가 마법을 연습한 횟수 S가 주어진다. 둘째 줄부터 M개의 줄에는 물고기의 정보 fx, fy, d가 주어진다. (fx, fy)는 물고기의 위치를 의미하고, d는 방향을 의미한다. 방향
www.acmicpc.net
문제
- 4 × 4 크기의 격자. 격자의 가장 왼쪽 윗 칸은 (1, 1)
- 물고기는 M마리, 8가지의 이동방향(상하좌우, 대각선) 중 하나를 가지고 있다.
- 상어는 1마리, 4가지의 이동방향(상하좌우) 중 하나를 가지고 있다.
- 둘 이상의 물고기가 같은 칸에 있을 수도 있으며, 마법사 상어와 물고기가 같은 칸에 있을 수도 있다.
상어의 마법 연습 한 번은 다음과 같은 작업이 순차적으로 이루어진다. S번 연습을 모두 마쳤을때, 격자에 있는 물고기의 수는?
- 상어가 모든 물고기에게 복제 마법을 시전한다. 복제 마법은 시간이 조금 걸리기 때문에, 5번에서 물고기가 복제된다.
- 모든 물고기가 한 칸 이동한다. 상어가 있는 칸, 물고기의 냄새가 있는 칸, 격자의 범위를 벗어나는 칸으로는 이동할 수 없다. 각 물고기는 자신이 가지고 있는 이동 방향이 이동할 수 있는 칸을 향할 때까지 방향을 45도 반시계 회전시킨 후 이동한다.
- 상어가 연속해서 3칸 이동한다. 상어는 현재 칸에서 상하좌우로 인접한 칸으로 이동할 수 있다. 연속해서 이동하는 칸 중 격자 범위를 벗어나면 해당 방법은 불가능하다. 이동하는 중 물고기가 있는 칸을 지나면 해당 물고기는 사라지고, 냄새를 남긴다. 가능한 이동 방법 중 없애는 물고기가 가장 많은 방법으로 이동하고, 여러개면 사전 순으로 앞선 방법을 택한다.
- 4^3 = 64가지 방법을 사전 순으로 나열해보면 [상, 상, 상], [상, 상, 좌], [상, 상, 하], [상, 상, 우], [상, 좌, 상], [상, 좌, 좌], [상, 좌, 하], [상, 좌, 우], [상, 하, 상], ..., [우, 하, 하], [우, 하, 우], [우, 우, 상], [우, 우, 좌], [우, 우, 하], [우, 우, 우] 이다.
- 두 번 전 연습에서 생긴 물고기의 냄새가 격자에서 사라진다.
- 1에서 사용한 복제 마법이 완료된다. 모든 복제된 물고기는 1에서의 위치와 방향을 그대로 갖게 된다.
아이디어
시뮬레이션 문제로, 1부터 5단계를 차례로 구현한다. 격자이므로 일단 2차원 배열, 각 칸에 물고기가 여러 마리 있을 수 있으므로 배열로 생각하여 3차원 배열로 초기화한다.
- 복제 마법 시전 : 3차원 배열이므로 deepcopy를 활용한다.
- 물고기 이동 : 물고기의 다음 위치를 확인하여 이동할 수 있으면 이동한다. 이 때, 상어가 있는 칸, 물고기의 냄새가 있는 칸, 격자의 범위를 벗어나는 칸은 분기처리하여 이동하지 못 하도록 하고 45도 회전시킨다.
- 상어 이동 : 무조건 3칸을 이동해야한다. 사전 순으로 나열한 것을 보면 상-좌-하-우 순으로 우선순위가 높은 것을 알 수 있다. 따라서, [상상상], [상상좌], [상상하] ... [우우우] 순으로 확인하면서, 이전에 제외시킨 물고기 수 보다 현재 제외시킨 물고기 수가 많을 때 이동 경로를 갱신한다. 단, 하나라도 경로 밖이면 제외시킨다.
- [상상상] 일 때 2마리, [상상좌] 일 때 1마리면 갱신하지 말고 [상상상] 그대로
- [상상상] 일 때 2마리, [상상좌] 일 때 3마리면 갱신하여 [상상좌]
- 해당 부분 코드
더보기더보기
# 3. 상어 세 칸 이동 def shark_move(s): # i, j, k 방향으로 상어가 이동할 때 잡아먹는 물고기 수 def shark_move_ijk(r, c, i, j, k): fish_cnt = len(matrix[r][c]) matrix_check = [[0]*4 for _ in range(4)] for DIR in [i, j, k]: nr = r + shark_dr[DIR] nc = c + shark_dc[DIR] if 0 <= nr < 4 and 0 <= nc < 4: # 이미 지나온 곳이면 가지 않는다. if matrix_check[nr][nc]: pass # 처음 가는 곳이면 방문 표시 + 물고기 잡아먹기 else: matrix_check[nr][nc] = 1 fish_cnt += len(matrix[nr][nc]) r, c = nr, nc # [i, j, k] 중 하나라도 격자 범위 바깥이면 out else: return -1 return fish_cnt # 상어의 위치 r, c = sr, sc # 최종적으로 잡아먹는 물고기 수 fish_cnt, 상어 이동방향 ijk result_fish_cnt = 0 result_ijk = -1 # 방향 지정 => i, j, k 중 방향 하나 픽스하기 for i in range(4): for j in range(4): for k in range(4): fish_cnt = shark_move_ijk(r, c, i, j, k) # ijk가 초기값(-1)이면 무조건 한 번 갱신해준다. if result_ijk == -1 and fish_cnt != -1: result_ijk = (i, j, k) # fish_cnt가 더 높은 ijk를 찾으면 새로 갱신 if result_fish_cnt < fish_cnt: result_fish_cnt = fish_cnt result_ijk = (i, j, k) # 최종적으로, 픽스된 ijk로 상어가 이동한다. for DIR in result_ijk: nr = r + shark_dr[DIR] nc = c + shark_dc[DIR] if matrix[nr][nc]: # 해당 matrix에 물고기를 잡아먹었으므로 [] matrix[nr][nc] = [] # s번째에 냄새가 남기 때문에 fish_smell에 s를 추가 fish_smell[nr][nc].append(s) r, c = nr, nc return r, c
- 냄새 삭제 : 보통 조건문을 돌면서 S번 확인하기 때문에 물고기 냄새를 기록했던 순간이 몇 번째인지 기록해두고, 그로부터 2번째 이전 냄새를 없앤다.
- while S: 로 시작해서 s번째 냄새를 기록해두었다면, s+2번째 냄새를 삭제하면 된다.
- 복제 마법 완료 : 1번에서 저장해둔 배열을 현재 진행하고 있는 배열에 합친다.
구현
import sys
sys.stdin = open("input.txt", "r")
from copy import deepcopy
dr = [0, -1, -1, -1, 0, 1, 1, 1]
dc = [-1, -1, 0, 1, 1, 1, 0, -1]
# 상 좌 하 우
shark_dr = [-1, 0, 1, 0]
shark_dc = [0, -1, 0, 1]
# 2. 물고기 한 칸 이동
def fish_move():
# 물고기가 해당 방향으로 이동할 수 있는지 확인하는 함수
def fish_can_move(nr, nc):
# 격자의 범위를 벗어나는 칸
if not (0 <= nr < 4 and 0 <= nc < 4):
return False
# 상어가 있는 칸
if nr == sr and nc == sc:
return False
# 물고기의 냄새가 있는 칸
if fish_smell[nr][nc]:
return False
return True
new_matrix = [[[] for _ in range(4)] for _ in range(4)]
for r in range(4):
for c in range(4):
# 기존 matrix에 있는 물고기들 확인
while matrix[r][c]:
d = matrix[r][c].pop()
for i in range(8):
# 방향 설정
nd = (d - i) % 8
nr = r + dr[nd]
nc = c + dc[nd]
# 해당 방향으로 이동할 수 있으면 새로운 matrix에 추가
if fish_can_move(nr, nc):
new_matrix[nr][nc].append(nd)
break
# 이동할 수 없으면 이동하지 않는다.
else:
new_matrix[r][c].append(d)
return new_matrix
# 3. 상어 세 칸 이동
def shark_move(s):
# i, j, k 방향으로 상어가 이동할 때 잡아먹는 물고기 수
def shark_move_ijk(r, c, i, j, k):
fish_cnt = len(matrix[r][c])
matrix_check = [[0]*4 for _ in range(4)]
for DIR in [i, j, k]:
nr = r + shark_dr[DIR]
nc = c + shark_dc[DIR]
if 0 <= nr < 4 and 0 <= nc < 4:
# 이미 지나온 곳이면 가지 않는다.
if matrix_check[nr][nc]:
pass
# 처음 가는 곳이면 방문 표시 + 물고기 잡아먹기
else:
matrix_check[nr][nc] = 1
fish_cnt += len(matrix[nr][nc])
r, c = nr, nc
# [i, j, k] 중 하나라도 격자 범위 바깥이면 out
else:
return -1
return fish_cnt
# 상어의 위치
r, c = sr, sc
# 최종적으로 잡아먹는 물고기 수 fish_cnt, 상어 이동방향 ijk
result_fish_cnt = 0
result_ijk = -1
# 방향 지정 => i, j, k 중 방향 하나 픽스하기
for i in range(4):
for j in range(4):
for k in range(4):
fish_cnt = shark_move_ijk(r, c, i, j, k)
# ijk가 초기값(-1)이면 무조건 한 번 갱신해준다.
if result_ijk == -1 and fish_cnt != -1:
result_ijk = (i, j, k)
# fish_cnt가 더 높은 ijk를 찾으면 새로 갱신
if result_fish_cnt < fish_cnt:
result_fish_cnt = fish_cnt
result_ijk = (i, j, k)
# 최종적으로, 픽스된 ijk로 상어가 이동한다.
for DIR in result_ijk:
nr = r + shark_dr[DIR]
nc = c + shark_dc[DIR]
if matrix[nr][nc]:
# 해당 matrix에 물고기를 잡아먹었으므로 []
matrix[nr][nc] = []
# s번째에 냄새가 남기 때문에 fish_smell에 s를 추가
fish_smell[nr][nc].append(s)
r, c = nr, nc
return r, c
# 4. 물고기 냄새 사라짐
def fish_smell_remove(s):
for r in range(4):
for c in range(4):
left_smell = []
while fish_smell[r][c]:
tmp = fish_smell[r][c].pop()
# s가 아닌 것들은 left_smell에 남기기
if tmp != s:
left_smell.append(tmp)
fish_smell[r][c] = left_smell
M, S = map(int, input().split())
matrix = [[[] for _ in range(4)] for _ in range(4)]
for _ in range(M):
fr, fc, d = map(int, input().split())
matrix[fr-1][fc-1].append(d-1)
sr, sc = map(int, input().split())
sr -= 1; sc -= 1
fish_smell = [[[] for _ in range(4)] for _ in range(4)]
while S:
# 1. 복제 마법 시전
replication_matrix = deepcopy(matrix)
# 2. 물고기 이동
matrix = fish_move()
# 3. 상어 이동해서 위치 변경 => 인자 S는 S번째 냄새 기록용
sr, sc = shark_move(S)
# 4. 물고기 냄새 사라짐 => 인자 S+2는 S+2번째 냄새 삭제용
fish_smell_remove(S+2)
# 5. 복제 마법 완료
for r in range(4):
for c in range(4):
while replication_matrix[r][c]:
tmp = replication_matrix[r][c].pop()
matrix[r][c].append(tmp)
S -= 1
# print(DataFrame(matrix))
result = 0
for r in range(4):
for c in range(4):
result += len(matrix[r][c])
print(result)
처음에 놓쳐서 틀렸었던 부분
- 상어가 세 칸 이동할 때, result_fish_cnt = 0, result_ijk = -1로 초기화 해두었다. 그런데, 상어가 64가지의 방향으로 이동하는 경우에서 모든 fish_cnt가 0 이면 ijk가 한 번도 갱신되지 않는다는 문제가 있었다. 이를 해결하기 위해 result_ijk = -1 로 초기값이면 격자 범위 내일때 최소한 한 번 갱신하도록 했다.
'APS > BOJ' 카테고리의 다른 글
[Python / BOJ 12865] 평범한 배낭 (0) | 2022.01.24 |
---|---|
[Python / BOJ 3151] 합이 0 (0) | 2022.01.17 |
[Python / BOJ 23291] 어항 정리 (0) | 2021.12.14 |
[Python / BOJ 17779] 게리맨더링2 (0) | 2021.10.17 |
[Python / BOJ 17143] 낚시왕 (0) | 2021.10.17 |