# ---------- Import ----------
from collections import deque
import sys
input = sys.stdin.readline

# ---------- Function ----------
def is_Bipartite(graph, node_count):
    visited = [-1] * (node_count+1)
    
    for node in range(node_count+1):
        # group1 number is 1, group2 number is 0
        if visited[node] != -1:
            continue
        
        group = 1
        visited[node] = group
        
        Q = deque([(node, group)]) # node, group
        
        while Q:
            node, group = Q.popleft()
            
            for i in graph[node]:
                if visited[i] == group:
                    return "NO"
                
                if visited[i] == -1:
                    visited[i] = (group+1) % 2
                    Q.append((i, (group+1) % 2))
            
    return "YES"

# ---------- Main ----------
for _ in range(int(input())):
    node, edge = map(int, input().split())
    graph = [[] for _ in range(node+1)]
    
    for _ in range(edge):
        s, e = map(int, input().split())
        graph[s].append(e)
        graph[e].append(s)
    
    answer = is_Bipartite(graph, node)
    print(answer)
# ---------- Import ----------
import heapq
import sys
input = sys.stdin.readline

INF = 1e9


# ---------- Function ----------
def Dijkstra(sNode: int, eNode: int):
    # Start node and distance Init
    distance = [INF] * (Node+1)
    distance[sNode] = 0
    
    # Init
    Q = []
    heapq.heappush(Q, (0, sNode))   # cost, node
    
    # Do dijkstra
    while Q:
        cost, current_node = heapq.heappop(Q)
        
        # End condition
        if current_node == eNode:
            return distance[eNode]
        
        # Check nodes connected with current node
        for next_node, weight in graph[current_node]:
            new_cost = cost + weight
            
            # New cost is more smallest
            if new_cost < distance[next_node]:
                distance[next_node] = new_cost
                heapq.heappush(Q, (new_cost, next_node))
    
    # No way to go eNode
    return None


# ---------- Main ----------
Node, Edge = map(int, input().split())
graph = [[] for _ in range(Node+1)]

# Input graph information
for _ in range(Edge):
    start, end, weight = map(int, input().split())
    graph[start].append((end, weight))
    graph[end].append((start, weight))

# Two node that must pass
fir, sec = map(int, input().split())
way_distance = [0, 0]

way_condition = [
    [(1, fir), (fir, sec), (sec, Node)],
    [(1, sec), (sec, fir), (fir, Node)]
]

# Find shortest way
for idx, condition in enumerate(way_condition):
    way = 0
    
    for s, e in condition:
        try:
            way += Dijkstra(s, e)
        except TypeError:
            way = None
            break
    
    way_distance[idx] = way
        
# Print answer
way1 = way_distance[0]
way2 = way_distance[1]

if (way1 is None) and (way2 is None): print(-1)
elif way1 is None: print(way2)
elif way2 is None: print(way1)
else: print(min(way1, way2))

 

'PS > 그래프' 카테고리의 다른 글

[백준] No.1707 이분 그래프 完  (0) 2024.01.11
[백준] No.1504 특정한 최단 경로 01  (0) 2023.11.16
[백준] No.1043 거짓말 完  (0) 2023.09.27
[백준] No.1753 최단 경로 完  (0) 2023.08.13
[백준] No.1753 최단 경로 01  (0) 2023.08.13

 

 

1504번: 특정한 최단 경로

첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존

www.acmicpc.net

이번에 "2024 카카오 채용 연계형 겨울 인턴십" 공고가 떠서 목표로 하고 있다.

지원 분야는 'Data Engineering'으로 11/25(토)에 코딩 테스트를 진행한다고 한다.

저번에 봤던 넥슨은 과제 테스트를 진행했는데, 이번 카카오는 없는 건지 추후 공지인지는 모르겠다.

하지만 확실하게 코딩 테스트를 본다는 것을 알고 있으니 대비해야 한다.

 

친구와 코딩 테스트를 준비하고, 문제를 많이 풀어본 결과

DP, 그리디, 다익스트라, 세 분야에서 보충이 필요하다고 느꼈다.

