알고리즘 풀이/백준

[백준 20056] 마법사 상어와 파이어볼

mhko411 2021. 7. 9. 13:11
728x90

문제

마법사 상어가 크기가 N×N인 격자에 파이어볼 M개를 발사했다. 가장 처음에 파이어볼은 각자 위치에서 이동을 대기하고 있다. i번 파이어볼의 위치는 (ri, ci), 질량은 mi이고, 방향은 di, 속력은 si이다. 위치 (r, c)는 r행 c열을 의미한다.

격자의 행과 열은 1번부터 N번까지 번호가 매겨져 있고, 1번 행은 N번과 연결되어 있고, 1번 열은 N번 열과 연결되어 있다.

파이어볼의 방향은 어떤 칸과 인접한 8개의 칸의 방향을 의미하며, 정수로는 다음과 같다.

7 0 1
6   2
5 4 3

마법사 상어가 모든 파이어볼에게 이동을 명령하면 다음이 일들이 일어난다.

  1. 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
    • 이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.
  2. 이동이 모두 끝난 뒤, 2개 이상의 파이어볼이 있는 칸에서는 다음과 같은 일이 일어난다.
    1. 같은 칸에 있는 파이어볼은 모두 하나로 합쳐진다.
    2. 파이어볼은 4개의 파이어볼로 나누어진다.
    3. 나누어진 파이어볼의 질량, 속력, 방향은 다음과 같다.
      1. 질량은 ⌊(합쳐진 파이어볼 질량의 합)/5⌋이다.
      2. 속력은 ⌊(합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)⌋이다.
      3. 합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고, 그렇지 않으면 1, 3, 5, 7이 된다.
    4. 질량이 0인 파이어볼은 소멸되어 없어진다.

마법사 상어가 이동을 K번 명령한 후, 남아있는 파이어볼 질량의 합을 구해보자.

 

입력

첫째 줄에 N, M, K가 주어진다.

둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다.

서로 다른 두 파이어볼의 위치가 같은 경우는 입력으로 주어지지 않는다.

 

출력

마법사 상어가 이동을 K번 명령한 후, 남아있는 파이어볼 질량의 합을 출력한다.


접근

처음에 문제를 잘못 이해했다. 4개의 파이어볼라 나눠질 때 각각 네 개의 방향으로 흩어지는 것으로 이해했다. 이후 네 개의 방향으로 흩어지는 것을 구현할 때 파이어볼의 다음 방향이 무엇이지? 라는 생각으로 문제를 다시 읽었고 그제서야 이해할 수 있었다.

문제를 읽을 때 확실히 이해한 문장만 넘어가야겠다.

 

구현해야할 것은 파이어볼 들이 동시에 이동하는 것과 이동 후에 2개 이상의 파이어볼이 같은 칸에 있을 때 4개의 파이어볼로 나누는 것이다. 동시에 움직이는 것을 구현하기 위해 새로운 2차원 리스트를 생성하여 이동이 완료된 파이어 볼을 넣고 2개 이상일 때 나눈 후에 다시 해당 자리에 넣었다.

 

위의 과정에서 메모리를 많이 사용하고 복잡해졌다. 추후에 다시한번 구현을 해봐야겠다.

 

구현

- 1차원 리스트를 원소로 갖는 2차우너 리스트를 생성하여

- 파이어볼들을 담아준다. 파이어볼은 질량 속도 방향순으로 넣어준다.

- 이어서 solve함수에서 K번 동안 파이어볼이 움직이고 흩어지는 과정이 반복된다.

N, M, K = map(int, input().split())
board = [[[] for _ in range(N)] for _ in range(N)]

for _ in range(M):
    r, c, m, s, d = map(int, input().split())
    r -= 1
    c -= 1
    board[r][c].append((m, s, d))

dy = [-1, -1, 0, 1, 1, 1, 0, -1]
dx = [0, 1, 1, 1, 0, -1, -1, -1]

answer = 0
solve(board, 0)
print(answer)

