from itertools import combinations
from collections import deque
def bfs(active_virus,total_virus):
    global result,virus_list,N
    virus_cnt = 0
    deactive_virus = virus_list-active_virus
    stack = deque()
    spend_time = [row[:] for row in spend_time_stand]
    for x,y in active_virus:
        stack.append((x,y,0))
        spend_time[x][y] = 0
        virus_cnt += 1
    min_time = -1
    while stack:
        x,y,time = stack.popleft()
        if min_time > result:
            return
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if 0<=nx<N and 0<=ny<N:
                if spend_time[nx][ny] == -1 and (nx,ny) not in wall_list:
                    if (nx,ny) not in deactive_virus:
                        spend_time[nx][ny] = time+1
                        stack.append((nx,ny,time+1))
                        min_time = spend_time[nx][ny]
                        virus_cnt += 1
                    else:
                        spend_time[nx][ny] = 0
                        stack.append((nx,ny,time+1))
                        

        
    if total_virus == virus_cnt:
        if result >min_time and min_time != -1:
            result = min_time


    



N,M = map(int,input().split())
dx = [-1,1,0,0]
dy = [0,0,1,-1]
arr = [list(map(int,input().split())) for _ in range(N)]
virus_list = set()
wall_list = set()
total_virus = N*N
spend_time_stand = [[-1] *N for _ in range(N)]
for x in range(N):
    for y in range(N):
        if arr[x][y] == 2:
            virus_list.add((x,y))
        elif arr[x][y] == 1:
            wall_list.add((x,y))
            total_virus -= 1

start_virus_list = combinations(virus_list,M)
result = float('inf')
if total_virus-len(virus_list)+M != M:
    for lists in start_virus_list:
        bfs(set(lists),total_virus-len(virus_list)+M)
else:
    result = 0

if result == float('inf'):
    print(-1)
else:
    print(result)

연구소 시리즈 문제 중 3번 문제이다. 여기서는 예외로 해야할 곳이 생각외로 많아서 많이 틀리면서 푼 문제이다.

 

문제 자체를 이해하자면, 처음부터 있는 바이러스들이 존재한다.

그런데 여기서 M개의 바이러스가 활성화 되고, 나머지 바이러스는 비활성 상태가 된다.

 

활성 바이러스가 비활성 바이러스 가면 활성화 상태가 된다.

 

여기서 주의해야할 점은 이 점이다. 처음부터 있는 바이러스는 비활성 상태지만 이미 뿌려진 상태이기 때문에, 비활성->활성이 될뿐이지, 이미 바이러스가 존재하기 때문에, 퍼트리는데 드는 시간에는 포함이 되지 않는다.

 

그리고 두번째로 주의해야할 점은, 퍼트리는데 드는시간에 들지는 않지만, 비활성간을 활성화시키고 넘어가기 위해선 시간이 누적되는 것이다.

 

예를 들어 

 

활성 비활성 비활성 빈칸

 

이렇게 되어있을시, 

1초 뒤

활성 활성 비활성 빈칸

2초뒤

활성 활성 활성 빈칸

3초뒤

활성 활성 활성 활성

 

이렇게 3초가 걸리는 걸 알 수 있다. 비활성 바이러스는 이미 존재하던 바이러스이기 때문에 새로 퍼트린 바이러스 시간에는 포함되지 않지만, 이동통로로서는 역할을 하기 위해 시간은 지나가줘야한다.

 

인제 코드로 돌아가서 기본적인 원리는 연구소 문제들과 동일하다.

전체에 바이러스가 퍼트릴수 있는 최소시간을 구하는 것이고, 구하기 전에 전처리 과정을 해준다.

먼저 벽인 부분은 따로 wall_list로 저장해두고, 전체 바이러스가 생길수 있는 공간 N*N에서 1씩 빼준다.

왜냐하면 벽에는 바이러스가 못살기 때문이다.

또한 virus가 존재하는 경우엔, virus_list라는 set에 저장해두었다.

