백준 7576번 BFS

Categories: Tags:


서론

7576번: 토마토

여전히 단계별로 문제를 푸는 중입니다. 지금은 BFS, DFS 단계인데 BFS, DFS가 알고리즘이긴 하지만 보통의 문제들은 이것을 사용해서 구현하는 문제이기 때문에 모르면 전혀 풀 수 없는 순수한 알고리즘 문제들보다는 훨씬 재밌었습니다. BFS, DFS는 그렇게 어려운 편은 아니고 시간을 들여서 풀면 다 풀 수 있는 문제들인데 이번에는 좀 새로운 유형이 나와서 기록합니다.

토마토가 익을 때 한 번의 탐색에서 탐색 된 노드들은 한 번(하루)으로 취급해야 하는 것이 핵심인데 저는 여기서 한 번의 탐색 후에 큐 자체에 구분자를 넣어서 해결했습니다.

제한 조건

BFS, DFS 문제들은 제한을 심하게 두진 않기 크게 신경 쓰지 않아도 되지만 그래도 최적화를 위해서 탐색해야 하는 graph나 map은 한 번만 훑는 것이 좋고 또 함수를 사용한다면 함수 내에 인자로 전달해서 괜히 복사하여 시간과 메모리를 잡아먹지 말고 그냥 전역변수로 사용하는 것이 좋습니다.

  • 시간제한 - 1초

시간제한은 1초입니다. 일반적인 컴퓨터로 1초에 1억 번 연산한다는 기준을 가지고 풉니다. map의 최대크기가 1,000*1,000 = 백만이기 때문에 map을 한 번만 훑으면 문제가 되지 않습니다. map을 훑으면서 BFS 함수를 호출하더라도 큰 문제는 없을 것 같습니다. 위에서 말한 것만 지켜주면 시간제한은 크게 신경 쓰지 않아도 될 것 같습니다.

  • 메모리 제한 - 256MB

메모리 제한은 256MB입니다. 데이터의 개수는 백만 개기 때문에 int 형 데이터라고 생각한다면 4MB입니다. 역시 위에서 말했던 것처럼 괜히 함수에서 복사하여 메모리 잡아먹지 말고 전역변수로 사용한다면 크게 신경 쓰지 않아도 될 것 같습니다.

BFS

일반적인 BFS 문제입니다. 토마토가 하루에 한 번씩 근접한 곳을 익게 만들어야 하니 DFS는 안되고 BFS를 사용해야 합니다. 익은 토마토를 기준으로 주변을 탐색하여 큐에 넣고 거기서부터 조건에 따라 익은 토마토를 전파하면 되는 문제입니다.

특이점으로는 탐색 한번이 문제의 행동에서의 한 번(하루)이 되는 것이 아니라 한 번의 탐색에서 큐에 넣은 노드들을 세트로 한 번(하루)으로 봐야 되기 때문에 이 부분을 해결해야 합니다. 고민하다가 탐색한 노드들을 큐에 넣고 구분자를 넣어서 세트를 끊어주기로 했습니다. 코드가 그렇게 이쁜 것 같진 않아서 다른 사람들 코드도 찾아봤는데 대부분 비슷하게 했습니다.

저 같은 경우는 큐에 bfs.append([-1,-1])을 사용하여 구분자를 넣었는데 제 코드에서 큐에 넣는 데이터 형식이 map의 [y, x]였기 때문에 구분자로 y, x로 나올 수 없는 값을 넣었습니다. 제어 할 수만 있다면 무슨 값을 넣어도 무방합니다. 전체 코드는 아래와 같습니다.

import sys
from collections import deque as dq
input = sys.stdin.readline

m,n = map(int,input().split())
map = [list(map(int,input().split())) for __ in range(n)]

def bfs(start_list):
    bfs = dq()
    ## 탐색을 시작해야 하는 출발지가 여러 개기 때문에 여러 개 넣습니다.
    for starty,startx in start_list:
        bfs.append([starty, startx])
    ## 구분자
    bfs.append([-1,-1])
    cnt = 0
    dy = [-1, 0, 1, 0]
    dx = [0, 1, 0, -1]
    while bfs:
        basey, basex = bfs.popleft()
        ## 구분자가 나올 경우 하루가 지났다고 판단합니다.
        if basey == -1 and bfs:
            cnt += 1
            bfs.append([-1,-1])
        else:
            for i in range(4):
                y = basey+dy[i]
                x = basex+dx[i]
                if (y >= 0 and y < n) and (x >= 0 and x < m) and map[y][x] == 0:
                    map[y][x] = 1
                    bfs.append([y, x])
    ## 모든 행동을 끝냈는데 map에 아직 0이 남아있다면 토마토를 다 익힐 수없는 경우입니다.
    for row in map:
        if 0 in row:
            return -1
    return cnt

start_list = []
for i in range(n):
    for j in range(m):
        if map[i][j] == 1:
            start_list.append([i,j])

print(bfs(start_list))

생각

그래도 풀면서 나름 신선했던 문제 같아서 기록해봤는데 또 그 정도 문제는 아닌 것 같습니다. BFS, DFS 문제를 빨리 쭉쭉 풀어봐야겠습니다.

*추가

같은 카테고리의 다른 문제들을 풀다 보면 더 좋은 코드가 생각나는데 이번에도 구분자를 넣는 새로운 방법이 생각나서 적습니다. 사실 큐 안에 구분자를 넣는다는 건 큐의 데이터 형식을 훼손하는 개념이었습니다. 제어할 수 있는 값이라고 해도 처음부터 [y, x]라는 좌표 데이터를 넣는다고 설계했는데 [-1,-1]을 넣게 되면서 코드가 조금 이상해지지 않았나 생각을 했습니다.

그래서 생각한 게 처음부터 [y, x]를 큐에 넣는 게 아니라 [y, x, day]를 큐에 넣는 방법입니다. 이렇게 하면 따로 구분자를 걸러내야 하는 조건 없이도 그냥 그 자체로 day 값을 가지게 됩니다. 적어 놓고 보니 BFS에서 최단 거리를 찾는 개념과 비슷합니다. 사소한 부분이고 성능에도 크게 영향을 미칠 것 같진 않지만, 코드가 더 이쁜 것 같습니다. 전체 코드는 아래와 같습니다.

import sys
from collections import deque as dq
input = sys.stdin.readline

m,n = map(int,input().split())
map = [list(map(int,input().split())) for __ in range(n)]

def bfs(start_list):
    bfs = dq()
    ## 탐색을 시작해야 하는 출발지가 여러 개기 때문에 여러 개 넣습니다.
    ## 구분자를 삭제했습니다.
    for starty,startx in start_list:
        bfs.append([starty, startx, 0])
    dy = [-1, 0, 1, 0]
    dx = [0, 1, 0, -1]

    while bfs:
        basey, basex, day = bfs.popleft()
        for i in range(4):
            y = basey+dy[i]
            x = basex+dx[i]
            if (y >= 0 and y < n) and (x >= 0 and x < m) and map[y][x] == 0:
                map[y][x] = 1
                ## 이런식으로 day값을 컨트롤합니다.
                bfs.append([y, x, day+1])
    ## 모든 행동을 끝냈는데 map에 아직 0이 남아있다면 토마토를 다 익힐 수없는 경우입니다.
    for row in map:
        if 0 in row:
            return -1
    return day

start_list = []
for i in range(n):
    for j in range(m):
        if map[i][j] == 1:
            start_list.append([i,j])

print(bfs(start_list))

BOJ내 다른 글 보기

댓글남기기