- 먼저 입력받은 board에서 파이어볼들을 이동시킨다.

- 이동시킨 파이어볼들은 move_board에 담는다.

- 모두 이동이 되었다면 move_board를 탐색하여 2개 이상의 파이어볼이 같은 위치에 있을 때 4개의 파이어볼로 나눴다.

- 모두 홀수또는 짝수일 때와 아닐 때를 구분하기 위해 홀수, 짝수의 개수를 카운트하였고 두 개의 개수가 0을 초과할 때는 1, 3, 5, 7의 방향을 갖도록 하였다.

- 이렇게 나눈 파이어볼을 다시 해당 자리에 넣는다.

- 모두 옮겼을 때 재귀호출을 진행한다.

- k가 K가 되었을 때는 파이어볼의 질량 합을 구한다.

def solve(board, k):
    global answer
    if k == K:
        for y in range(N):
            for x in range(N):
                while board[y][x]:
                    m, s, d = board[y][x].pop()
                    answer += m
        return

    move_board = [[[] for _ in range(N)] for _ in range(N)]
    for y in range(N):
        for x in range(N):
            while board[y][x]:
                m, s, d = board[y][x].pop()
                ny = (s * dy[d] + y) % N
                nx = (s * dx[d] + x) % N
                move_board[ny][nx].append((m, s, d))


    for y in range(N):
        for x in range(N):
            if len(move_board[y][x]) >= 2:
                count = 0
                odd = 0
                even = 0
                m_total = 0
                s_total = 0
                while move_board[y][x]:
                    m, s, d = move_board[y][x].pop()
                    if check_odd(d):
                        odd += 1
                    else:
                        even += 1
                    count += 1
                    m_total += m
                    s_total += s

                M = m_total // 5
                S = s_total // count
                if M == 0:
                    continue
                if odd != 0 and even != 0:
                    for nd in [1, 3, 5, 7]:
                        move_board[y][x].append((M, S, nd))
                else:
                    for nd in [0, 2, 4, 6]:
                        move_board[y][x].append((M, S, nd))

    solve(move_board, k+1)

전체 코드

import sys
input = sys.stdin.readline
sys.setrecursionlimit(10000)
def check_range(y, x):
    return (0 <= y < N) and (0 <= x < N)

def check_odd(number):
    return number % 2

def solve(board, k):
    global answer
    if k == K:
        for y in range(N):
            for x in range(N):
                while board[y][x]:
                    m, s, d = board[y][x].pop()
                    answer += m
        return

    move_board = [[[] for _ in range(N)] for _ in range(N)]
    for y in range(N):
        for x in range(N):
            while board[y][x]:
                m, s, d = board[y][x].pop()
                ny = (s * dy[d] + y) % N
                nx = (s * dx[d] + x) % N
                move_board[ny][nx].append((m, s, d))


    for y in range(N):
        for x in range(N):
            if len(move_board[y][x]) >= 2:
                count = 0
                odd = 0
                even = 0
                m_total = 0
                s_total = 0
                while move_board[y][x]:
                    m, s, d = move_board[y][x].pop()
                    if check_odd(d):
                        odd += 1
                    else:
                        even += 1
                    count += 1
                    m_total += m
                    s_total += s

                M = m_total // 5
                S = s_total // count
                if M == 0:
                    continue
                if odd != 0 and even != 0:
                    for nd in [1, 3, 5, 7]:
                        move_board[y][x].append((M, S, nd))
                else:
                    for nd in [0, 2, 4, 6]:
                        move_board[y][x].append((M, S, nd))

    solve(move_board, k+1)

N, M, K = map(int, input().split())
board = [[[] for _ in range(N)] for _ in range(N)]

for _ in range(M):
    r, c, m, s, d = map(int, input().split())
    r -= 1
    c -= 1
    board[r][c].append((m, s, d))

dy = [-1, -1, 0, 1, 1, 1, 0, -1]
dx = [0, 1, 1, 1, 0, -1, -1, -1]

answer = 0
solve(board, 0)
print(answer)