만약 바이러스가 생길수 있는 공간에서 입력된 바이러스의 값을 뺐을때 0이면, 이미 전부 퍼진거니 0을 출력해주면 된다.

그렇지 않을 경우, 바이러스를 퍼트리는 과정을 해준다. 앞에서 virus_list에서 M개의 combination을 뽑아낸다.

퍼지는 시간이 들어갈 주어진 공간과 같은 크기의 행렬을 만들어준다. 또한, 기본값은 -1로 시간상 존재할수 없는 값으로 초기화 해준다.

 

파이썬의 집합의 특징을 이용해 전체 virus_list에서 active_list(활성화된 바이러스)를 빼주어서, 비활성된 바이러스의 집합을 따로 만들어준다.

 

그리고 우리가 만든 spend_time이라는 행렬에 활성화된 바이러스의 위치에 0으로 초기화 시켜주고 stack에 넣어준다.

나머지는 기본적인 bfs랑 동일하다.

여기서 주의해야할 점은 deactive_virus인 공간과 빈공간에서 차이점을 둔다.

deactive_virus는 stack에 똑같이 time을 +1 해서 추가해주는 대신 spend_time은 0으로 해준다.

하지만 빈공간일때는 spend_time에 현재시간 +1 을 넣어주고, stack에도 동일하게 넣어준다. 그리고 virus_cnt를 늘려준다.

그리고 여기서 min_time을 갱신시켜준다. bfs는 최단경로를 구하는 것이기 때문에, 가장 마지막에 stack에 들어간 값이 모두 퍼트리는데 걸리는 시간이기 때문이다.

 

또한 시간을 줄여보고자, 우리가 출력하고자하는 결과 result보다 현재 min_time이 높으면 bfs를 중단시켜주었다.

 

 

이 문제에서 여러번 시도하면서 틀렸던 점은 위의 주의점 2가지를 빠르게 파악을 못했기 때문이었다. 문제를 제대로 이해하는데, 더 열심히 해야겠다.

 

이 문제를 풀면서 틀렸습니다. 됬을때 도움이 되는 반례는

 

5 1

2 1 1 1 1

2 1 1 1 1

2 1 1 1 1

2 1 1 1 1

2 1 1 1 1

 

answer = 0

 

4 2

0 1 1 0

2 1 1 2

2 1 1 2

0 1 1 0

 

answer = 2

 

더 많은 반례는 www.acmicpc.net/board/view/43303www.acmicpc.net/board/view/59703 를 참조하면 좋겠습니다.

 

글 읽기 - 연구소3 92%에서 틀렸습니다 뜨시는분들

댓글을 작성하려면 로그인해야 합니다.

www.acmicpc.net

 

글 읽기 - ☆★ 연구소3 반례모음 ★☆

댓글을 작성하려면 로그인해야 합니다.

www.acmicpc.net

 

def dfs(bomblist):
    global N,result
    while bomblist:
        x,y = bomblist.pop()
        for i in range(8):
            nx = x + dx[i]
            ny = y + dy[i]
            if (nx == 0 or nx == N-1) or (ny == 0 or ny ==N-1):
                if arr[nx][ny] == 0:
                    break
        else:
            for i in range(8):
                nx = x + dx[i]
                ny = y + dy[i]
                if (nx == 0 or nx == N-1) or (ny == 0 or ny ==N-1):
                    arr[nx][ny] -= 1
            result += 1
    return

            



N = int(input())
dx = [-1,0,1,-1,1,-1,0,1]
dy = [-1,-1,-1,0,0,1,1,1]
arr = [list(input()) for _ in range(N)]
result = 0
if N >4:
    result += (N-4)**2
bomb = []
for x in range(N):
    for y in range(N):
        if x == 1 or x == N-2:
            if arr[x][y] == '#':
                bomb.append((x,y))
            else:
                arr[x][y] = int(arr[x][y])
        elif 1<x<N-2:
            if y == 1 or y == N-2:
                bomb.append((x,y))
            else:
                if arr[x][y] != '#':
                    arr[x][y] = int(arr[x][y])
        else:
            if arr[x][y] != '#':
                arr[x][y] = int(arr[x][y])