최근 1달 반동안 알고리즘 및 코딩 테스트 관련 코딩을 하나도 못 하다보니... 다익스트라를 완전히 까먹었다.

DFS, BFS와 기본 Python 문법, 그외 알고리즘들은 종종 봐서 기억나는데, 다익스트라는 진짜 하나도 기억이 안 난다.  

그저 heapq를 사용한다밖에...

그래서 이 문제를 선택했다.

 

문제 자체는 간단하다.

START에서 END로 갈 때, 반드시 point1과 point2(이하 fir와 sec)를 거쳐가야 한다.

이때 최단 거리는 얼마일까요? 없다면 -1을 출력하라는 게 전부인 문제다.

역시 시작은 Pseudo code부터 한다.

 

# ---------- Import ----------
import heapq
import sys
input = sys.stdin.readline

INF = 1e9


# ---------- Function ----------
def Dijkstra(sNode: int, eNode: int):
    return


# ---------- Main ----------
Node, Edge = map(int, input().split())
graph = [[] for _ in range(Node+1)]
distance = [INF] * (Node+1)

# Input graph information
for _ in range(Edge):
    start, end, weight = map(int, input().split())
    graph[start].append((end, weight))
    graph[end].append((start, weight))
    
fir, sec = map(int, input().split())
        
# Print answer
answer = min(way1, way2)
print(answer)

Import 부분에서 다익스트라로 사용할 heapq와 빠른 입력을 위한 sys을 호출했다.

Function 부분에서 시작(sNode)과 끝노드(eNode)를 받는 다익스트라 함수를 작성한다.

Main 부분에서는 문제에서 주어진 입력을 받는다.

그리고 마지막에 가장 짧은 부분을 찾아 정답을 출력한다.

 

# ---------- Main ----------
Node, Edge = map(int, input().split())
graph = [[] for _ in range(Node+1)]

# Input graph information
for _ in range(Edge):
    start, end, weight = map(int, input().split())
    graph[start].append((end, weight))
    graph[end].append((start, weight))

다익스트라 코딩 우선순위는 미뤘다.

왜냐하면 다익스트라는 이미 정해진 틀이 있기 때문이다. 즉,  다익스트라는 단순한 '계산'에 불과하다.

따라서 문제의 주의할 점과 흐름 작성이 먼저라고 생각하여 Main부터 작성하였다.

 

위의 코드는 N(Node)와 E(Edge), graph를 입력받는 부분이다.

이때 distance = [INF] * (Node+1) 부분은 지웠다. 이유는 문제에서 주어진다.

"한번 이동했던 정점은 물론, 한번 이동했던 간선도 다시 이동할 수 있다."

즉 START에서 fir, sec를 거쳐 END로 갈 때, 갔던 간선을 다시 이동할 수 있다는 이야기다.

그럼 연산을 나눠서 START에서 fir로, fir에서 sec로, sec에서 END로 가는 걸 따로 계산해야 한다는 말과 같다.

따라서 위의 distance 부분은 다익스트라를 호출할 때 초기화해줘야 한다. 그래서 함수 내부로 옮겨주었다.

 

그밖에 주의할 점은 문제에서 "방향성이 없는 그래프"라고 주어진다.

start를 기준으로 end에 추가한다면, end를 기준으로 start도 추가해줘야 한다.

 

# ---------- Main ----------
# Two node that must pass
fir, sec = map(int, input().split())
way_distance = [0, 0]

way_condition = [
    [(1, fir), (fir, sec), (sec, Node)],
    [(1, sec), (sec, fir), (fir, Node)]
]

fir와 sec를 거쳐가는 방법은 두 가지가 있다.

1. START > fir > sec > END

2. START > sec > fir > END

 

둘 중 무엇이 더 빠른 방법인지 모르니, 둘 다 계산하고 비교해야 한다.

따라서 way_distance에는 각 방법의 최소 거리를 저장하는 리스트로 선언하고,

way_condition에는 각 방법의 경로를 저장하는 2차원 리스트로 선언한다.

way_distance와 way_condition은 index를 기준으로 mapping한다. 

 

