[Algorithm] BaekJoon : 19236. 청소년 상어 by Python

엄희관·2021년 1월 18일
0

Algorithm

목록 보기
61/128
post-thumbnail

[문제 바로가기] https://www.acmicpc.net/problem/19236

📌문제 설명

아기 상어가 성장해 청소년 상어가 되었다.

4×4크기의 공간이 있고, 크기가 1×1인 정사각형 칸으로 나누어져 있다. 공간의 각 칸은 (x, y)와 같이 표현하며, x는 행의 번호, y는 열의 번호이다. 한 칸에는 물고기가 한 마리 존재한다. 각 물고기는 번호와 방향을 가지고 있다. 번호는 1보다 크거나 같고, 16보다 작거나 같은 자연수이며, 두 물고기가 같은 번호를 갖는 경우는 없다. 방향은 8가지 방향(상하좌우, 대각선) 중 하나이다.

오늘은 청소년 상어가 이 공간에 들어가 물고기를 먹으려고 한다. 청소년 상어는 (0, 0)에 있는 물고기를 먹고, (0, 0)에 들어가게 된다. 상어의 방향은 (0, 0)에 있던 물고기의 방향과 같다. 이후 물고기가 이동한다.

물고기는 번호가 작은 물고기부터 순서대로 이동한다. 물고기는 한 칸을 이동할 수 있고, 이동할 수 있는 칸은 빈 칸과 다른 물고기가 있는 칸, 이동할 수 없는 칸은 상어가 있거나, 공간의 경계를 넘는 칸이다. 각 물고기는 방향이 이동할 수 있는 칸을 향할 때까지 방향을 45도 반시계 회전시킨다. 만약, 이동할 수 있는 칸이 없으면 이동을 하지 않는다. 그 외의 경우에는 그 칸으로 이동을 한다. 물고기가 다른 물고기가 있는 칸으로 이동할 때는 서로의 위치를 바꾸는 방식으로 이동한다.

물고기의 이동이 모두 끝나면 상어가 이동한다. 상어는 방향에 있는 칸으로 이동할 수 있는데, 한 번에 여러 개의 칸을 이동할 수 있다. 상어가 물고기가 있는 칸으로 이동했다면, 그 칸에 있는 물고기를 먹고, 그 물고기의 방향을 가지게 된다. 이동하는 중에 지나가는 칸에 있는 물고기는 먹지 않는다. 물고기가 없는 칸으로는 이동할 수 없다. 상어가 이동할 수 있는 칸이 없으면 공간에서 벗어나 집으로 간다. 상어가 이동한 후에는 다시 물고기가 이동하며, 이후 이 과정이 계속해서 반복된다.

<그림 1>은 청소년 상어가 공간에 들어가기 전 초기 상태이다. 상어가 공간에 들어가면 (0, 0)에 있는 7번 물고기를 먹고, 상어의 방향은 ↘이 된다. <그림 2>는 상어가 들어간 직후의 상태를 나타낸다.

이제 물고기가 이동해야 한다. 1번 물고기의 방향은 ↗이다. ↗ 방향에는 칸이 있고, 15번 물고기가 들어있다. 물고기가 있는 칸으로 이동할 때는 그 칸에 있는 물고기와 위치를 서로 바꿔야 한다. 따라서, 1번 물고기가 이동을 마치면 <그림 3>과 같아진다.

2번 물고기의 방향은 ←인데, 그 방향에는 상어가 있으니 이동할 수 없다. 방향을 45도 반시계 회전을 하면 ↙가 되고, 이 칸에는 3번 물고기가 있다. 물고기가 있는 칸이니 서로 위치를 바꾸고, <그림 4>와 같아지게 된다.

3번 물고기의 방향은 ↑이고, 존재하지 않는 칸이다. 45도 반시계 회전을 한 방향 ↖도 존재하지 않으니, 다시 회전을 한다. ← 방향에는 상어가 있으니 또 회전을 해야 한다. ↙ 방향에는 2번 물고기가 있으니 서로의 위치를 교환하면 된다. 이런 식으로 모든 물고기가 이동하면 <그림 5>와 같아진다.

물고기가 모두 이동했으니 이제 상어가 이동할 순서이다. 상어의 방향은 ↘이고, 이동할 수 있는 칸은 12번 물고기가 있는 칸, 15번 물고기가 있는 칸, 8번 물고기가 있는 칸 중에 하나이다. 만약, 8번 물고기가 있는 칸으로 이동하면, <그림 6>과 같아지게 된다.

상어가 먹을 수 있는 물고기 번호의 합의 최댓값을 구해보자.

입력
첫째 줄부터 4개의 줄에 각 칸의 들어있는 물고기의 정보가 1번 행부터 순서대로 주어진다. 물고기의 정보는 두 정수 ai, bi로 이루어져 있고, ai는 물고기의 번호, bi는 방향을 의미한다. 방향 bi는 8보다 작거나 같은 자연수를 의미하고, 1부터 순서대로 ↑, ↖, ←, ↙, ↓, ↘, →, ↗ 를 의미한다.