dfs(bomb)
print(result)

 

 지뢰찾기를 하는 문제이다. 테두리에만 숫자가 있고, 그 안은 파악되지않는 지뢰구역이 있다.

이 지뢰구역에서 최대로 설치가능한 지뢰의 개수를 구하는 문제이다.

먼저, 테두리에 직접적으로 닿아있는 칸들을 제외한 나머지 칸들은 전부 지뢰가 있다고 가정을 해야, 최대 지뢰를 구할 수 있다.

그리고 두번째로 테두리에 근접해있는 칸들을 기준으로 8칸을 탐색을 해서, 그 안에 0이 있다면, 해당 구역은 지뢰가 존재할수 없는 공간이니 넘어간다.

하지만, 0이 존재하지 않는 경우 지뢰가 존재하는 경우이니, 지뢰의 수를 1개 늘려주고, 해당 구역의 주변 8칸의 숫자를 1씩 줄여준다.

이걸 반복해준다.

 

여기서 쓴 코드적인 부분은 for else 문이다.

for문을 반복하면서, break를 한번도 마주치지 않으면 자동적으로 else문이 실행되는 것이다.

이걸 통해 따로 flag같은 True False 값 같은 분기점이 없이 진행이 가능했다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/백준] 2564 경비원  (0) 2021.01.15
[BOJ] 5569 출근 경로  (0) 2021.01.15
[BOJ] 1058 친구  (0) 2021.01.14
[BOJ] 10884 쉬운 계단 수  (0) 2021.01.14
[BOJ] 1149 RGB 거리  (0) 2021.01.14
def bfs(x,y,color):
    global puyo,visited,arr
    temp = [[x,y]]
    stack = [[x,y]]
    while stack:
        sx,sy = stack.pop(0)
        for i in range(4):
            nx = sx + dx[i]
            ny = sy + dy[i]
            if 0<= nx < 12 and 0 <= ny <6:
                if arr[nx][ny] == color and visited[nx][ny]:
                    visited[nx][ny] = 0
                    stack.append([nx,ny])
                    temp.append([nx,ny])
    if len(temp) >= 4:
        return temp
    else:
        return []




arr = [list(input()) for _ in range(12)]
times = 0
dx = [-1,1,0,0]
dy = [0,0,-1,1]
while True:
    visited= [[1]*6 for _ in range(12)]
    puyo = []
    for x in range(12):
        for y in range(6):
            if arr[x][y] != '.' and visited[x][y]:
                visited[x][y] = 0
                check = bfs(x,y,arr[x][y])
                if check:
                    for x,y in check:
                        puyo.append((x,y))
    for x,y in puyo:
        arr[x][y] = '.'
    if len(puyo) == 0:
        break
    new_arr = [['.']*6 for _ in range(12)]
    for y in range(6):
        temp = []
        for x in range(11,-1,-1):
            if arr[x][y] != '.':
                temp.append(arr[x][y])
        for i in range(len(temp)):
            new_arr[11-i][y] = temp[i]
    arr = [row[:] for row in new_arr]
    times += 1
print(times)

 

 이 문제는 한단계씩 BFS를 돌려서 이어진 뿌요를 찾고, 그 뿌요가 하나도 없으면 멈추면 된다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ] 17471 게리멘더링  (0) 2021.01.12