# ---------- Main ----------
# Print answer
way1 = way_distance[0]
way2 = way_distance[1]

if (way1 is None) and (way2 is None): print(-1)
elif way1 is None: print(way2)
elif way2 is None: print(way1)
else: print(min(way1, way2))

문제에서 "그러한 경로가 없을 때에는 -1을 출력한다"라고 적혀있어서 의문이 들었다.

그럼 1번과 2번 경로가 모두 존재하지 않는 경우여야 하는 것 아닌가?

아 그러면 1번만 없을 때, 2번만 없을 때, 1번과 2번 모두 없을 때가 따로 존재하겠구나.

그래서 위의 형태로 코드를 작성했다.

이때 없다는 것을 확실하게 표현하기 위해 None으로 값을 할당했다.

 

# ---------- Main ----------
# Find shortest way
for idx, condition in enumerate(way_condition):
    way = 0
    
    for s, e in condition:
        try:
            way += Dijkstra(s, e)
        except TypeError:
            way = None
            break
    
    way_distance[idx] = way

가장 짧은 거리를 구하는 알고리즘이다.

경로가 존재하지 않는 경우를 None으로 하기로 했다. 하지만 최소 거리는 int 자료형이다.

Dijkstra 함수에서 경로가 없으면 None을 반환할 것이고, 이러면 TypeError가 발생한다.

NoneType과 Int는 서로 더할 수 없다. 따라서 try-except를 활용한다.

 

만약 중간에 한 번이라도 TypeError가 발생한다면, 해당 경로는 미완성이 된다.

따라서 except로 빠질 경우, way에 None을 할당하고, break로 빠져나온다.

그게 아니라면 계속해서 way에 최단 거리(Dijkstra(s, e))를 구하여 더해준다.

최종적으로 다 더한 way는 idx를 기준으로 way_distance에 접근하여 값을 저장한다.

이러면 기본적인 흐름은 전부 작성했다.

이렇게 작성하는데 20분이 조금 넘게 걸렸다.

 

# ---------- Function ----------
def Dijkstra(sNode: int, eNode: int):
    # Start node and distance Init
    distance = [INF] * (Node+1)
    distance[sNode] = 0
    
    # Init
    Q = []
    heapq.heappush(Q, (0, sNode))   # cost, node

기본적인 다익스트라 함수는 sNode(출발 노드)만 받아서, 전체 노드의 최단 거리를 반환한다.

하지만 이 문제에서는 굳이 그럴 필요가 없다고 생각했다.

기점과 종점이 정해져 있으니, 두 노드만 확인하면 되는 거 아닌가?

해서 sNode와 eNode 두 값을 매개변수로 받아들인다.

그 다음 자기 자신의 거리를 0으로 초기화하고, Q에 (cost, node)를 쌍으로 추가한다.

 

# ---------- Function ----------
# Do dijkstra
while Q:
    cost, current_node = heapq.heappop(Q)

    # End condition
    if current_node == eNode:
        return distance[eNode]

    # Check nodes connected with current node
    for next_node, weight in graph[current_node]:
        new_cost = cost + weight

        # New cost is more smallest
        if new_cost < distance[next_node]:
            distance[next_node] = new_cost
            heapq.heappush(Q, (new_cost, next_node))

# No way to go eNode
return None

다익스트라 알고리즘에서 핵심인 부분이다.

Q가 존재한다면 계속해서 pop을 하면서 확인한다.

이때 꺼낸 node(current_node)가 입력받은 종점(eNode)과 일치한다면 조기 종료한다.

내가 원하는 것은 기점과 종점 사이의 최단 거리뿐이지, 다른 것은 불필요하다.

 

아니라면 current_node와 연결된 값들을 하나하나 둘러본다(graph[current_node]). 

이때 새로운 거리(new_cost)가 기존의 거리(distance[next_node])보다 작다면, 업데이트한다.

위 과정을 전부 돌았음에도 불구하고 조기 종료가 되지 않을 수 있다.

이 말은 종점으로 가는 길을 찾지 못했다는 말이다.

