본문 바로가기
알고리즘 PS (백준)/🐍 Python (파이썬)

[백준 14502] 연구소 - 파이썬(Python) BFS 풀이

by 코딩하는 동현😎 2023. 9. 23.

연구소

문제

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

출력

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

예제 입력 1

7 7
2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

예제 출력 1

27

너비 우선 탐색(BFS)

루트 노드에 바로 직접 인접한 분기들 먼저 전부 방문하고, 분기들의 직속 자식 노드들을 방문하는 방법입니다.
그래서 넓게 방문한다고 불립니다.
가장 인접한 노드들을 최우선적으로 방문하기 때문에 최단거리 구하는 문제에 적합합니다.
아래는 DFS로 탐색하는 순서입니다. 정점에 쓰여져 있는 숫자는 n번째, 즉 순서를 의미합니다.

BFS 구현

너비 후선 탐색은 큐로 구현할수 있습니다. 큐는 FIFO(First in, First out) 먼저 넣어준것을 먼저 출력하는 자료구조를 가지고 있습니다.
루트 노드에 가장 가까이 인접한 노드들을 먼저 입력해서 가장 인접한 노드들을 우선적으로 탐색합니다.

예시코드 (파이썬)

def bfs(start):
    queue = deque()
    queue.append(start)
    visited[start]=True
    while queue:
        node = queue.popleft()
        for i in adjacent[node]:
            if visited[i] == False:
                queue.append(i)
                visited[i]=True

문제 풀이

막무가내로 좌표에서 임의로 세 좌표를 잡고 벽으로 지정한 다음에, bfs를 수행해서 오염한된 공기의 타일 수를 구해야 합니다.
모든 임의의 세좌표에 관해서 bfs를 하면 엄청나게 많은 시간이 걸려서 시간 초과가 될것 같지만,
N, M의 범위가 (3 ≤ N, M ≤ 8)로 매우 좁게 정해졌으므로 임의로 세 좌표를 지정하는 최악의 경우의 수가 8^2C3=41664로 매우 작습니다.
그래서 그 모든 경우마다 bfs로 얼마나 신선한 공기가 많이 나오는지 테스트 하면 됩니다.
설명은 주석에 담겨있습니다.
 

주의사항!

2차원배열처럼 변형 객체 같은 경우 copy(), [::]와 같은 얕은복사론 안됩니다.
얕은 복사는 일반 리스트 까지 되고, 더 복잡한 형태는 지원 안합니다.
깊은 복사를 해야되므로 copy라이브러리의 copy.deepcopy(원본) 함수를 적용하거나 2차원 배열을 슬라이싱해서 얕은 복사가 가능한 1차원 배열로 나워서 다시 붙여서 만들어주는 방식이 있습니다.
 

deepcopy로 이차원 배열 복사

# deepcopy 방법
import copy
test_box = copy.deepcopy(box)
# python3 기준 3000ms

 


슬라이싱해서 1차원 배열을 얕은 복사하는 방법

deepcopy함수가 워낙 오래걸리므로 이 방법이 더 빠릅니다.

# 2차원은 얕은 복사가 안되지만, 1차원 리스트는 되는 것을 이용
# 슬라이싱 방법
test_box = []
    for b in box:
        test_box.append(b.copy())
# python3 기준 2000ms

 

파이썬 코드

from collections import deque
import sys
input = sys.stdin.readline
n, m = map(int, input().split())
box = [list(map(int, input().split())) for _ in range(n)]
# 읽기 쉽게 0,1,2를 의미대로 치환
air, wall, virus = 0, 1, 2


# 공기갯수-새로생긴 바이러스 갯수로 잔여 공기를 구하려고 함
# 공기가 드갈 자리에 벽3개가 들어갈 것이므로 미리 -3 해줌
air_cnt = -3
# 바이러스 지점을 리스트에 미리 저장함
virus_point = []
for i in range(n):
    for j in range(m):
        if box[i][j] == air:
            air_cnt += 1
        if box[i][j] == virus:
            virus_point.append((i, j))


def isInside(x, y):
    if 0 <= x < n:
        if 0 <= y < m:
            return True
    return False


def bfs(x1, y1, x2, y2, x3, y3):
    global air_cnt, virus_point
    # 2차원배열처럼 변형 객체 같은 경우 copy(), [::]와 같은 얕은복사론 안된다
    # 얕은 복사는 일반 리스트 까지 되고, 더 복잡한 형태는 지원 안한다.
    # box를 test_box로 깊은 복사하는 과정
    test_box = []
    for b in box:
        test_box.append(b.copy())
    ##
    test_box[x1][y1] = wall
    test_box[x2][y2] = wall
    test_box[x3][y3] = wall
    dx = [1, -1, 0, 0]
    dy = [0, 0, 1, -1]
    queue = deque(virus_point)
    # 큐에 처음부터 있는 바이러스는 새로생긴 바이러스수에서 제외해야야함
    virus_cnt = -1 * len(virus_point)
    while queue:
        x, y = queue.popleft()
        virus_cnt += 1
        for i in range(4):
            nx, ny = x+dx[i], y+dy[i]
            if isInside(nx, ny) and test_box[nx][ny] == air:
                queue.append((nx, ny))
                test_box[nx][ny] = virus
    safezone = air_cnt - virus_cnt
    return safezone


max_safezone = 0

# 공기인 곳에서 좌표 세개 무작위로 잡아서 bfs해서 최댓값 구하는 과정
for i in range(n):
    for j in range(m):
        if box[i][j] != air:
            continue
        for x in range(i, n):
            for y in range(m):
                if x == i and y <= j:
                    continue
                if box[x][y] != air:
                    continue
                for a in range(x, n):
                    for b in range(m):
                        if a == x and b <= y:
                            continue
                        if box[a][b] != air:
                            continue
                        # print(i, j, x, y, a, b)
                        max_safezone = max(max_safezone, bfs(i, j, x, y, a, b))
print(max_safezone)
반응형

댓글