[BOJ] 14500 테트로미노  (0) 2021.01.12
[BOJ] 3055 탈출  (0) 2021.01.11
[BOJ] 12907 동물원  (0) 2021.01.11
[BOJ] 17472 다리 만들기 2  (0) 2021.01.10
n, k = list(map(int,input().split()))
conveyer = list(map(int,input().split()))
robot = [0]*n
robot_cnt = 1
step = 0
while conveyer.count(0) < k:
    last_number = conveyer.pop()
    conveyer.insert(0,last_number)
    last_robot = robot.pop()
    robot.insert(0,0)
    robot[n-1] = 0
    que = []
    for i in range(n):
        if robot[i] > 0:
            que.append([robot[i],i])
    que.sort()
    while que:
        robot_ind, ind = que.pop(0)
        if ind + 1 == n-1:
            if conveyer[n-1] != 0:
                robot[ind+1] = 0
                robot[ind] = 0
                conveyer[n-1] -= 1
            else:
                robot[ind] = robot_ind 
        else:
            if conveyer[ind+1] != 0 and robot[ind+1]==0:
                robot[ind+1] = robot_ind
                robot[ind] = 0
                conveyer[ind+1] -= 1
            else:
                robot[ind] = robot_ind
    if robot[0] == 0 and conveyer[0]:
        robot[0] = robot_cnt
        robot_cnt += 1
        conveyer[0] -= 1
    step += 1
print(step)


 

첫 풀이는 문제에서 robot에는 로봇의 최초진입시기를 저장해주고, conveyer에는 컨베이어의 내구도를 적어주는 것이다.

먼저 컨베이어 벨트가 움직이고 n-1 위치에 도착시 로봇이 바로 내려가는 것만 주의해주면 어렵지 않은 문제였다. 하지만 시간이 오래걸리는 문제가 있으므로, 밑의 방식으로 개선했다.

 

 

 

from collections import deque

n, k = map(int, input().split())
arr = list(map(int, input().split()))
ls = [['X'] for _ in range(2*n)]
for i in range(2*n):
    ls[i].append(arr[i])
belt = deque(ls)

lift_idx = 0
drop_idx = n-1
cnt = 0 
result = 0
while True:
    #내구도가 0인게 k개 이상인지 카운트해서 while문 탈출조건을 세워준다.
    if cnt >= k:
        break
    # 벨트 돌아가기
    tmp = belt.pop()
    belt.appendleft(tmp)
    # 내릴 수 있으면 내린다.
    if belt[drop_idx][0] == 'O':
        belt[drop_idx][0] = 'X'
    #이동이 가능하면 이동한다.
    for i in range(n-2,-1,-1):
        if belt[i][0] == 'O':
            if belt[i+1][0] == 'X' and belt[i+1][1] != 0:
                belt[i][0] = 'X'
                belt[i+1][0] = 'O'
                belt[i+1][1] -= 1
                if i+1 == drop_idx:
                   belt[i+1][0] = 'X'
    #로봇을 올린다.
    if belt[lift_idx][1] != 0 and belt[lift_idx][0] == 'X':
        belt[lift_idx][0] = 'O'
        belt[lift_idx][1] -= 1
    cnt = 0
    for i in range(len(belt)):
        if belt[i][1] == 0: 
            cnt += 1
    result += 1
                
print(result)

belt의 i번째는 해당 index에 로봇이 있는지 유무와 belt의 내구도를 저장해준다.

그리고 어차피 n-1에서 모든 로봇은 내려가므로, n-2 부터 0까지 반복문을 돌려주면 차례대로 이동하는 것이 된다.

이 부분만 바뀌고 나머지는 동일하다.

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


n,m,k = map(int,input().split())

fireball = {}

for _ in range(m):
    temp = list(map(int,input().split()))
    fireball[(temp[0]-1,temp[1]-1)] = []
    # 질량, 속도, 방향
    fireball[(temp[0]-1,temp[1]-1)].append([temp[2],temp[3],temp[4]])