return None으로 '갈 수 없음'을 반환하고 함수를 종료한다.

 

다행히도 이번에는 놓친 부분이 없었다.

한 번에 AC를 받을 수 있었다.

 

친구가 코딩 테스트 연습을 할 때 스톱워치를 켜두고 문제를 푼다고 했다.

자기가 얼마나 시간을 소모했고, 얼마나 줄여야 하는지 눈에 보인다고 하길래 이번에 시간을 측정해봤다. 

다행히 한 번에 맞아 45분이 조금 안 걸리는 시간이긴 하다.

이정도면 딱 '무난하다'  그 이상도 이하도 아닌 것 같다.

다익스트라의 구조만 제대로 외운다면 시간을 30분으로 줄일 수 있을 것 같다.

내게 부족한 알고리즘을 외우는 데 시간을 할애해야 겠다.

'PS > 그래프' 카테고리의 다른 글

[백준] No.1707 이분 그래프 完  (0) 2024.01.11
[백준] No.1504 특정한 최단 경로 完  (0) 2023.11.16
[백준] No.1043 거짓말 完  (0) 2023.09.27
[백준] No.1753 최단 경로 完  (0) 2023.08.13
[백준] No.1753 최단 경로 01  (0) 2023.08.13
# ---------- Import ----------
import sys
input = sys.stdin.readline

# ---------- Main ----------
people, M = map(int, input().split())
truthy = set(list(map(int, input().split()))[1:])

parties = [set(list(map(int, input().split()))[1:]) for _ in range(M)]

for _ in range(M):
    for party in parties:
        if party & truthy:
            truthy = truthy.union(party)
            
cnt = 0
for party in parties:
    if party & truthy: continue
    cnt += 1
    
print(cnt)
# ---------- Import  ----------
import heapq
import sys
input = sys.stdin.readline

INF = 1e9

# ---------- Function ----------
def Dijkstra(sNode):
    # Start node init
    distance[sNode] = 0
    
    # Heap init
    heap = []
    heapq.heappush(heap, (0, sNode))
    
    # Do dijkstra
    while heap:
        dist, current_node = heapq.heappop(heap)
        
        if distance[current_node] < dist:
            continue
        
        # Check nodes connected with current node
        for i in graph[current_node]:
            cost = dist + i[1]
            
            if cost < distance[i[0]]:
                distance[i[0]] = cost
                heapq.heappush(heap, (cost, i[0]))

# ---------- Main ----------
Vertex, Edge = map(int, input().split())
start = int(input())

# Init list
distance = [INF] * (Vertex+1)
graph = [[] for _ in range(Vertex+1)]

# Input weight in Edge times
for _ in range(Edge):
    u, v, w = map(int, input().split())
    graph[u].append((v, w))

# Doing dijkstra
Dijkstra(start)

# Print
for i in range(1, Vertex+1):
    print("INF") if distance[i] == INF else print(distance[i])

 

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가

www.acmicpc.net

ㅇㅇ

 

(공사 중)

 

# ---------- Import ----------
from collections import deque
import sys
input = sys.stdin.readline

# ---------- Function ----------
def BFS(row, col, graph, visited):
    # UP, DN, LFT, RGT
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]
    
    queue = deque([(x, y) for x in range(row) for y in range(col) if graph[x][y] == 2])
    visited[queue[0][0]][queue[0][1]] = 0
    
    while queue:
        x, y = queue.popleft()
        
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            
            if 0<=nx<row and 0<=ny<col and visited[nx][ny] == -1:
                if graph[nx][ny] == 0:
                    visited[nx][ny] = 0
                
                elif graph[nx][ny] == 1:
                    visited[nx][ny] = visited[x][y] + 1
                    queue.append((nx, ny))

    return visited

# ---------- Main ----------
row, col = map(int, input().split())
map_ = [list(map(int, input().split())) for _ in range(row)]

visited = [[-1] * col for _ in range(row)]
visited = BFS(row, col, map_, visited)

for r in range(row):
    for c in range(col):
        if not map_[r][c]: print(0, end=" ")
        else: print(visited[r][c], end=" ")
    print()

 

 

