철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자모양 상자의 칸에 하나씩 넣은 다음, 상자들을 수직으로 쌓아 올려서 창고에 보관한다.

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토에 인접한 곳은 위, 아래, 왼쪽, 오른쪽, 앞, 뒤 여섯 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지 그 최소 일수를 알고 싶어 한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

입력

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N과 쌓아올려지는 상자의 수를 나타내는 H가 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M ≤ 100, 2 ≤ N ≤ 100, 1 ≤ H ≤ 100 이다. 둘째 줄부터는 가장 밑의 상자부터 가장 위의 상자까지에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 하나의 상자에 담긴 토마토의 정보가 주어진다. 각 줄에는 상자 가로줄에 들어있는 토마토들의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0 은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다. 이러한 N개의 줄이 H번 반복하여 주어진다.

토마토가 하나 이상 있는 경우만 입력으로 주어진다.

출력

여러분은 토마토가 모두 익을 때까지 최소 며칠이 걸리는지를 계산해서 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

 


코드

import sys
from collections import deque

input = sys.stdin.readline

m, n, h = map(int, input().split())
matrix = [[list(map(int, input().split())) for _ in range(n)] for _ in range(h)]  # 3차원 배열

q = deque()
has_ripe = False

for z in range(h):
    for y in range(n):
        for x in range(m):
            if matrix[z][y][x] == 1:
                q.append((z, y, x))
                has_ripe = True

if not has_ripe:
    print(-1)
    sys.exit()

# 6방향 이동
dx = [1, -1, 0, 0, 0, 0]
dy = [0, 0, 1, -1, 0, 0]
dz = [0, 0, 0, 0, 1, -1]

days = -1
while q:
    for _ in range(len(q)):  # 하루 단위로 확산
        z, y, x = q.popleft()
        for i in range(6):
            nz, ny, nx = z + dz[i], y + dy[i], x + dx[i]
            if 0 <= nz < h and 0 <= ny < n and 0 <= nx < m and matrix[nz][ny][nx] == 0:
                matrix[nz][ny][nx] = 1
                q.append((nz, ny, nx))
    days += 1

for z in range(h):
    for y in range(n):
        for x in range(m):
            if matrix[z][y][x] == 0:
                print(-1)
                sys.exit()

print(max(0, days))

 

문제를 3차원 BFS로 모델링

  • matrix[h][n][m] 형태의 3차원 배열을 이용하여 토마토 저장 상태를 나타낸다.
  • BFS(너비 우선 탐색)를 사용하여 동시에 여러 방향으로 퍼지는 문제를 해결한다.
  • deque를 사용하여 익은 토마토(1) 위치를 큐에 저장하고, BFS로 확산시킨다.

- BFS가 끝난 후에도 익지 않은 토마토(0)가 남아 있으면 -1 출력
- 모든 토마토가 익었다면, days 값을 출력

-

이미 다 익어 있는 경우(days == -1)라면 max(0, days)를 사용하여 0 출력

 

시간복잡도

  • 입력 크기: m × n × h
  • BFS 탐색 수행 시, 최악의 경우 모든 칸을 방문하므로 O(m × n × h)
  • sys.stdin.readline() 사용으로 입력 속도 최적화
  • deque를 이용하여 O(1) 시간 복잡도로 빠르게 탐색

'Algorithm' 카테고리의 다른 글

[백준] 2573 빙산 / python  (0) 2025.03.13
[백준] 2178 미로탐색 / python  (0) 2025.03.11
[백준] 2644 촌수계산 / python  (0) 2025.03.09
[백준] 15591 MooTube / python  (0) 2025.03.09
세그먼트 트리 (Segment Tree)  (4) 2024.07.25
 