for _ in range(k):
    new_fireball = {}
    for ind,vals in fireball.items():
        for val in vals:
            new_x = (ind[0] + val[1]*dx[val[2]])%n
            new_y = (ind[1] + val[1]*dy[val[2]])%n

            if new_fireball.get((new_x,new_y)):
                new_fireball[(new_x,new_y)].append([val[0],val[1],val[2]])
            else:
                new_fireball[(new_x,new_y)] = [[val[0],val[1],val[2]]]
    fireball ={}
    for ind,vals in new_fireball.items():
        if len(vals) > 1:
            total_weight = 0
            total_speed = 0
            total_direction = []
            for val in vals:
                total_weight += val[0]
                total_speed += val[1]
                total_direction.append(val[2])
            next_weight = total_weight//5
            next_speed = total_speed//len(vals)
            if next_weight:
                total_direction = list(map(lambda x: x%2 ,total_direction))
                if sum(total_direction) == 0 or sum(total_direction) == len(vals):
                    next_direction = [0,2,4,6]
                else:
                    next_direction = [1,3,5,7]
                fireball[ind] = []
                for i in range(4):
                    fireball[ind].append([next_weight,next_speed,next_direction[i]])

        else:
            fireball[ind] = vals

result = 0
for ind,vals in fireball.items():
    for val in vals:
        result += val[0]
print(result)

문제에 나온 내용을 그대로 시뮬레이션을 해주면 되는 것이었다.

이러한 무한의 길이를 가진 행렬문제나, 한 위치에 여러가지 정보를 저장해야하는 경우가

나오면 리스트로 구현하기 보단, 딕셔너리로 해서 위치좌표를 키로 지정하고 밸류 값에 여러가지 정보를 저장하는 방식을 선호한다.

 

이 문제에서 주의해야할 점은 행렬의 범위를 벗어나면 반대편으로 나오는 것이다. 

이 부분은 나는 n으로 나눈 나머지로 해결했다. 그 뒤에는 딕셔너리에 저장된 값 중 길이가 2 이상인 경우에는 문제에서 주어졌던 조건에 맞추어, 분리가 되도록 구현했다.

direction = [0,1,2,3]
# dire의 값이 0 : 좌
#  1: 하
#  2: 우
#  3: 상
move_dire = [[0,-1],
[1,0],
[0,1],
[-1,0]]
# 토네이도가 부는 방향 좌 하 우 상
#  -90도 회전 list(zip(*tornado))[::-1]
tornado = [[0,0,2,0,0],
[0,10,7,1,0],
[5,0,0,0,0],
[0,10,7,1,0],
[0,0,2,0,0]]



N = int(input())
arr = [list(map(int,input().split())) for _ in range(N)]