14940번: 쉬운 최단거리

지도의 크기 n과 m이 주어진다. n은 세로의 크기, m은 가로의 크기다.(2 ≤ n ≤ 1000, 2 ≤ m ≤ 1000) 다음 n개의 줄에 m개의 숫자가 주어진다. 0은 갈 수 없는 땅이고 1은 갈 수 있는 땅, 2는 목표지점이

www.acmicpc.net

ㅇㅇ

 

# ---------- Import ----------
from collections import deque
import sys
input = sys.stdin.readline

# ---------- Function ----------
def BFS(row, col, graph, visited):
    queue = deque()

    return

# ---------- Main ----------
row, col = map(int, input().split())
map_ = [list(map(int, input().split())) for _ in range(row)]

ㅇㅇ

 

# ---------- Main ----------
visited = [[-1] * col for _ in range(row)]
visited = BFS(row, col, map_, visited)

for line in visited:
    print(*line)

ㅇㅇ

 

# ---------- Function ----------
def BFS(row, col, graph, visited):
    # UP, DN, LFT, RGT
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]
    
    queue = deque([(x, y) for x in range(row) for y in range(col) if graph[x][y] == 2])
    visited[queue[0][0]][queue[0][1]] = 0
    
    return visited

ㅇㅇ

 

# ---------- Function ----------
while queue:
        x, y = queue.popleft()
        
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            
            if 0<=nx<row and 0<=ny<col and visited[nx][ny] == -1:
                if graph[nx][ny] == 0:
                    visited[nx][ny] = 0
                
                elif graph[nx][ny] == 1:
                    visited[nx][ny] = visited[x][y] + 1
                    queue.append((nx, ny))

ㅇㅇ

 

# ---------- Comment ----------
# Counter Example
# 2 3
# 2 0 0
# 0 1 0

# 5 5
# 2 0 0 0 0
# 0 1 1 1 0
# 0 1 0 1 0
# 0 1 1 1 0
# 0 0 0 0 0
# -------------------- Case 1 --------------------
# ---------- Import ----------
from itertools import combinations
from collections import deque
import copy
import sys
input = sys.stdin.readline

# ---------- Function ----------
def BFS(row: int, col: int, lab: list) -> int:
    result = 0
    FLAG = 0
    
    # UP, DN, LFT, RHT
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]
    
    # Checking empty(0) area that can be build a wall
    empty_space = [(x, y) for x in range(row) for y in range(col) if lab[x][y] == 0]
    for empty in combinations(empty_space, 3):
        graph = copy.deepcopy(lab)
        
        # Build a wall using combinations
        for wallX, wallY in empty:
            graph[wallX][wallY] = 1

        # Checking virus(2) area    
        queue = deque([(x, y) for x in range(row) for y in range(col) if graph[x][y] == 2])
        # queue = deque((x, y) for x in range(row) for y in range(col) if graph[x][y] == 2)
             
        # Starting BFS   
        while queue:
            x, y = queue.popleft()
            
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                
                if 0<=nx<row and 0<=ny<col and graph[nx][ny] == 0:
                    graph[nx][ny] = 2
                    queue.append((nx, ny))
                    
        # Checking safe(0) area
        cnt = 0
        for line in graph:
            cnt += line.count(0)
        
        result = max(cnt, result)        
        
    return result

# ---------- Main ----------
row, col = map(int, input().split())
lab = [list(map(int, input().split())) for _ in range(row)]

result = BFS(row, col, lab)
print(result)

1번 풀이, 파이썬 라이브러리 combinations를 활용한 방법

Python3에서도 시간 초과없이 무난하게 통과 가능하다.

 

# -------------------- Case 2: TLE (Pypy3: AC) --------------------
# ---------- Import ----------
from collections import deque
import copy
import sys
input = sys.stdin.readline