우리 나라는 가족 혹은 친척들 사이의 관계를 촌수라는 단위로 표현하는 독특한 문화를 가지고 있다. 이러한 촌수는 다음과 같은 방식으로 계산된다. 기본적으로 부모와 자식 사이를 1촌으로 정의하고 이로부터 사람들 간의 촌수를 계산한다. 예를 들면 나와 아버지, 아버지와 할아버지는 각각 1촌으로 나와 할아버지는 2촌이 되고, 아버지 형제들과 할아버지는 1촌, 나와 아버지 형제들과는 3촌이 된다.

여러 사람들에 대한 부모 자식들 간의 관계가 주어졌을 때, 주어진 두 사람의 촌수를 계산하는 프로그램을 작성하시오.

입력

사람들은 1, 2, 3, …, n (1 ≤ n ≤ 100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어진다. 그리고 셋째 줄에는 부모 자식들 간의 관계의 개수 m이 주어진다. 넷째 줄부터는 부모 자식간의 관계를 나타내는 두 번호 x,y가 각 줄에 나온다. 이때 앞에 나오는 번호 x는 뒤에 나오는 정수 y의 부모 번호를 나타낸다.

각 사람의 부모는 최대 한 명만 주어진다.

출력

입력에서 요구한 두 사람의 촌수를 나타내는 정수를 출력한다. 어떤 경우에는 두 사람의 친척 관계가 전혀 없어 촌수를 계산할 수 없을 때가 있다. 이때에는 -1을 출력해야 한다.

 


코드

import sys
from collections import deque

input = sys.stdin.readline

n = int(input())
a, b = map(int, input().split())
m = int(input())
graph = [[] for _ in range(n+1)]

for _ in range(m):
    p, q = map(int, input().split())
    graph[p].append(q)
    graph[q].append(p)

q = deque([(a, 0)]) # (시작 사람, 촌수)
visited = [False] * (n+1)
visited[a] = True

while q:
    person, degree = q.popleft()

    if person == b:
        print(degree)
        sys.exit()

    for next in graph[person]:
        if not visited[next]:
            visited[next] = True
            q.append((next, degree + 1))

print(-1) # 연결되지 않은 경우

 

앞 문제와 마찬가지로 연결 리스트로 BFS를 구현했다.

target에 도달하면 촌수를 출력하고 종료하도록 하였고,

탐색이 끝났음에도 도달하지 못했다면 -1을 출력하도록 하였다.

시간복잡도는 BFS 복잡도와 같은 O(n)이다.

'Algorithm' 카테고리의 다른 글

[백준] 2178 미로탐색 / python  (0) 2025.03.11
[백준] 7569 토마토 / python  (0) 2025.03.10
[백준] 15591 MooTube / python  (0) 2025.03.09
세그먼트 트리 (Segment Tree)  (4) 2024.07.25
[백준] 6603 로또 / python  (0) 2024.05.03

농부 존은 남는 시간에 MooTube라 불리는 동영상 공유 서비스를 만들었다. MooTube에서 농부 존의 소들은 재밌는 동영상들을 서로 공유할 수 있다. 소들은 MooTube에 1부터 N까지 번호가 붙여진 N (1 ≤ N ≤ 5,000)개의 동영상을 이미 올려 놓았다. 하지만, 존은 아직 어떻게 하면 소들이 그들이 좋아할 만한 새 동영상을 찾을 수 있을지 괜찮은 방법을 떠올리지 못했다.

농부 존은 모든 MooTube 동영상에 대해 “연관 동영상” 리스트를 만들기로 했다. 이렇게 하면 소들은 지금 보고 있는 동영상과 연관성이 높은 동영상을 추천 받을 수 있을 것이다.

존은 두 동영상이 서로 얼마나 가까운 지를 측정하는 단위인 “USADO”를 만들었다. 존은 N-1개의 동영상 쌍을 골라서 직접 두 쌍의 USADO를 계산했다. 그 다음에 존은 이 동영상들을 네트워크 구조로 바꿔서, 각 동영상을 정점으로 나타내기로 했다. 또 존은 동영상들의 연결 구조를 서로 연결되어 있는 N-1개의 동영상 쌍으로 나타내었다. 좀 더 쉽게 말해서, 존은 N-1개의 동영상 쌍을 골라서 어떤 동영상에서 다른 동영상으로 가는 경로가 반드시 하나 존재하도록 했다. 존은 임의의 두 쌍 사이의 동영상의 USADO를 그 경로의 모든 연결들의 USADO 중 최솟값으로 하기로 했다.

존은 어떤 주어진 MooTube 동영상에 대해, 값 K를 정해서 그 동영상과 USADO가 K 이상인 모든 동영상이 추천되도록 할 것이다. 하지만 존은 너무 많은 동영상이 추천되면 소들이 일하는 것이 방해될까 봐 걱정하고 있다! 그래서 그는 K를 적절한 값으로 결정하려고 한다. 농부 존은 어떤 K 값에 대한 추천 동영상의 개수를 묻는 질문 여러 개에 당신이 대답해주기를 바란다.

입력

입력의 첫 번째 줄에는 N과 Q가 주어진다. (1 ≤ Q ≤ 5,000)

다음 N-1개의 줄에는 농부 존이 직접 잰 두 동영상 쌍의 USADO가 한 줄에 하나씩 주어진다. 각 줄은 세 정수 pi, qi, ri (1 ≤ pi, qi ≤ N, 1 ≤ ri ≤ 1,000,000,000)를 포함하는데, 이는 동영상 pi와 qi가 USADO ri로 서로 연결되어 있음을 뜻한다.

다음 Q개의 줄에는 농부 존의 Q개의 질문이 주어진다. 각 줄은 두 정수 ki와 vi(1 ≤ ki ≤ 1,000,000,000, 1 ≤ vi ≤ N)을 포함하는데, 이는 존의 i번째 질문이 만약 K = ki라면 동영상 vi를 보고 있는 소들에게 몇 개의 동영상이 추천될 지 묻는 것이라는 것을 뜻한다.

출력

Q개의 줄을 출력한다. i번째 줄에는 농부 존의 i번째 질문에 대한 답변이 출력되어야 한다.

 

 


 

코드

from collections import deque
import sys

# 빠른 입력 받기
input = sys.stdin.readline

n, q = map(int, input().split())
graph = [[] for _ in range(n+1)]

for i in range(n-1):
    a, b, usado = map(int, input().split())
    graph[a].append((b, usado))
    graph[b].append((a, usado))


def bfs(graph, n, k, start):
    queue = deque([start])
    visited = [False] * (n+1)
    visited[start] = True
    result = 0

    while queue:
        node = queue.popleft()
        for neighbor, usado in graph[node]:
            if not visited[neighbor] and usado >= k:
                visited[neighbor] = True
                queue.append(neighbor)
                result += 1
    
    return result

output = []
for _ in range(q):
    k, v = map(int, input().split())
    output.append(str(bfs(graph, n, k, v)))

sys.stdout.write("\n".join(output) + "\n")

 

  • 입력으로 주어진 N-1개의 간선 정보를 이용해 무방향 그래프를 만든다.
  • 각 노드에는 연결된 노드와 USADO 값을 저장한다.
  • 이를 위해 인접 리스트(Adjacency List)를 사용하며, graph[i]에는 (연결된 노드, USADO 값) 형태의 튜플을 저장한다.
  • BFS는 deque(덱)을 사용해 탐색하며, k 이상의 USADO 값을 가지는 경로만 이동한다.
  • 시작 노드 v에서 출발하여, 방문할 수 있는 노드를 하나씩 탐색하며 USADO 값이 k 이상인 노드만 방문한다.
  • 방문한 노드의 개수를 result 변수에 저장하고, 최종적으로 result 값을 반환한다.

 

 

 

'Algorithm' 카테고리의 다른 글

[백준] 7569 토마토 / python  (0) 2025.03.10
[백준] 2644 촌수계산 / python  (0) 2025.03.09
세그먼트 트리 (Segment Tree)  (4) 2024.07.25
[백준] 6603 로또 / python  (0) 2024.05.03
[백준] 11279 최대 힙 / python  (4) 2024.05.01

 

세그먼트 트리 (Segment Tree)

주어진 배열의 구간에 대한 특정 연산을 빠르게 처리하기 위한 자료 구조이다.

일반적으로 구간 합, 구간 최댓값, 구간 최솟값 등의 연산을 수행하는 데 사용된다.

주요 특징

1. 구간 연산의 빠른 처리 : 세그먼트 트리는 구간에 대한 쿼리를 O(logn) 시간에 처리할 수 있다.

2. 동적 업데이트 : 배열의 요소가 변경될 때도 세그먼트 트리를 업데이트하는 데 O(logn) 시간이 걸린다.

 

세그먼트 트리의 구조

세그먼트 트리는 주어진 배열을 이진 트리 형태로 표현한다.

각 노드는 배열의 특정 구간에 대한 정보를 저장한다.

루트 노드는 전체 배열의 구간 정보를 포함하고, 자식 노드는 반으로 나눈 하위 구간의 정보를 포함한다.

각 노드에는 해당 구간의 합 또는 최댓값 등 원하는 연산의 결과를 저장할 수 있다.

 

세그먼트 트리의 구현

1. 트리 구축(Build) :  주어진 배열을 기반으로 세그먼트 트리를 구축한다.

2. 쿼리 처리(Query) : 특정 구간에 대한 연산 결과를 요청 받았을 때 트리의 정보를 사용하영 응답한다.

3. 업데이트(Update) : 배열의 특정 요소가 변경될 때 트리의 관련 정보를 업데이트 한다.

 

세그먼트 트리는 포화 이진트리 (Full Binary Tree)이다.

크기가 n인 배열을 가지고 리프 노드가 n개인 세그먼트 트리를 만들 때 필요한 노드 수는 다음과 같다.

높이가 h = ceil(log2(n)) 이라면

필요한 크기는 2**(h+1)-1이며 편의를 위해서 2**(h+1) 또는 4n으로 크기를 정하기도 한다.

 

1. 트리 구축

- 세그먼트 트리는 재귀를 사용하여 구축할 수 있다.

- 트리를 순회하면서 작업을 진행한다.

1. 리프 노드라면 배열의 요소를 저장한다.

2. 내부 노드라면 구간 정보를 저장한다.

 

2. 쿼리 처리

- 세그먼트 트리에서 구간 정보를 가져온다.

트리를 순회

-> 노드가 나타내는 범위가 지정된 범위 밖에 있다면 0을 반환

-> 노드가 나타내는 범위가 지정된 범위 내에 있다면 값을 반환

-> 노드가 나타내느 범위가 지정된 범위 일부만 포함한다면 왼쪽 자식과 오른쪽 자식의 합을 반환

 

노드가 나타내는 범위가 start, end 이고 지정된 범위는 left, right

 

3. 업데이트

i번째 배열 값을 var 값으로 변경하는 작업을 한다.

업데이트는 i가 포함된 구간을 담당하는 노드들만 변경한다.

트리를 순회

-> i가 포함된 구간을 가지고 있는 자식 노드로 재귀

-> 리프 노드라면 배열 값을 변경

-> 내부 노드라면 구간 정보를 저장

 

 

변화량(diff)을 사용한 업데이트 구현

i번째 배열 값을 val값으로 변경할 때 변화량은 diff = val - A[i] 이다.

i가 포함된 구간을 가진 노드들만 diff만큼 증가시키는 방식으로 구현

'Algorithm' 카테고리의 다른 글

[백준] 2644 촌수계산 / python  (0) 2025.03.09
[백준] 15591 MooTube / python  (0) 2025.03.09
[백준] 6603 로또 / python  (0) 2024.05.03
[백준] 11279 최대 힙 / python  (4) 2024.05.01
[백준] 1874 스택 수열 / python  (0) 2024.05.01

 

 

독일 로또는 {1, 2, ..., 49}에서 수 6개를 고른다.

로또 번호를 선택하는데 사용되는 가장 유명한 전략은 49가지 수 중 k(k>6)개의 수를 골라 집합 S를 만든 다음 그 수만 가지고 번호를 선택하는 것이다.

예를 들어, k=8, S={1,2,3,5,8,13,21,34}인 경우 이 집합 S에서 수를 고를 수 있는 경우의 수는 총 28가지이다. ([1,2,3,5,8,13], [1,2,3,5,8,21], [1,2,3,5,8,34], [1,2,3,5,13,21], ..., [3,5,8,13,21,34])

집합 S와 k가 주어졌을 때, 수를 고르는 모든 방법을 구하는 프로그램을 작성하시오.

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있다. 첫 번째 수는 k (6 < k < 13)이고, 다음 k개 수는 집합 S에 포함되는 수이다. S의 원소는 오름차순으로 주어진다.

입력의 마지막 줄에는 0이 하나 주어진다. 

출력

각 테스트 케이스마다 수를 고르는 모든 방법을 출력한다. 이때, 사전 순으로 출력한다.

각 테스트 케이스 사이에는 빈 줄을 하나 출력한다.

 

 

코드

import sys
input = sys.stdin.readline

def dfs(arr, s, idx, cnt):
    if cnt == 6:
        print(*arr)
        return

    for i in range(idx, len(s)):
        arr[cnt] = s[i]
        dfs(arr, s, i+1, cnt+1)

while True:
    n = list(map(int, input().split()))

    if n[0] == 0:
        break

    k = n[0]
    s = n[1:]
    arr = [0]*6
    dfs(arr, s, 0, 0)
    print()

 

재귀로 dfs를 구현하여 풀었다.

입력이 오름차순으로 되어 있으니 재귀함수를 호출할 때

기존에 뽑았던 인덱스보다 이전의 인덱스를 뽑지 않아야 한다. 

 

 

널리 잘 알려진 자료구조 중 최대 힙이 있다. 최대 힙을 이용하여 다음과 같은 연산을 지원하는 프로그램을 작성하시오.

  1. 배열에 자연수 x를 넣는다.
  2. 배열에서 가장 큰 값을 출력하고, 그 값을 배열에서 제거한다.

프로그램은 처음에 비어있는 배열에서 시작하게 된다.

 

입력

첫째 줄에 연산의 개수 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 N개의 줄에는 연산에 대한 정보를 나타내는 정수 x가 주어진다. 만약 x가 자연수라면 배열에 x라는 값을 넣는(추가하는) 연산이고, x가 0이라면 배열에서 가장 큰 값을 출력하고 그 값을 배열에서 제거하는 경우이다. 입력되는 자연수는 231보다 작다.

 

출력

입력에서 0이 주어진 횟수만큼 답을 출력한다. 만약 배열이 비어 있는 경우인데 가장 큰 값을 출력하라고 한 경우에는 0을 출력하면 된다.

 

코드

import sys
import heapq
input = sys.stdin.readline

n = int(input())
arr = []

for _ in range(n):
    x = int(input())
    if x == 0:
        if arr:
            print(-arr[0])
            heapq.heappop(arr)
        else:
            print(0)
    else:
        heapq.heappush(arr, -x)

 

heapq 모듈을 사용하여 힙 구조를 구현하였다.

처음에는 heappop(), heappush() 함수가 최대힙으로도 동작하는 방법이 있는 줄 알았다.

하지만.. 최소힙만 동작하기 때문에 최대힙을 구현하기 위해서는 다른 접근이 필요하다.

 

구조 자체는 최소힙과 동일하게 동작한다.

하지만 힙에 push 할 때 값을 음수로 변환하여 가장 작은 입력값이 힙 내에서는 가장 큰 값이 되도록 한다.

값을 추출할 때는 최소힙으로 동작하기 때문에 가장 작은 값이 추출된다.

이 값을 다시 음수로 변환하면 처음 입력값과 동일한 양수 값이 되고,

결론적으로 가장 큰 값이 된다.

+ Recent posts