k = N**2 -1
cnt = 0
number_cnt = 1 
# number_cnt는 현재 방향으로 진행되는 최대거리를 알려주는 것이다.
double_cnt = 0
# 최대거리가 진행되는 횟수를 알려주는 것이다.
cur_cnt = 0
# 현재 이동 횟수이다.
cur_dire = 0 
# 현재 진행 방향이다.
# 이 문제에서 보면 알 수 있듯이, 좌하우상 순서로 진행되면 그 거리는 1,1,2,2,3,3,4,4,5,5,6,6 순으로 진행된다.
# 최대거리가 동일한게 2번 반복되는 것을 알 수 있다.
location = [N//2,N//2]
result = 0

while True:
    # 최대 거리와 현재 이동거리가 같다면 방향이 바뀌어야하는 것이므로, 방향을 바꾸면서 최대거리를 갱신해주는 작업을 해준다.
    if cur_cnt == number_cnt:
        cur_cnt = 0
        double_cnt += 1
        cur_dire = (cur_dire+1)%4
        tornado = list(zip(*tornado))[::-1]
        # -90도 회전을 해주는 간편한 방법이다. 
        if double_cnt == 2:
            # double_cnt가 2라는 것은 최대 이동거리가 동일한게 2번 반복된것이니, 최대 이동거리를 증가시켜줘야한다.
            double_cnt = 0
            number_cnt += 1
    # target_location은 현재 토네이도의 위치인 x에서 y로 갈때 y의 위치이다.
    target_location = [location[0]+move_dire[cur_dire][0],location[1]+move_dire[cur_dire][1]]
    # total_more는 targetloaction에 있는 모래의 양이다.
    total_more = arr[target_location[0]][target_location[1]]
    # torandao로 이동하는 것을 해주는 것이다.
    for i in range(5):
        for j in range(5):
            move_ind = [target_location[0]+i-2,target_location[1]+j-2]
            more = int(tornado[i][j]*arr[target_location[0]][target_location[1]]/100)
            if 0<=move_ind[0]<N and 0<=move_ind[1]<N:
                arr[move_ind[0]][move_ind[1]] += more
            # 만약 범위를 벗어나면, 결과값에 추가해준다.
            else:
                if more:
                    result += more
            # 흩날리고 남은 모래를 a의 모래가 되므로 구해준다.
            total_more -= more
    cur_cnt += 1
    last_location = [location[0]+2*move_dire[cur_dire][0],location[1]+2*move_dire[cur_dire][1]]
    # a의 위치가 범위를 벗어나면, result에 추가해주고, 아니면 해당위치에 모래를 추가해준다.
    if 0<=last_location[0]<N and 0<=last_location[1]<N:
        arr[last_location[0]][last_location[1]] += total_more
    else:
        result += total_more
    location = [location[0]+move_dire[cur_dire][0],location[1]+move_dire[cur_dire][1]]
    arr[location[0]][location[1]] = 0
    if location[0] == 0 and location[1] == 0:
        break
print(result)

 

 처음 풀었던 방식은 문제에 나와있는걸 그대로 구현해주었다.

이 문제를 풀때 중요한것은 방향전환과 그 반복성이 어떻게 진행되는지 찾아주고 그것을 구현해주는 방식이다.

하지만 코드길이가 길고, 변수도 많아서 문제를 풀때 헷갈리는게 많았다.

 

import sys

def blowSand(x,y,dire):
    global N
    ret = 0
    init_sand = arr[x][y]
    for i in range(10):
        if i == 9:
            sand = arr[x][y]
        else:
            sand = int(init_sand * rate[i]/100)
            arr[x][y] -= sand
        move_x = x + blowx[dire][i]
        move_y = y + blowy[dire][i]

        if 0<=move_x<N and 0<=move_y<N:
            arr[move_x][move_y] += sand
        else:
            ret += sand
    arr[x][y] = 0
    return ret

dx = [0,1,0,-1]
dy = [-1,0,1,0]
rate = [1, 1, 7, 7, 10, 10, 2, 2, 5]

blowx = [[-1, 1, -1, 1, -1, 1, -2, 2, 0,0], #left
[-1, -1, 0, 0, 1, 1, 0, 0, 2,1],   #down
[-1, 1, -1, 1, -1, 1, -2, 2, 0,0], #right
[1, 1, 0, 0, -1, -1, 0, 0, -2,-1],]  #up

blowy = [[1, 1, 0, 0, -1, -1, 0, 0, -2,-1],  #left
[-1, 1, -1, 1, -1, 1, -2, 2, 0,0], #down
[-1, -1, 0, 0, 1, 1, 0, 0, 2,1],   #right
[-1, 1, -1, 1, -1, 1, -2, 2, 0,0]] #up
N = int(sys.stdin.readline())
arr =[list(map(int,sys.stdin.readline().split())) for _ in range(N)]
x,y = N//2,N//2
i = 1
dire = 0
result = 0
while i<=N:
    j = 0
    while j <int(i):
        x = x + dx[dire%4]
        y = y + dy[dire%4]
        result += blowSand(x,y,dire%4)
        j += 1
    dire += 1
    i += 0.5
print(result)

위의 코드는 클론코딩한 것으로, 기본적인 원리는 위와 비슷하지만 좀 더 깔끔해서 보기가 편하다.

모래방향에 따른 x,y의 이동값을 전부 저장해준다. 그리고 그에 맞게 이동시 날라가는 비율을 적어준다.

 

여기서는 위에서 내가 했던 double_cnt 같은 것을 이용하지 않고 0.5 단위로 i의 값을 올려주고, 그것을 int화 함으로서 2번반복을 구현해냈다.

blowSand라는 함수를 통해, 날라간 모래 양을 계속 더해주고, 모래가 날라간 상황을 만들어줬다.

+ Recent posts