PS/구현

[백준] No.23288 주사위 굴리기 2 01

_빌런 2023. 8. 28. 20:28

 

 

23288번: 주사위 굴리기 2

크기가 N×M인 지도가 존재한다. 지도의 오른쪽은 동쪽, 위쪽은 북쪽이다. 지도의 좌표는 (r, c)로 나타내며, r는 북쪽으로부터 떨어진 칸의 개수, c는 서쪽으로부터 떨어진 칸의 개수이다. 가장 왼

www.acmicpc.net

상어 초등학교와 같은 빡구현 문제다.

빡구현 문제도 여러 번 풀어봐야 다른 문제나 프로젝트에서도 능숙하게 코딩할 수 있다고 생각하긴 한다.

흔히 말하는 맨 땅에 헤딩을 많이 해봐야 실력이 느는 느낌이다.

 

근데 이번 문제는 해석하는데 굉장히 오래 걸렸다... 뭔가 적혀있는 게 여러 해석의 여지가 있다고 할까.

전개도가 있다고 할 때 어떤 식으로 접어야 하는지,

동쪽을 바라보는 방향이 놓여져 있다는 것이 주사위의 입장인지 바라보는 사람 입장인지,

회전할 때 이동 방향만 바뀌는 건지 주사위도 같이 회전하는 건지,

동서남북 방향으로 연속해서 이동할 수 있는 칸의 수는 대체 뭘 의미하는 건지...

 

 

해석을 해서 예제 입력 1을 실제로 그리면 위와 같은 상황이 된다.

전개도는 위의 주사위의 형태로 접히고, 3도 위의 그림처럼 놓인다.

주사위는 굴러가기만 할 뿐, 실제로 회전하지는 않는다. 위의 그림을 생각하면서 코드 작성을 해보자. 

 

# ---------- Import ----------
import sys
input = sys.stdin.readline

# ---------- Function ----------
def check_range():
    return

def roll_dice(dir):
    return

def change_direction(dir, dice_num, map_num):
    return

def BFS(x, y, value):
    return

# ---------- Main ----------
row, col, move = map(int, input().split())
graph = [list(map(int, input().split())) for _ in range(row)]

for _ in range(move):
    check_range()

    dice_map = roll_dice(cur_dir)

    answer += BFS(x, y, graph[x][y])

    cur_dir = change_direction(cur_dir, dice_map[-1], graph[x][y])
    
print(answer)

이런 문제를 풀 때, 전체적인 흐름을 파악하는 게 굉장히 중요하다.

문제를 순서대로 따라가다보면, 주사위가 이동하고 범위를 벗어났는지 확인한다.

주사위의 전개도도 바꾸고,  방향 전환도 하면서, BFS로 점수 탐색도 해줘야 한다.

각각에 해당하는 함수와 Main에 해당하는 수도 코드를 작성해준다.

 

# ---------- Main ----------
# UP, RGT, DN, LFT
dx = [-1, 0, 1, 0]
dy = [0, 1, 0, -1]
    
# UP, RGT, DN, LFT, dir = [0, 1, 2, 3]
cur_dir = 1

answer = 0
x, y = 0, 0
dice_map = [2, 4, 1, 3, 5, 6]

Main에서 우선 여러 잡다한 변수들을 선언해준다.

BFS에서 탐색에서 사용할 dx, dy 방향 변수를 선언해준다.

현재 방향을 상 우 하 좌 순서대로 0 1 2 3으로 선언한다. 이때 0123 순서는 dx, dy와 같게 한다.

또한 정답을 담을 answer, 현재 주사위 위치를 나타낼 x, y 변수도 선언한다.

마지막으로 현재 주사위 전개도를 위에서부터 아래로, 왼쪽에서부터 오른쪽으로 적은 list를 선언한다.

 

# ---------- Function ----------
def check_range():
    global cur_dir, x, y

    if x < 0 or x >= row:
        cur_dir = (cur_dir + 2) % 4
        x += dx[cur_dir] * 2

    if y < 0 or y >= col:
        cur_dir = (cur_dir + 2) % 4
        y += dy[cur_dir] * 2

범위를 바로잡아주는 check_range()부터 하나씩 구현해보자.

x와 y 각각에 대해서 0보다 작고 row와 col보다 크거나 같은지 확인한다.

만약 범위를 벗어났다면, 방향을 반대로 바꿔주고, 바꾼 방향으로 2칸 움직인다.

이렇게 구현한 이유는 check_range()를 실제로 움직인 다음에 검사하기 때문이다.

 

예를 들어 (0, 0)에서 왼쪽으로 한 칸 간 (0, -1)이라고 가정해보자.

그럼 방향을 오른쪽으로 바꿔주고((cur_dir + 2) % 4), 2칸 이동해야 한다.

위에서 이미 1칸을 범위 밖으로 이동했기 때문이다.

 

(cur + 2) % 4가 반대 방향으로 가는 이유는 %는 나머지 연산, 즉 MOD 연산이기 때문이다.

방향은 0, 1, 2, 3 중에 하나이다. 이는 4의 나머지들로만 이루어져있다.

