728x90
반응형

https://www.acmicpc.net/problem/16562

 

16562번: 친구비

첫 줄에 학생 수 N (1 ≤ N ≤ 10,000)과 친구관계 수 M (0 ≤ M ≤ 10,000), 가지고 있는 돈 k (1 ≤ k ≤ 10,000,000)가 주어진다. 두번째 줄에 N개의 각각의 학생이 원하는 친구비 Ai가 주어진다. (1 ≤ Ai ≤ 10,

www.acmicpc.net

문제 설명

(문제를 다 읽고나서 일단 슬펐다 ㅠㅠ) 준석이는 친구를 사귀기 위해서 친구비를 내야되는데 친구의 친구는 똑같이 준석이의 친구이므로, 만약 A라는 친구를 사귀면 A의 친구들도 모두 준석이의 친구가 되는 논리이다. 그래서 k원이 있을 때 준석이는 모든 친구를 사귈 수 있는지에 대해서 판단하는 문제였다.

문제 해설

먼저 친구들끼리는 이미 연관관계가 있을 것이다. 해당 정보를 graph에 1차원 배열로 정리하였다. (혹시나 2차원으로 정의했을 경우 시간초과에 대비했다)
그리고 친구가 되었는지 아닌지를 판단하기 위해 visited라는 변수를 두었다.
bfs함수를 정의하였다. bfs함수는 시작 노드를 파라미터로 입력받는다.

그리고 bfs를 진행하면서 연결된 모든 노드들을 확인하고 친구비가 최소인 값을 반환한다.

즉, 만약 1번 3번 5번 친구의 친구비가 각각 10,20,30 이고 서로 연결되어있다고 가정하자.

bfs(1)을 넣으면 1,3,5은 visited가 True로 바뀌고 제일 최소값인 10이 반환된다.

(연결된 친구들 정보도 컴파일을 위해 member 집합으로 확인하였다)

그리고 n번 반복해서 해당 노드를 방문하지 않았을 경우 bfs(node값)을 해주면 된다.

 

그리고 반환값은 최소이므로 해당 값들을 모두 더해준것이 정답이 된다. 그리고 해당 정답이 k보다 크다면 문제에서 주어진대로 Oh no를 출려해주면 된다.

from collections import deque

n, m, k = map(int, input().split())  # 학생 수, 친구관계 수, 가지고 있는 돈
costs = list(map(int, input().split()))  # 친구별 필요한 돈
graph = [[] for _ in range(n)]

for _ in range(m):
    v, w = map(int, input().split())
    graph[v - 1].append(w - 1)
    graph[w - 1].append(v - 1)
temp = [i for i in range(n)]
answers = []
visited = [False for _ in range(n)]

def bfs(start):
    q = deque()
    members = set()
    min_node = 10000
    q.append(start)
    while q:
        x = q.popleft()
        if costs[x] < min_node:
            min_node = costs[x]
        visited[x] = True
        members.add(x)
        for node in graph[x]:
            if not visited[node]:
                q.append(node)
    return min_node
min_cost = 0
for i in range(n):
    if not visited[i]:
        min_cost += bfs(i)
if min_cost > k:
    print("Oh no")
else:
    print(min_cost)
728x90
반응형

'알고리즘 > 알고리즘 문제' 카테고리의 다른 글

[알고리즘] 합승 택시 요금  (2) 2023.11.27
[알고리즘] 2048 (Easy)  (0) 2023.10.08
[백준] 타임머신 11657  (0) 2023.03.22
[백준 2636] 치즈  (0) 2023.03.11
[백준 - 1062] [파이썬] 가르침  (0) 2023.01.08
728x90
반응형

www.acmicpc.net/problem/7576

 

7576번: 토마토

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토

www.acmicpc.net

문제

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다. 

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

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

입력

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.

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

출력

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

 

예제 입력1

6 4
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 1

예제 출력1

8

 

예제 입력2

6 4
0 -1 0 0 0 0
-1 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 1

예제 출력2

-1

 

 

[문제 해설]

문제를 해석하면 1은 익은 토마토, 0은 익지 않은 토마토 -1은 토마토가 없는 곳이다. 하루가 지나면 1 옆에 0은 1로 변한다. (옆이라는 것은 대각선이 아닌 가로, 세로로 인접한 곳을 의미) 

 

dx,dy를 통해 상하좌우를 탐색해야 되므로 bfs를 떠올렸다. 일단 bfs로 근처 0이 1로 변하는 것을 짜고 정답인 걸리는 시간은 나중에 생각해야 겠다고 결정했다.

 

입력을 받고, bfs에 사용되는 큐에는 토마토 중 익은것인 1이 되어있는 것을 넣어주었다.

from collections import deque
m,n = map(int,input().split())
tomato=[]
for i in range(n):
    tomato.append(list(map(int,input().split())))

queue=deque()
for i in range(n):
    for j in range(m):
        if tomato[i][j]==1:
            queue.append((i,j))

(bfs에서는 deque를 사용하므로 collections import해주었다.)

 

그리고 bfs를 사용해서 1. 범위를 넘어간 곳이면 continue 2. 0인 곳을 만나면 1로 바꾸어주고 큐에 넣어줌. 3. -1인 곳도 그냥 continue

from collections import deque
m,n = map(int,input().split())
tomato=[]
for i in range(n):
    tomato.append(list(map(int,input().split())))

queue=deque()
for i in range(n):
    for j in range(m):
        if tomato[i][j]==1:
            queue.append((i,j))
            
dx = [-1, 1, 0, 0]  # 상, 하, 좌, 우
dy = [0, 0, -1, 1]
while queue:
    x,y=queue.popleft()
    for i in range(4):
        nx=x+dx[i]
        ny=y+dy[i]
        if nx<0 or nx>=n or ny<0 or ny>=m:
            continue
        if tomato[nx][ny]==0:
            tomato[nx][ny]=1
            queue.append((nx, ny))
        if tomato[nx][ny] == -1:
            continue

 

그리고 이제 여기까지 되었으면 마지막 단계인 토마토가 다익는데 걸리는 기간을 확인해야 된다. 여기서 떠올린 생각은 토마토가 완성되는데 걸리는 시간은 큐에 원소의 갯수에 따라 달라질 수 있다는 것이다. 

만약 처음에 tomato맵에서 1이었던 것이 2개이면 동시에 bfs함수를 돌려서 그날에 익는 갯수를 확인해야 될 것이다. 그러므로 for문을 통해 tomato맵에서 1이었던 것의 갯수만큼만 반복해주면 된다.

(이미 한것에 한해서는 bfs함수를 해서 주위엔 익은 상태이기 때문에 신경을 안써주어도 되기 때문)

 

[최종 정답]

from collections import deque

m, n = map(int, input().split())
tomato = []
for _ in range(n):
    tomato.append(list(map(int, input().split())))
# 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않음
dx = [-1, 1, 0, 0]  # 상, 하, 좌, 우
dy = [0, 0, -1, 1]
queue = deque()
for i in range(n):
    for j in range(m):
        if tomato[i][j]==1:
            queue.append((i, j))
day = 1
count = len(queue)
while queue:
    for i in range(count):
        x, y = queue.popleft()
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if nx<0 or nx>=n or ny<0 or ny>=m:
                continue
            if tomato[nx][ny] == 0:
                tomato[nx][ny] = 1
                queue.append((nx, ny))
            if tomato[nx][ny] == -1:
                continue
    day+=1
    count=len(queue)
answer=0
for i in range(n):
    for j in range(m):
        if tomato[i][j]==0:
            answer=-1
if answer!=-1:
    answer=day

print(answer)
728x90
반응형
728x90
반응형

www.acmicpc.net/problem/1697

 

1697번: 숨바꼭질

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

문제

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다. 순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동하게 된다.

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

입력

첫 번째 줄에 수빈이가 있는 위치 N과 동생이 있는 위치 K가 주어진다. N과 K는 정수이다.

출력

수빈이가 동생을 찾는 가장 빠른 시간을 출력한다.

 

예제 입력 1

5 17

예제 출력 

4

힌트

수빈이가 5-10-9-18-17 순으로 가면 4초만에 동생을 찾을 수 있다.

 

[문제 해설]

처음 생각했던 것은 다이나믹 프로그래밍이였다. 각각의 위치에 가는 시간을 배열에 저장해서 다이나믹 프로그래밍으로 풀려고 했으나 생각보다 어려웠던 것은 뒤로 가는 경우와 앞으로 가는 경우까지 생각해야되서 복잡했다. 

 

돌고 돌아 BFS로 풀게 되었다. 모든 장소를 0으로 초기화 시킨 배열 d도 준비한다. 출발점을 큐의 첫번째 원소로 먼저 넣는다. 그 점에서 -1, +1, *2 한 곳을 next로 두고 for문을 통해서 반복한다. next가 범위 안에 있고, d배열에서 방문하지 않은 0이면 d[next]는 d[v]+1값이 들어가고 next를 큐에 추가해준다. 그럼 n에서 k로 가는 최소점이 d배열에 들어가게 된다. 종료 조건은 큐에 k값이 들어간게 확인되었을때 d[v]를 출력해주고 종료하면 된다.

 

(나는 d배열에 어떤값이 들어가있는지 계속 헷갈려서 print로 찍어주었다. 주석은 무시하고 보면 된다_

from collections import deque
n, k = map(int, input().split())
# n에서 k로 가기
d=[0]*100001
def bfs(n):
    queue=deque()
    queue.append(n)
    while queue:
        v=queue.popleft()
        if v==k:
            print(d[v])
            return
        for next in (v-1,v+1,v*2):
            if 0<=next<100001 and not d[next]:
                # for i in range(20):
                #     print(d[i], end=' ')
                # print()
                d[next]=d[v]+1
                queue.append(next)

bfs(n)
728x90
반응형
728x90
반응형

탐색Search이란 많은 양의 데이터 중에서 원하는 데이터를 찾는 과정을 의미한다. 대표적인 탐색 알고리즘이 DFS와 BFS이다. 프로그래밍에서는 자료구조 안에서 탐색을 하는데 DFS와 BFS를 알려면 스택과 큐에 관한 자료구조를 알아야된다.

 

스택

스택Stack은 레고 쌓기에 비유할 수 있다. 레고는 아래에서부터 위로 차곡차곡 쌓는다. 맨 아래에 레고를 빼기 위해서는 위에 레고를 빼야 된다. 즉, 먼저 쌓은 것이 제일 나중에 뺄 수 있다. 선입후출(First In Last Out) 또는 후입선출(Last In First Out) 구조 라고 한다. 

 

stack=[]
#삽입(1) - 삭제() - 삽입(3) - 삽입(5) - 삽입(6) - 삭제() - 삽입(2) - 삭제()
stack.append(1)
stack.pop()
stack.append(3)
stack.append(5)
stack.append(6)
stack.pop()
stack.append(2)
stack.pop()
print(stack)#[3,5]
print(stack[::-1])#[5,3]

 

append() 메서드는 리스트의 가장 뒤쪽에 데이터를 삽입하고, pop() 메서드는 리스트의 가장 뒤쪽에서 데이터를 꺼낸다.


 

큐Queue는 대기 줄에 비유할 수 있다. 우리가 놀이공원에 입장하기 위해 줄을 설 때, 먼저 온 사람이 먼저 들어가게 된다. 나중에 온 사람이 나중에 들어가기 때문에 흔히 '공정한' 자료구조라고 비유된다. 이러한 구조를 선입선출(First In First Out)라고 한다.

 

from collections import deque
queue=deque()
#삽입(4) - 삽입(2) - 삽입(1) - 삽입(8) - 삭제() - 삭제() - 삽입(4)
queue.append(4)
queue.append(2)
queue.append(1)
queue.append(8)
queue.popleft()
queue.popleft()
queue.append(4)

print(queue)
queue.revers()
print(queue)

 

파이썬으로 큐를 구현할 때는 collections 모듈에서 제공하는 deque 자료구조를 활용한다. deque는 스택과 큐의 장점을 모두 채택한 것인데 데이터를 넣고 빼는 속도가 리스트 자료형에 비해 효율적이며 queue 라이브러리를 이용하는 것보다 더 간단하다.

deque객체를 자료형으로 변경하고자 한다면 list(queue)를 사용하면 리스트 자료형이 반환된다.


DFS

DFS는 Depth-First Search, 깊이 우선 탐색이라고도 부르며, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘이다. DFS를 알려면 그래프의 표현 방식을 알아야 된다. 

 

그래프는 2가지 방식으로 표현할 수 있는데 인접 행렬 방식과 인접 리스트 방식이 있다.

인접 행렬 방식은 2차원 배열로 그래프의 연결 관계를 표현하는 방식이고 인접 리스트 방식은 리스트로 그래프의 연결 관계를 표현하는 방식이다. 

 

인접 행렬 방식은 2차원 배열에 각 노드가 연결된 형태를 기록하는 방식이다. 연결이 되어 있지 않은 노드끼리는 무한Infinity의 비용이라고 작성한다. 

 

인접 행렬 방식 예제

INF = 9999999999

graph = [
    [0,7,5],
    [7,0,INF],
    [5,INF,0]
]

print(graph)

[[0,7,5],[7,0,99999999],[5,999999999,0]]

 

 인접 리스트 방식에서는 모든 노드에 연결된 노드에 대한 정보를 차례대로 연결하여 저장한다. C++이나 Java와 같은 프로그래밍 언어에서는 별도로 연결 리스트 기능을 위한 표준 라이브러리를 제공한다.

 반면에 파이썬은 기본 자료형인 리스트 자료형이 append()와 메소드를 제공하므로, 전통적인 프로그래밍 언어에서의 배열과 연결 리스트의 기능을 모두 기본으로 제공한다. 파이썬으로 인접 리스트를 이용해 그래프를 표현하고자 할 때에도 단순히 2차원 리스트를 이용하면 된다. 

 

# 행(ROW)이 3개인 2차원 리스트로 인접 리스트 표현
graph = [[] for _ in range(3)]

#노드 0에 연결된 노드 정보 저장(노드, 거리)
graph[0].append((1,7))
graph[0].append((2,5))

#노드 1에 연결된 노드 정보 저장(노드, 거리)
graph[1].append((0,7))

#노드 2에 연결된 노드 정보 저장(노드, 거리)
graph[2].append((0,5))

print(graph)

 

DFS는 어떻게 작동할까? DFS는 깊이 우선 탐색 알고리즘이다. 이 알고리즘은 특정한 경로로 탐색하다가 특정한 상황에서 최대한 깊숙이 들어가서 노드를 방문한 후, 다시 돌아가 다른 경로로 탐색하는 알고리즘이다. 

 

1. 탐색 시작 노드를 스택에 삽입하고 방문 처리를 한다.

2. 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리를 한다. 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼낸다. 

3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.

 

방문 처리는 스택에 한 번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크하는 것을 의미한다. 방문 처리를 함으로써 각 노드를 한 번씩만 처리할 수 있다.

 

깊이 우선 탐색 알고리즘인 DFS는 스택 자료구조에 기초한다는 점에서 구현이 간단하다. 데이터의 개수가 N개인 경우 O(N)의 시간이 소요된다는 특징이 있다. 

 

또한 DFS는 스택을 이용하는 알고리즘이기 때문에 실제 구현은 재귀 함수를 이용했을 때 매우 간결하게 구현할 수 있다. 

 

def dfs(graph, v, visited):
    #현재 노드를 방문 처리
    visited[v]=True
    print(v,end=" ")
    #현재 노드와 연결된 다른 노드를 재귀적으로 방문
    for i in graph[v]:
        if not visited[i]:
            dfs(graph,i,visited)


#각 노드가 연결된 정보를 리스트 자료형으로 표현(2차원 리스트)
graph= [
    [],
    [2,3,8],
    [1,7],
    [1,4,5],
    [3,5],
    [3,4],
    [7],
    [2,6,8],
    [1,7]
]

#각 노드가 방문된 정보를 리스트 자료형으로 표현
visited = [False]*9

#정의된 DFS 함수 호출
dfs(graph,1,visited)

#1 2 7 6 8 3 4 5

BFS

BFS(Breadth First Search) 알고리즘은 '너비 우선 탐색'이라는 의미를 가진다. BFS는 가까운 노드부터 탐색하는 알고리즘이다. DFS는 최대한 멀리 있는 노드를 우선으로 탐색하는 방식으로 동작한다고 했는데, BFS는 그 반대로 가까이 있는것부터 차근차근 탐색해나간다. 

 

BFS 구현에서는 선입선출 방식인 큐 자료구조를 이용한다. 인접한 노드를 반복적으로 큐에 넣어서 가까운 노드부터 탐색해서 탐색할 수 있을때까지 찾아나가는 것이다.

 

1. 탐색 시작 노드를 큐에 삽입하고 방문 처리를 한다.

2. 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리를 한다.

3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.

 

BFS는 큐 자료구조에 기초한다. 구현함에 있어 deque라이브러리를 사용하는 것이 좋으며 탐색을 수행함에 있어 O(N)의 시간이 소요된다. 일반적인 경우 실제 수행 시간은 DFS보다 좋은 편이다.

 

from collections import deque

graph=[
    [],
    [2,3,8],
    [1,7],
    [1,4,5],
    [3,5]
]
visited=[False]*9

def bfs(graph,start,visited):
    queue=deque([start])
    visited[start]=True
    while queue:
        #큐에서 하나의 원소를 뽑아 출력
        v=queue.popleft()
        print(v,end=' ')
        #해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
        for i in graph[v]:
            if not visited[i]:
                queue.append(i)
                visited[i]=True

 

2차원 배열에서의 탐색 문제를 만나면 그래프 형태로 바꿔서 생각하면 풀이 방법을 조금 더 쉽게 떠올릴 수 있다.

728x90
반응형

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

최단경로 (다익스트라)  (0) 2021.05.04
힙 자료구조  (0) 2021.05.04
정렬(Sort)  (0) 2021.04.12
재귀 함수  (0) 2021.04.02
브루트 포스 (Brute Force)  (0) 2021.03.18

+ Recent posts