# ---------- Function ----------
def BFS():    
    queue = deque()
    graph = copy.deepcopy(lab)
    
    # Checking virus location
    for r in range(row):
        for c in range(col):
            if graph[r][c] == 2:
                queue.append([r, c])
    
    # BFS calculating
    while queue:
        x, y = queue.popleft()
        
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            
            if nx < 0 or ny < 0 or nx >= row or ny >= col: continue
            if graph[nx][ny] == 1 or graph[nx][ny] == 2: continue
            
            if graph[nx][ny] == 0:
                graph[nx][ny] = 2
                queue.append([nx, ny])
    
    # Count '0' area
    global result
    
    cnt = 0
    for r in range(row):
        for c in range(col):
            if graph[r][c] == 0:
                cnt += 1
    
    result = max(result, cnt)
    return 


def makeWall(cnt: int):
    # Wall is 3, then BFS
    if cnt == 3:
        BFS()
        return
    
    # Making wall using backtracking
    for r in range(row):
        for c in range(col):
            if lab[r][c] == 0:
                lab[r][c] = 1
                makeWall(cnt + 1)
                lab[r][c] = 0
                
    return


# ---------- Main ----------
row, col = map(int, input().split())
lab = [list(map(int, input().split())) for _ in range(row)]

# UP, DN, LFT, RGT
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

result = 0
makeWall(0)
print(result)

2번 풀이, Backtracking을 활용한 방법

Pypy3에서만 통과하고 Python3에서는 시간 초과가 뜬다.

 

 

14502번: 연구소

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

www.acmicpc.net

평범한 그래프 BFS 문제이다. 백준 2178번 미로와 비슷한 느낌이다.

'미로' 문제에서는 1이 이동 가능한 칸, 0이 이동 불가능한 칸이었다.

'연구소' 문제에서는 0이 이동 가능한 칸, 1이 이동 불가능한 칸이라는 차이점이 있다.

또한 미로에서는 정해진 위치 (1, 1)에서만 시작을 했다.

연구소에서는 정해진 위치 없이 2 값이 들어있는 모든 위치에 동시에 시작을 해야 한다.

 

이 BFS 문제에서 얻고자 하는 정답(출력)이 무엇인지 알아보면,

경우에 따라 벽을 세우고,  BFS로 0을 전부 2로 바꾼 다음, 남아있는 0의 개수를 센다.

이때 0의 개수가 최대가 될 때, 0의 개수를 물어보는 문제이다.

 └ 문제 성격상 BFS든 DFS든 상관없어 보이지만, 재귀 깊이 때문에 BFS로 해당 문제를 고려했다.

 

연구소 문제를 해석하면서 가장 의문인 점이 두 가지가 있었다.

하나는 바이러스(2)가 있는 곳은 동시다발적으로 퍼져야 할텐데... 이를 어떻게 구현하지?였고,

다른 하나는 조건에서 무조건 3개의 벽을 세워야 한다는데... 이게 제한 시간 내에 가능할까?였다.

 

# Pseudo Code BFS
dfs BFS():
    queue = [ ''' somthing elements ''' ]
    
    while queue:
        tmp = queue.popleft()
        
        if tmp not visit:
            tmp = visit True
            queue.append(tmp)

의문을 하나하나 생각해보자. 위처럼 BFS의 수도 코드를 작성하고 생각했다.

그리고 곧 첫 번째 의문은 굉장히 멍청한 의문이었음을 깨달았다...

처음에는 모든 바이러스가 동시다발적으로 일어나야 하니까 멀티스레드 구현을 해야 하나?라고 생각했다.

하지만 이 문제는 바이러스가 얼마나 빨리 퍼지는지에 대한 문제가 아니다.

'바이러스가 퍼진 뒤' 남아있는 공간을 세는 문제이다.

따라서  BFS나 DFS로 그래프를 전부 탐색한 다음, 남아있는 0을 세면 된다.

 

또한, BFS 코드 특성상 들어온 순서대로 하나씩 처리를 한다.

위의 그림을 예로 들어보자면, 녹색 부분이 바이러스 부분이라고 가정해보자. 이때 벽은 없다.

그 다음으로 퍼져나간 곳이 빨강이고, 그 다음으로 퍼져나간 곳이 파랑이다.