따라서 2칸 이동하고 4로 나눈 나머지라는 건, 0 -> 2, 1-> 3, 2-> 0, 3 -> 1로 바뀐다는 것이다.

 

# ---------- Function ----------
def roll_dice(dir: int) -> list:
    new_map = [0] * 6

    if dir == 0:
        new_map[0] = dice_map[2]; new_map[1] = dice_map[1]; new_map[2] = dice_map[4]
        new_map[3] = dice_map[3]; new_map[4] = dice_map[5]; new_map[5] = dice_map[0]
        return new_map

    elif dir == 1:
        new_map[0] = dice_map[0]; new_map[1] = dice_map[5]; new_map[2] = dice_map[1]
        new_map[3] = dice_map[2]; new_map[4] = dice_map[4]; new_map[5] = dice_map[3]
        return new_map

    elif dir == 2:
        new_map[0] = dice_map[5]; new_map[1] = dice_map[1]; new_map[2] = dice_map[0]
        new_map[3] = dice_map[3]; new_map[4] = dice_map[2]; new_map[5] = dice_map[4]
        return new_map

    else:
        new_map[0] = dice_map[0]; new_map[1] = dice_map[2]; new_map[2] = dice_map[3]
        new_map[3] = dice_map[5]; new_map[4] = dice_map[4]; new_map[5] = dice_map[1]
        return new_map

주사위 전개도를 바꾸는 roll_dice() 함수이다.

이부분에서는 규칙을 찾으려고 했으나, 하드 코딩으로 짜는 게 오히려 직관적이라 생각했다.

실제로 S-DES라고 DES를 간단하게(Simple) 만든 버전이 있다.

이때 값을 바꿔주는 Permutation(치환) 알고리즘 부분이 존재한다.

보통 S-DES는 6bit에서 10bit로 치환 부분은 하드 코딩해준다. 거기서 착안하여 이 부분은 하드코딩 해주었다.

위에 값이 어떻게 바뀌는지는 직접 주사위를 굴려가면서 확인해보자.

 

# ---------- Function ----------
def change_direction(dir: int, dice_num: int, map_num: int) -> int:
    if dice_num == map_num:
        return dir

    elif dice_num > map_num:
        dir += 1; dir %= 4
        return dir

    else:
        dir -= 1; dir %= 4
        return dir

방향을 바꾸는 change_direction() 함수이다. 이 함수는 생각보다 간단하다.

같다면 방향(dir)을 그대로 return하고, 주사위가 크다면 +1, 작다면 -1을 하고 return한다.

이때 %가 MOD 연산 결과가 들어간다는 것만 알면 간단하게 구현할 수 있다.

 

# ---------- Import ----------
from collections import deque

# ---------- Function ----------
def BFS(x: int, y: int, value: int) -> int:
    total = value
    visited = [[0] * col for _ in range(row)]

    queue = deque([(x, y)])
    visited[x][y] = 1

    while queue:
        x, y = queue.popleft()

        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            if 0 <= nx < row and 0 <= ny < col and visited[nx][ny] == 0 and graph[nx][ny] == value:
                    visited[nx][ny] = 1
                    queue.append((nx, ny))
                    total += value

    return total

이제껏 DFS와 BFS에 대한 그래프 문제를 착실히 풀어왔다면, 이만큼 쉬운 것 없을 것이다.

deque로 방문 여부(visited)를 확인하면서 지도 값(value)와 같은 부분을 전부 탐색한다.

BFS에서 한 가지 주의할 점은 visited[nx][ny] = 1 부분을 if문에서 셀 때, 바꿔주자는 것이다.

그렇지 않고 x, y = queue.popleft()에서 세준다면, 중복 탐색의 경우가 생겨 TLE나 MLE가 뜰 수 있다.

 

# ---------- Import ----------
import sys
input = sys.stdin.readline

# ---------- Main ----------
for _ in range(move):    
    # Move
    x += dx[cur_dir]
    y += dy[cur_dir]
    
    # Check bound of range
    check_range()
    
    # Rolling dice
    dice_map = roll_dice(cur_dir)
    
    # Add score
    answer += BFS(x, y, graph[x][y])
    
    # Check direction change
    cur_dir = change_direction(cur_dir, dice_map[-1], graph[x][y])
    
print(answer)

위에서 작성한 함수들을 대단결할 차례이다.

현재 이동 방향(cur_dir)에 따라 이동해주고, 범위을 벗어났는지 확인(check_range())한다.

주사위가 굴러가고(roll_dice()) 점수 계산(BFS())을 한다.

마지막으로 방향을 바꿔주는(change_direction()) 과정을 이동 횟수(move)만큼 반복한다.

 

다행히도 이번 문제도 첫 시도만에 AC를 받았다.

하지만... 문제를 해석하고 이해하는데는데 거의 1시간을 투자했다.

평소 이해력과 문해력이 높으면 높았지 낮다고 생각하는 경우는 드물었는데 흠...

오히려 너무 많은 경우를 생각하는 것이 독이 된 것 같다는 느낌을 받았다.

적당한 선에서 너무 말도 안 되는 경우는 알아서 잘 걸러서 생각해야겠다.