출력
상어가 먹을 수 있는 물고기 번호의 합의 최댓값을 출력한다.


💡 문제 풀이

전형적인 A형 유형의 문제다.
문제는 DFS + 재귀로 해결하였다.

step 1)
변수 선언

  • tank : 상어 및 물고기가 돌아다니는 배열(4x4)
  • answer : 상어가 먹을 수 있는 물고기 번호의 합의 최대값을 담는 변수

step 2)
DFS(재귀) 함수 생성 → recurisve 함수

재귀를 통하여 각 단계마다 상어, 물고기의 위치를 변경시킬 것이므로 배열의 복사가 필요하다. → deepcopy 사용

  • copy_tank : tank 배열을 복사

다음 문제 순서대로 (청소년) 상어의 초기 위치를 세팅해준다.
※ 상어는 처음에 (0, 0)위치의 물고기를 잡아먹으면서 출발지점으로 선택한다.

  • eat : 상어가 현재 먹은 물고기 번호의 합

step 3)
상어의 위치를 정하였으면 물고기의 위치를 변경시켜준다. → move_fish 함수

1번부터 16번까지의 번호를 갖고 있는 물고기들을 방향에 맞게 이동시켜준다.
이 때, 아래의 두 가지 경우에 대해서는 조건문으로 처리해준다.
1. 4x4 배열 밖으로 나가는 경우
2. 이동시 상어의 위치가 되는 경우

위 두 가지 경우에 대해서는 45도 반시계 방향으로 회전시켜준다.

step 4)
물고기의 이동이 마치면 다음으로 상어의 방향에 존재하는 먹이(물고기)를 탐색한다.

상어 역시 배열 밖으로 벗어나지 않는 조건에서 다음 재귀를 진행한다.

step 5)
step 2) ~ step 4)의 반복을 통해서 상어는 더 이상 잡아먹을 수 없는 상황까지 탐색하게 된다.

각 단계마다 상어가 잡아먹는 물고기의 번호의 합(eat)이 answer보다 크다면 최신화 해준다.

코드는 아래와 같다.

import sys
from copy import deepcopy

dir = [(-1, 0), (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1)]
tank = []
answer = 0

def move_fish(copy_tank, num, shark_r, shark_c): # 모든 물고기의 이동
    for r in range(4):
        for c in range(4):
            if copy_tank[r][c][0] == num:
                while True:
                    nr = r + dir[copy_tank[r][c][1]][0]
                    nc = c + dir[copy_tank[r][c][1]][1]
                    if 0 <= nr < 4 and 0 <= nc < 4: # 조건1 : 4x4 배열안의 범위
                        if not(nr == shark_r and nc == shark_c): # 조건2 : 상어가 존재하지 않는 칸
                            copy_tank[r][c], copy_tank[nr][nc] = copy_tank[nr][nc], copy_tank[r][c]
                            return
                    copy_tank[r][c][1] = (copy_tank[r][c][1]+1) % 8

def recursive(tank, shark_r, shark_c, eat): # DFS + 재귀를 통해 답 도출
    global answer
    copy_tank = deepcopy(tank)
    eat += copy_tank[shark_r][shark_c][0]
    copy_tank[shark_r][shark_c][0] = 0
    for num in range(1, 17):
        move_fish(copy_tank, num, shark_r, shark_c)

    if answer < eat:
        answer = max(answer, eat)

    for m in range(1, 4): # 상어가 먹을 수 있는 물고기가 있다면 다음 재귀를 진행
        n_shark_r = shark_r + (dir[copy_tank[shark_r][shark_c][1]][0] * m)
        n_shark_c = shark_c + (dir[copy_tank[shark_r][shark_c][1]][1] * m)
        if 0 <= n_shark_r < 4 and 0 <= n_shark_c < 4 and copy_tank[n_shark_r][n_shark_c][0]:
            recursive(copy_tank, n_shark_r, n_shark_c, eat)

for r in range(4):
    n1, d1, n2, d2, n3, d3, n4, d4 = map(int, input().split())
    before = [[n1, d1-1], [n2, d2-1], [n3, d3-1], [n4, d4-1]]
    tank.append(before)

recursive(tank, 0, 0, 0)
print(answer)

너무 어이없는 실수를 했다... 😩
'현재' 상어가 있는 칸에는 물고기가 이동하면 안 된다는 조건을
상어가 지나갔던 모든 칸에는 물고기가 이동할 수 없도록 코드를 만들었다;;
주의하자 ^^

'아기 상어'문제에서 '청소년 상어'문제로 난이도가 적절하게 올라간 것 같다.

지극히 A형스러운 문제로서 재귀 + DFS(또는 BFS)를 복습하기 좋은 문제였다.

profile
허브

0개의 댓글