이때 한 바이러스 부분을 먼저 계속해서 깊이 탐색해 나가지 않는다. (이는 DFS이다.)

'녹 > 녹 > 녹 > 빨 > 빨 > 빨 > 빨 > ... > 파 > 파' 이런식으로 들어온 순서대로 탐색을 진행한다.

사실... 남아있는 0의 개수만을 고려하는 문제이기에 애초에 큰 상관이 없었다.

 

from itertools import combinations

all_number_of_case = [ ''' something elements ''' ]

combinations(all_number_of_case, length)

다른 의문 하나는, 시간 초과에 대한 의문이다.

문제를 읽어보면 "벽의 개수는 3개이며, 꼭3개를 세워야 한다."라고 하는 조건이 있다.

하지만 어디에 어떻게 3개를 세우라는 말은 없으니, 브루트포스 문제라고 생각했다.

그리고 정확하게 알고리즘 분류에 브루트포스 알고리즘이 들어있었다.

(갈수록 문제를 정확하게 읽는 것 같아 성장하는 느낌이 든다.)

브루트포스가 떠오르자마자 생각난 방법은 파이썬 라이브러리를 사용하는 방법이었다.

(빠르기도 하지만, 백트래킹 구현을 많이 하지 않아서 자신이 없어 가장 먼저 떠오른 것 같다.)

 

입력 조건을 살펴본다면 가로 세로의 길이는 최소 3, 최대 8이다.

최악의 경우 8 * 8 사이즈에서 바이러스가 하나만 있는 경우이다.

63칸에서 3개의 벽을 세울 수 있는 경우의 수는 63_C_3으로 39,711가지이다.

만 단위정도라면 제한 시간 2초 내에 충분히 계산이 가능하리라 생각했다.

 

# ---------- Import ----------
from itertools import combinations
from collections import deque
import sys
input = sys.stdin.readline

가장 먼저 Import 부분을 작성해주었다.

조합 계산에 사용할 combinatinos, 그래프 계산에 사용할 deque, 빠른 입력을 위한 sys를 사용한다.

 

# ---------- Function ----------
def BFS(row: int, col: int, lab: list) -> int:
    result = 0

    return result

# ---------- Main ----------
row, col = map(int, input().split())
lab = [list(map(int, input().split())) for _ in range(row)]

result = BFS(row, col, lab)
print(result)

그리고 Function을 간단하게, Main 부분을 상세하게 작성해주었다.

코딩을 시작할 때는 늘 이런 방식으로 Import와 Main 틀을 작성하고, 함수들을 작성한다.

comprehension으로 연구소(lab) 2차원 배열을 입력받는다.

result에 BFS를 계산한 결과를 넣고 출력한다.

 

# ---------- Function ----------
def BFS(row: int, col: int, lab: list) -> int:
    result = 0
    
    # UP, DN, LFT, RHT
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]

BFS에서 row, col, lab을 입력받는다.

최종 결과를 저장할 result 변수를 선언한다.

많은 BFS 문제를 풀어봤다면 익숙할 네 방향으로 이동하기 위한 dx, dy 리스트도 선언해준다.

 

# ---------- Import ----------
import copy

# ---------- Function ----------
# Checking empty(0) area that can be build a wall
empty_space = [(x, y) for x in range(row) for y in range(col) if lab[x][y] == 0]

for empty in combinations(empty_space, 3):
    graph = copy.deepcopy(lab)

    # Build a wall using combinations
    for wallX, wallY in empty:
        graph[wallX][wallY] = 1

    # Checking virus(2) area    
    queue = deque([(x, y) for x in range(row) for y in range(col) if graph[x][y] == 2])

lab에서 우선 빈 공간(0)의 위치들을 알아내야 한다. 그래야 combinations으로 조합이 가능하기 때문이다.

comprehension으로 (x, y)를 알아낸 뒤, empty_space에 저장한다.

그리고 for문으로 가능한 모든 조합의 수에 대해서 반복을 진행한다.

이때 BFS를 진행해야 하는데, 한 번 진행하고 나면 기존 BFS는 없어지기에 이를 유지할 필요가 생겼다.

