Algorithm/BFS

[Baekjoon 14502 / Python / 골드4] 연구소

양선규 2024. 9. 24. 20:50
728x90
반응형

문제

 

BFS, 백트래킹 문제이다. 거창한 백트래킹.. 이라기 보다, 그냥 모든 경우의 수를 찾기 위한 부분에서 백트래킹이 사용되었다. BFS가 메인이고 백트래킹은 거드는 느낌의 문제이다.

 

# BFS, 백트래킹
# 백트래킹으로 벽 3개를 세우는 경우의 수를 전부 구한 후, BFS로 바이러스를 전파시킨 후 안전 영역의 개수를 센다
from collections import deque
import sys
import copy
input = sys.stdin.readline

N, M = map(int, input().split()) # 행, 열
graph = [] # 지도
for _ in range(N):
    graph.append(list(map(int, input().split())))
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

# 3개의 벽을 세우는 경우의 수를 구하는 함수
def createWall(wall):

    if wall == 3: # 벽 3개가 세워졌다면 안전영역 개수 센다
        BFS()
        return
   
    for i in range(N):
        for j in range(M):
            if graph[i][j] == 0: # 안전 영역일 경우 벽을 세운다
                graph[i][j] = 1
                createWall(wall + 1) # 벽 개수 1개 추가하고 탐색
                graph[i][j] = 0 # 갔다 왔으니 다시 벽 허물어 준다


result = 0 # 결과 담을 변수
# BFS로 바이러스를 퍼트린 후 안전 영역 개수를 세는 함수
def BFS():

    global result

    trashGraph = copy.deepcopy(graph)
    queue = deque()

    for i in range(N):
        for j in range(M):
            if trashGraph[i][j] == 2: # 바이러스일 경우 큐에 추가
                queue.append([i, j])

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

        # 상하좌우 퍼뜨리기
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            # 좌표가 지도 안에 있고 안전 영역일 경우 퍼뜨린다
            if 0 <= nx < N and 0 <= ny < M and trashGraph[nx][ny] == 0:
                trashGraph[nx][ny] = 2
                queue.append([nx, ny])

    # 다 퍼뜨렸으면 안전영역 개수 센다
    cnt = 0
    for g in trashGraph:
        cnt += g.count(0)
   
    # 더 큰 값을 결과값으로 갱신한다
    result = max(result, cnt)

createWall(0)
print(result)

 

첫번째 통과 코드이다. pypy3으로 제출할 때만 통과한다.

 

문제를 어떻게 풀지 고민해 봤는데, 아무리 생각해도 벽 3개를 놓는 경우의 수를 모두 체크하는 방법밖에 없다고 생각되었다. 하지만 분명히 더 효율적인 방법이 있지만 내가 못 알아내는 거라고 생각해서, 너무 답답한 마음에 정답 코드를 참고했는데, 그렇게 푼 사람은 거의 없었다. 그냥 백트래킹으로 모든 경우의 수를 다 찾아도 풀리는 것이었다... 내 생각대로 먼저 풀어볼 걸 그랬다.

 

항상 너무 완벽하게 풀려는 마음에 조금만 내 생각이 불완전한 것 같다면 코드를 짜기가 싫어져 버린다. 이런 마음을 버리고 그냥 코드를 짜봐도 될 텐데. 고쳐야 할 부분이다.

 

python3으론 통과가 안되고 pypy3으로만 통과가 되는데 메모리와 시간을 꽤 많이 먹어서 이걸 어떻게 개선할 수 있을까 고민하다가, BFS함수에서 바이러스의 위치를 찾는 반복문이 중복된다는 걸 깨달았다.

 

 

# BFS, 백트래킹
# 백트래킹으로 벽 3개를 세우는 경우의 수를 전부 구한 후, BFS로 바이러스를 전파시킨 후 안전 영역의 개수를 센다
from collections import deque
import sys
import copy
input = sys.stdin.readline

N, M = map(int, input().split()) # 행, 열
graph = [] # 지도 입력받기
for _ in range(N):
    graph.append(list(map(int, input().split())))

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

# 바이러스 위치 미리 찾아놓기
virus = []
for i in range(N):
    for j in range(M):
        if graph[i][j] == 2:
            virus.append([i, j])

# 백트래킹으로 벽 3개를 세우는 모든 경우의 수를 구하는 함수
def createWall(wall):
   
    if wall == 3: # 벽 3개를 세웠으면 BFS 함수 실행
        BFS()
        return
   
    for i in range(N):
        for j in range(M):
            if graph[i][j] == 0: # 안전 영역에 한해 벽을 세운다
                graph[i][j] = 1
                createWall(wall + 1) # 세웠으면 다음 벽을 세우러 간다
                graph[i][j] = 0 # 돌아왔으면 벽을 허문다. 다른 경우의 수를 찾는다


result = 0 # 최종 결과 저장할 변수
# BFS로 바이러스를 퍼뜨리고 안전 영역의 개수를 세는 함수
def BFS():

    global result
    trashGraph = copy.deepcopy(graph)

    # 미리 찾아놓은 바이러스 위치를 큐에 넣는다
    queue = deque()
    for v in virus:
        queue.append(v)

    # 큐가 빌 때까지 실행한다
    while queue:
       
        x, y = queue.popleft()

        # 상하좌우로 바이러스 퍼뜨리기
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            # 좌표가 지도 안에 있고 안전 영역일 경우 퍼뜨린다
            if 0 <= nx < N and 0 <= ny < M and trashGraph[nx][ny] == 0:
                trashGraph[nx][ny] = 2
                queue.append([nx, ny])
   
    # 안전 영역의 개수를 세고 더 큰 값을 result로 갱신한다
    cnt = 0
    for g in trashGraph:
        cnt += g.count(0)
    result = max(result, cnt)

createWall(0)
print(result)

 

개선한 코드이다. 벽의 위치만 바뀔 뿐 바이러스의 시작 위치는 계속해서 동일하므로, 처음에 위치를 미리 구해두고 BFS 함수에서 그것을 사용했다. 근데 드라마틱한 효율 개선은 일어나지 않았다. 해봤자 100ms ~ 200ms 줄어드는 정도? 메모리는 거의 비슷했고.

 

바이러스의 시작 위치를 찾는 부분보다, 벽 3개 경우의 수를 찾는 부분의 영향력이 크게 높아서 그런 듯 하다. 근데 내 생각으로는 경우의 수 찾는 부분을 어떻게 개선해야 할 지 잘 모르겠다. 어쨌든 조금이라도 시간을 줄이긴 했다.

 

참고로 초기 바이러스 위치를 deepcopy로 복사해 봤는데, 오히려 시간이 증가했었다. 직접 반복문으로 담는 게 더 효율적인 듯 하다. deepcopy는 객체를 하나 더 만들기 때문에 비효율적인 것 같다. 어차피 바이러스 개수는 해봐야 10개가 최대이기도 하고. 여기에 굳이 deepcopy를 쓸 필요는 없을 것 같다.

 

728x90
반응형