https://www.acmicpc.net/problem/17141
Problem
- 인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 승원이는 연구소의 특정 위치에 바이러스 M개를 놓을 것이고, 승원이의 신호와 동시에 바이러스는 퍼지게 된다.
- 연구소의 상태가 주어졌을 때, 모든 빈 칸에 바이러스를 퍼뜨리는 최소 시간을 구해보자.
조건
- 연구소는 크기가 N×N인 정사각형으로 나타낼 수 있으며, 정사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다.
- 일부 빈 칸은 바이러스를 놓을 수 있는 칸이다. 바이러스는 상하좌우로 인접한 모든 빈 칸으로 동시에 복제되며, 1초가 걸린다.
- 첫째 줄에 연구소의 크기 N(5 ≤ N ≤ 50), 놓을 수 있는 바이러스의 개수 M(1 ≤ M ≤ 10)이 주어진다. 둘째 줄부터 N개의 줄에 연구소의 상태가 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스를 놓을 수 있는 칸이다. 2의 개수는 M보다 크거나 같고, 10보다 작거나 같은 자연수이다.
SOL
벽을 세우는게 아니라, 바이러스가 될 수 있는 후보지에서 m개를 뽑아서 바이러스를 터뜨리는 경우이다.
m<=10이므로, 10C5 =252 가지밖에 되지않으므로, BFS로 탐색을 한다고 해도 충분히 가능하다.
크게 3가지 부분으로 나눌 수 있을거 같다.
- 바이러스 후보지를 선택하는 방법
- 바이러스를 퍼뜨리는 방법
- 바이러스를 퍼뜨리고 난 후, 최소시간을 구하는 방법
바이러스 후보지를 선택하는 방법
바이러스 후보지를 선택하려면, 우선 "2"가 있는 곳에서 m개를 뽑아내야한다.
- "2"가 있는 곳을 virus에 index 정보를 저장해놓는다.
- 그리고, virus안에 index들을 선택하기 위해서, mapping을 위한 index 배열을 따로 만들어준다. (select)
- board=0인 이유는, 선택되지 못한 곳에도 바이러스가 퍼져야하기 때문이다. 그래서 그때마다 처리해주기 귀찮아서 다 0으로 만들어버렸음. (어차피, virus가 퍼지는건 visited라는 배열에 기록할거기 때문)
for i in range(N):
for j in range(N):
if board[i][j]==2:
virus.append((i,j))
board[i][j]=0
select =[0 for _ in range(10)]
다음은 , 후보들 중에, 우리가 봐야하는 조합을 구현하면 된다. 조합을 구현할 때, 재귀적으로 직접 구현도 가능하고, 내장되어있는 기능인 combination을 사용하여도 무방하다.우선, DFS로 하나하나 다 재귀적으로 구하는 경우로 구현할거임!
# 탈출 지점, cnt==M이 되는 경우
if cnt==M:
##탐색하기
## 모든 조합을 재귀적으로 구현
for i in range(idx,len(virus)):
if select[i]:
continue
select[i] =1
select_virus(i+1,cnt+1)
select[i] =0
의사코드를 쓰자면, 위와같이 적을 수 있다.
바이러스 퍼뜨리기 (탐색하기)
사실 이 부분은 , 워낙 많이 구현했던 부분이라, 어려움 없이 구현할 수 있었다.
#if cnt==M인 경우
dq=deque()
visited=[[-1]*N for _ in range(N)]
for i in range(len(virus)):
if select[i]: # select[i] ,즉 i번째 인덱스가 선택된 경우만 뽑아서 넣어주기.
y,x=virus[i][0],virus[i][1]
dq.append((y,x))
visited[y][x] =0
solve()
#solve() 함수의 의사코드
while dq:
y,x = dq.popleft()
for k in range(4):
ny=y+dy[k]
nx=x+dx[k]
if (0<=ny<N and 0<=nx<N) and visited[ny][nx]==-1:
if board[ny][nx] ==0:
visited[ny][nx] = visited[y][x] +1
cnt=max(cnt,visited[ny][nx])
dq.append((ny,nx))
퍼뜨리고 난 후, 최소시간 구하기
- 퍼뜨리고 난 후, 문제에서 바이러스가 어떻게 놔도 퍼지지 못하는 곳이 있으면 print -1을 해야하므로, 그거 먼저 검사해준다.
- 그리고, 각 바이러스를 놓는 경우의 수에 따라, max값을 찾아주고, 그 중에 최솟값을 구해야 하기 때문에,
- ans = min(ans,cnt)를 해주면 된다!
for i in range(N):
for j in range(N):
if board[i][j] ==0 and visited[i][j] ==-1:
cnt=sys.maxsize
ans=min(ans,cnt)
전체코드
# 연구소 2
from collections import deque
from itertools import combinations
import sys
input = sys.stdin.readline
N,M =map(int,input().split())
board=[list(map(int,input().split())) for _ in range(N)]
dx=[1,0,-1,0]
dy=[0,1,0,-1]
virus =[]
for i in range(N):
for j in range(N):
if board[i][j]==2:
virus.append((i,j))
board[i][j]=0
#print(virus)
#탐색 바이러스를 터뜨린다
def solve():
global visited,dq,ans
cnt=0
while dq:
y,x = dq.popleft()
for k in range(4):
ny=y+dy[k]
nx=x+dx[k]
if (0<=ny<N and 0<=nx<N) and visited[ny][nx]==-1:
if board[ny][nx] ==0:
visited[ny][nx] = visited[y][x] +1
cnt=max(cnt,visited[ny][nx])
dq.append((ny,nx))
# 최솟값 찾기
for i in range(N):
for j in range(N):
if board[i][j] ==0 and visited[i][j] ==-1:
cnt=sys.maxsize
ans=min(ans,cnt)
# 퍼뜨릴 바이러스를 정한다.
def select_virus(idx,cnt):
global dq,visited,ans
if cnt==M:
dq=deque()
visited=[[-1]*N for _ in range(N)]
for i in range(len(virus)):
if select[i]:
y,x=virus[i][0],virus[i][1]
dq.append((y,x))
visited[y][x] =0
solve()
return
for i in range(idx,len(virus)):
if select[i]:
continue
select[i]=1
select_virus(i+1,cnt+1)
select[i]=0
select =[0 for _ in range(10)]
ans=sys.maxsize
select_virus(0,0)
print(ans) if ans != sys.maxsize else print(-1)
내일은 combination을 이용해서 풀어보고 풀이를 추가해보겠다!
'알고리즘 문제(SOL)' 카테고리의 다른 글
[백준/16236/파이썬] 아기 상어 (0) | 2022.01.30 |
---|---|
[백준/1256/파이썬] 사전 (0) | 2022.01.29 |
[백준/2804/파이썬] 크로스워드 만들기 (0) | 2022.01.28 |
[백준/2163/파이썬] 초콜릿 자르기 (0) | 2022.01.28 |
[백준/14502/파이썬] 연구소 (0) | 2022.01.28 |