파이썬의 copy 라이브러리를 사용하여 기존의 lab을 유지해준다.

 └ graph = lab을 해주면 이는 얕은 복사라 값이 같이 바뀐다. 깊은 복사를 위해 deepcopy를 해줘야 한다.

 

이러면 empty 변수에는 ((0, 1), (0, 2), (0, 3)) 다음에 ((0, 1), (0, 2), (0, 4)) 이런 식으로 3개의 벽 위치가 들어온다.

for문으로 돌면서 벽 위치를 1로 바꿔준다.

그리고 queue에 virus의 위치를 저장한다. 이 또한 for와 if의 comprehension으로 작성해주었다.

이때 BFS를 사용해야 하기에 deque로 저장했다.

 

# ---------- Function ----------
# Starting BFS   
while queue:
    x, y = queue.popleft()

    for i in range(4):
        nx = x + dx[i]
        ny = y + dy[i]

        if 0<=nx<row and 0<=ny<col and graph[nx][ny] == 0:
            graph[nx][ny] = 2
            queue.append((nx, ny))

# Checking safe(0) area
cnt = 0
for row in graph:
    cnt += row.count(0)

result = max(cnt, result)

일반적인 BFS 함수 부분이다.

x, y를 queue 가장 왼쪽(첫 번째) 원소를 꺼내어 저장한 다음, 네 방향을 계산한다.

이때 범위를 벗어나지 않고, 갈 수 있는 상황(0)이라면,

바이러스가 퍼진 상태(2)로 바꾸고, 그 위치를 virus의 위치를 나타내는 queue에 저장한다.

 

마지막으로 cnt 변수에 각 combinations 경우에 대한 0 개수를 저장한다.

result와 비교하여 더 큰 값을 result에 저장하고 마지막으로 return한다.

이러면 함수 작성을 완료다. 코드가 길어지기는 하지만 생각보다는 풀만한 문제였다. 

 

queue = deque((x, y) for x in range(row) for y in range(col) if graph[x][y] == 2)
                              ^^^^^^^^^^
TypeError: 'list' object cannot be interpreted as an integer

그리고 에러가 떴다.

range(row)가 list라서 range() 사용이 불가능하다...?

분명 int로 받아줬는데 왜 자꾸 list 타입으로 뜨는 건지 이해할 수 없었다.

breakpoint() 함수로 확인해보니 처음은 <int>로 나오지만 두 번째부터는 <list>로 인식했다.

그럼 처음은 문제가 없지만, 한 바퀴 돌고 나서 문제이니... 코드에서 에러가 있다는 거였다.

int를 list로 바꿔주는 식의 형변환 코드는 없고... row 변수에서 문제가 생겼으니 row 변수를 확인해봤다.

 

마지막 코드 for row in graph에서 row 변수를 한 번 더 사용했다...

여기서 list를 덮어써서 제대로 동작하지 않은 것이었다.

변수 사용함에 있어서도 제대로 확인을 해줘야겠다.

 

# Before
cnt = 0
for row in graph:
    cnt += row.count(0)

result = max(cnt, result)

# After
cnt = 0
for line in graph:
    cnt += line.count(0)

result = max(cnt, result)

겹치는 row 변수에서 line이라는 다른 변수로 바꿔주었다.

그랬더니 정상적으로 코드가 실행되었다.

 

다행히 시간 초과가 뜨지 않고 정상적으로 완료했다.

백트래킹으로 작성하는 것도 한번 연습해봐야겠다.

 └ 알아보니 백트래킹을 이용하면 Pypy3에서만 AC가 뜨고 Python3에서는 TLE가 뜬다.

 

P.S.

전에는 이틀이나 사흘에 걸쳐 문제를 고민하고는 했는데...

어째 요즘에는 하루만에 끝나는 경우가 태반이다.

하루종일 시간을 잔뜩 갈아넣어서 하루만에 끝나는 건지... 생각이 늘어난 건지 구분이 가지 않는다.

좋은지 나쁜지 판단하는 거는 아직 시기상조일지도

+ Recent posts