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

 

 

 

이 문제에서 중요한 점은 입력으로 수학에서의 x, y 축을 받은 후에

27번 줄처럼 코딩할 때는 배열에서의 row, col 으로 바꿔주어야 한다는 것이다.

 

그래프 문제를 처음 풀었을 때 헷갈렸던 것이 바로 이 x축과 y축을 어떻게 볼 것인가에 대한 문제였는데,

x와 y는 변수 이름으로 쓰지 않고 row와 column만을 쓰는 방식으로 나름대로 해결책을 찾아냈었다. 

그런데 이 문제에서는 수학에서의 x축과 y축을 입력으로 줘버린다. 따라서 굉장히 헷갈리는 문제였다.

 

일단 입력은 그대로 받은 후,

보드판을 검은 색으로 칠할 때 적절한 row와 column을 찾아서 검은색으로 칠해주도록 했다.

한 눈에 규칙이 보이지 않아서 하나하나 대입하며 보드판을 프린트해가면서 규칙을 찾아냈다.

결론은 row는 세로길이 - y - 1 이 되고,

column은 x를 그대로 가져오면 된다.

import sys
WHITE, BLACK = 0, 1


def count_white(start):
    count = 1
    stack = [start]
    visited[start[0]][start[1]] = 1

    while stack:
        r, c = stack.pop()
        for nR, nC in [[r+1, c], [r-1, c], [r, c+1], [r, c-1]]:
            if 0 <= nR < m and 0 <= nC < n:
                if board[nR][nC] == WHITE:
                    if not visited[nR][nC]:
                        visited[nR][nC] = 1
                        count += 1
                        stack.append([nR, nC])

    return count


def paint_black():
    for rec in rectangle:
        for x in range(rec[0], rec[2]):
            for y in range(rec[1], rec[3]):
                board[m-y-1][x] = BLACK  # 모눈종이의 x, y를 행렬의 row, col로 바꾸고 칠하기


if __name__ == '__main__':
    m, n, k = map(int, sys.stdin.readline().split())  # m=세로 n=가로 k=직사각형 갯수
    board = [[0 for _ in range(n)] for _ in range(m)]
    visited = [[0 for _ in range(n)] for _ in range(m)]
    rectangle = []
    ret = []

    for _ in range(k):
        rectangle.append(list(map(int, sys.stdin.readline().split())))
    paint_black()

    for row in range(m):
        for col in range(n):
            if board[row][col] == WHITE and not visited[row][col]:
                ret.append(count_white([row, col]))

    ret.sort()
    print(len(ret))
    print(' '.join(map(str, ret)))

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

 

 

 

참고: 단지 번호 붙이기 https://awesomeroo.tistory.com/35?category=743874

 

 

처음에는 메모리 낭비 방지를 위해 인접 행렬을 인접 딕셔너리로 표현했으나 인접 리스트(이중 리스트)로 바꾸었다.

연결 요소 구하기 문제에서는 동떨어져 있는 무인도 노드도 하나의 연결 요소로 보아야 하기 때문이다.

(인접 딕셔너리에서는 간선이 없는 노드는 key로서 추가되지 않는다)

단지 번호 붙이기 문제와 유사한데, 연결되어 있는 부분 찾기 (Flood fill) 문제 타입이라고 한다.

import sys


def dfs(start):
    stack = [start]
    visited[start] = 1

    while stack:
        node = stack.pop()
        for child in range(1, n+1):
            if graph[node][child]:
                if not visited[child]:
                    visited[child] = 1
                    stack.append(child)



n, m = map(int, sys.stdin.readline().split())
graph = [[0 for _ in range(n+1)] for _ in range(n+1)]
for _ in range(m):
    u, v = map(int, sys.stdin.readline().split())
    graph[u][v] = 1
    graph[v][u] = 1

visited = [0 for _ in range(n+1)]

count = 0
for startNode in range(1, n+1):
    if not visited[startNode]:
        dfs(startNode)
        count += 1

print(count)

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

13460번. 구슬 탈출 2  (0) 2019.12.31
2468번. 안전 영역  (0) 2019.12.30
14502번. 연구소  (0) 2019.12.20
11403번. 경로 찾기  (0) 2019.12.19
2206번. 벽 부수고 이동하기  (0) 2019.12.18

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

 

 

이 문제는 DFS or BFS 이기도 하고, 완전 탐색이기도 하다. 

더 힌트를 주자면 두 번의 DFS 나 BFS 를 사용해야 한다.

 

코드를 실행 순서대로 보자.

 

n, m = map(int, sys.stdin.readline().split())  # n=세로 m=가로
lab = []
for _ in range(n):
    lab.append(list(map(int, sys.stdin.readline().split())))
    
virusList = []
virusInit, wallInit = count_num(virusList)

maxSafe = 0

set_wall(0, 0)

1~4번 줄은 데이터 입력을 받는 부분이다.

6번줄의 virusList는 초기에 바이러스(2)인 위치를 저장하는 리스트이다. 토마토를 익히는 문제를 생각해보자.

초기에 익은 상태인 토마토들을 리스트에 따로 담아서, 익히는 과정(BFS)을 시작할 때 큐의 초기값으로 넣어줬었다.

바이러스가 확산되는 것이나 토마토가 익는 것이나 같은 행동이나 마찬가지이므로 익은 토마토처럼 바이러스를 따로 저장해주었다.

7번 줄은 문제의 정답인 안전지대 갯수를 구하기 위해서 따로 변수를 선언한 것인데, 나는 (전체 지역-벽 갯수-바이러스 갯수=안전지대 갯수) 로 구했기 때문에 변수를 저렇게 받아왔다. 안전지대 갯수를 구하는 방법은 나중에 이중 for문을 돌려서 0을 구해도 되고 자기 마음이기 때문에 count_num() 함수를 따로 쓰지는 않겠다.

9번 줄의 maxSafe는 최종 정답이 될 안전지대 갯수이다.

이제 다음으로 호출되는 set_wall 함수를 보자.

 

 

# startR을 인자로 넣어줌으로써 해당 row 보다 이전 row 들은 선택하지 않도록 한다. (중복 선택 방지)
def set_wall(count, startR):
    if count == 3:
        spread_virus()
        return

    for r in range(startR, n):
        for c in range(m):
            if lab[r][c] == 0:  # 아무것도 없으면 벽으로 선택
                lab[r][c] = 1
                set_wall(count+1, r)
                lab[r][c] = 0

set_wall은 벽을 선택하는 함수이다. 그런데 어디서 많이 본 양식이다. 바로 조합 문제이다. N과 M (2) 참고

처음 호출할 때 set_wall(0, 0) 으로 호출했는데 이 의미는 '현재 0개의 벽을 선택했고, 0번째 row부터 보자'라는 의미이다.

N과 M (2)를 풀 때도 마찬가지였다. 조합은 순서가 없으므로 한 번 선택한 것은 다시 볼 필요가 없다. 따라서 현재 인덱스를 기억하기 위해서 row를 인자로 넘겨준 것이다.

잘 생각해보면 N과 M 은 일차원 리스트였으므로 인덱스 하나만 저장했었다. 이 문제는 이차원 리스트이다. 그럼 row와 column을 다 저장해야 하나? 그건 아닌게, column 까지 기억해버리면 안된다.

예를 들어

1 2 3
4 5 6
7 8 9

라는 행렬에서 저 함수대로 3개를 조합으로 선택해야 된다고 쳐 보자.

[1, 2, 3]을 선택한 후 [1, 2, 4]를 선택해야 되는데 column에 제한을 걸었으므로 [1, 2, 5]를 선택하게 된다. 

만약 중복을 최대한 줄이고 싶다면 같은 row일 때는 해당 column 보다 작은 값들은 선택하지 않도록 해야 정확히 그 조합 갯수만큼 선택할 수 있다. 2차원 배열에서 조합 선택하기

 

 

 

그 다음, 벽을 3개 선택했을 때 호출되는 spread_virus 함수를 보자.

# bfs
def spread_virus():
    global maxSafe
    queue = collections.deque()
    queue.extend(virusList)  # 처음에 바이러스가 있었던 자리들로 초기화
    visited = [[0 for _ in range(m)] for _ in range(n)]
    virus_spread = virusInit  # 바이러스 갯수 = 초기 바이러스 갯수 (점차 더해갈 것임)

    while queue:
        row, col = queue.popleft()
        for r, c in [[row+1, col], [row-1, col], [row, col+1], [row, col-1]]:
            if 0 <= r < n and 0 <= c < m:
                if visited[r][c] == 0:
                    visited[r][c] = 1
                    if lab[r][c] == 0:
                        virus_spread += 1
                        queue.append([r, c])
    maxSafe = max(maxSafe, (n * m) - wallInit - virus_spread)
    return virus_spread

바이러스를 전염시키는 것은 토마토를 익히는 것과 비슷해서 bfs를 썼지만 토마토와는 달리 최단 경로를 구하는 것이 아니므로 dfs를 사용해도 무방할 것 같다.

바이러스를 초기 큐로 잡은 후, 바이러스는 상하좌우로 움직일 수 있으므로 경로 탐색하는 것처럼 상하좌우에 있는 노드들을 큐에 넣고, 방문(탐색)했을 시에는 재탐색하지 않도록 1로 체크한다.

0을 만나면 전염시킬 수 있는 것이므로 virus_spread 에 1을 더한다. 그리고 안전 지대 최댓값을 구한다.

큐가 없어지면 전염 시킬 수 있는대로 다 시킨 것이므로 바이러스 갯수를 리턴한다.

 

 

이제 이 문제의 알고리즘 설명은 다 끝났다고 볼 수 있다. 사실 이 문제를 처음 풀 때는 벽을 선택하는 함수까지도  visited 리스트를 만들고 상하좌우로 이동하는 것처럼 만들었다. 하지만 그렇게 할 필요가 없는게 우선 벽은 (0, 0)부터 (n, m)까지 순차적으로 선택해서 세우면 된다. 바이러스처럼 상하좌우로 움직이는 것이 아닌 것이다. (그렇게해도 결국엔 다 탐색하겠지만..) 그리고 둘째로, 벽은 3개가 되면 거기서 멈추고 바이러스를 퍼트리는 함수로 넘어가야 하므로 백트래킹의 형식을 띠고 있다고 볼 수 있다. 복잡복잡하게 경로 탐색 형식으로 만들어도 되긴 하겠지만 읽기도 불편하고 추가적인 연산이 더 필요할 것이다. 

 

 

 

다음은 코드 전문이다.

import sys
import collections


def count_num(lst):
    w = 0
    v = 0
    s = 0
    for r in range(n):
        for c in range(m):
            if lab[r][c] == 2:
                v += 1
                lst.append([r, c])
            elif lab[r][c] == 1:
                w += 1
    return v, w + 3  # 세워질 3개의 벽까지 포함


# bfs
def spread_virus():
    global maxSafe
    queue = collections.deque()
    queue.extend(virusList)  # 처음에 바이러스가 있었던 자리들로 초기화
    visited = [[0 for _ in range(m)] for _ in range(n)]
    virus_spread = virusInit

    while queue:
        row, col = queue.popleft()
        for r, c in [[row+1, col], [row-1, col], [row, col+1], [row, col-1]]:
            if 0 <= r < n and 0 <= c < m:
                if visited[r][c] == 0:
                    visited[r][c] = 1
                    if lab[r][c] == 0:
                        virus_spread += 1
                        queue.append([r, c])
    maxSafe = max(maxSafe, (n * m) - wallInit - virus_spread)
    return virus_spread


# startR을 인자로 넣어줌으로써 해당 row 보다 이전 row 들은 선택하지 않도록 한다. (중복 선택 방지)
def set_wall(count, startR):
    if count == 3:
        spread_virus()
        return

    for r in range(startR, n):
        for c in range(m):
            if lab[r][c] == 0:  # 아무것도 없으면 벽으로 선택
                lab[r][c] = 1
                set_wall(count+1, r)
                lab[r][c] = 0


n, m = map(int, sys.stdin.readline().split())  # n=세로 m=가로
lab = []
for _ in range(n):
    lab.append(list(map(int, sys.stdin.readline().split())))
virusList = []
virusInit, wallInit = count_num(virusList)

maxSafe = 0

set_wall(0, 0)

print(maxSafe)

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

2468번. 안전 영역  (0) 2019.12.30
11724번. 연결 요소의 개수  (0) 2019.12.24
11403번. 경로 찾기  (0) 2019.12.19
2206번. 벽 부수고 이동하기  (0) 2019.12.18
7576번. 토마토  (0) 2019.12.17

lst 라는 이차원 행렬에서 3개를 조합으로 뽑고 싶을 때

중복되는 경우 없이 뽑고 싶다면 

row, col을 기억하면서 같은 row일 때는 해당 col보다 작은 값들은 보지 않도록 하면 된다.

(같은 row에서 해당 col보다 작은 값들은 이미 본 것이므로)

lst = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
check = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
n, m = 3, 3


def combination(count, r, c):
    global ret  # 조합 갯수 세기
    if count == 3:
        print(selected)
        ret += 1
        return
    for i in range(r, n):
        if i == r:  # 해당 row일 때는 col보다 작은 값은 보지 않기
            c2 = c
        else:
            c2 = 0
        for j in range(c2, m):
            if check[i][j] == 0:
                check[i][j] = 1
                selected.append(lst[i][j])
                combination(count+1, i, j)
                check[i][j] = 0
                selected.pop()
ret = 0
selected = []

combination(0, 0, 0)
print(ret)

 

'알고리즘 > 백트래킹' 카테고리의 다른 글

14888. 연산자 끼워넣기  (0) 2019.11.28
2580. 스도쿠  (0) 2019.11.27
9663. N-Queen  (0) 2019.11.26
15650. N과 M (2)  (0) 2019.11.22
15649. N과 M(1)  (0) 2019.11.22

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

 

 

DFS로도 풀 수 있지만, 플로이드-워셜 알고리즘으로 풀리는 문제.

플로이드 워셜 알고리즘이란 

정점 i 에서 정점 j 로 가는 경로가 있는지 확인하고 싶을 때,

 

i 에서 j 로 가는 직통 길이 있니? matrix[i][j] == 1? 

k ( k = 모든 정점 ) 를 지나쳐 가는 길이 있니? matrix[i][k] == 1 and matrix[k][j] == 1?

 

를 확인하는 알고리즘. k에는 0부터 마지막 정점까지 모든 정점을 하나씩 대입한다.

플로이드 워셜 알고리즘에서는 최단 거리를 발견할 때마다 업데이트하지만

여기서는 최단 거리가 아니라 가는 경로 유무 1, 0으로 만 확인하면 된다.

 

 

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

11724번. 연결 요소의 개수  (0) 2019.12.24
14502번. 연구소  (0) 2019.12.20
2206번. 벽 부수고 이동하기  (0) 2019.12.18
7576번. 토마토  (0) 2019.12.17
1012번. 유기농 배추  (0) 2019.12.13

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

 

 

1. visited를 따로 사용할 필요가 없다. 거리를 나타내는 dist 배열이 0 이상이면 방문한 적이 있다는 뜻이기 때문.

2. dist에 방문 여부를 표시하되, 지금까지 벽을 부순 적 있는 경우와 없는 경우를 따로 체크해야 한다.

벽을 부수지 않고 오다가, 어떤 벽을 만났는데 다른 경로가 한번 부숴서 지나갔다고 거기서 막히면 안 된다. 벽은 각 '경로마다' 한 번씩 부술 수 있기 때문이다. 벽이 아닌 길을 만났을 때도 여기까지 '벽을 부수면서 온 경로'만 방문했다면 이번 경로는 '벽을 부수지 않고 온 유일한 경로'이다.

3. 모든 길을 모두 방문하지 않고 중간에 목적지를 만났으면 리턴하는 것이 좋다. 벽을 부수면서 도착했건, 부수지 않으면서 도착했건 일단 도착했으면 제일 먼저 도착한 것이므로 바로 리턴하면 된다. 또다른 경우를 상정하지 않는다.

 

아, 벽이 0이나 1이 아니라 '0'이나 '1'임에 유의하자.

import sys
import collections


def bfs(start):
    queue = collections.deque()
    queue.append(start)
    dist[start[0]][start[1]][0] = 1

    while queue:
        r, c, wallBreak = queue.popleft()  # wallBreak=지금껏 벽을 부순 적이 있는가?
        if r == n-1 and c == m-1:
            return dist[r][c][wallBreak]  # BFS는 처음 목적지에 도착하면 그것이 최단경로
        for nR, nC in [[r+1, c], [r-1, c], [r, c+1], [r, c-1]]:
            if 0 <= nR < n and 0 <= nC < m:
                if dist[nR][nC][wallBreak] > 0:  # 이전에 같은 상태에서 방문한 적이 있으면 방문하지 않음
                    continue
                if board[nR][nC] == '0':  # 벽이 뚫려있으면
                    dist[nR][nC][wallBreak] = dist[r][c][wallBreak] + 1  # 단순하게 부모 노드+1
                    queue.append([nR, nC, wallBreak])
                elif board[nR][nC] == '1' and wallBreak == 0:  # 벽이 막혀 있고, 이전에 뚫고 온 적이 없으면
                    dist[nR][nC][1] = dist[r][c][wallBreak] + 1
                    queue.append([nR, nC, 1])
    return -1


n, m = map(int, sys.stdin.readline().split())
board = []
for _ in range(n):
    board.append(sys.stdin.readline().rstrip())
dist = [[[0, 0] for _ in range(m)] for _ in range(n)] # [non-break distance, break distance]

print(bfs([0, 0, 0]))  # 큐의 초기값. row, col, break_or_not

 

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

14502번. 연구소  (0) 2019.12.20
11403번. 경로 찾기  (0) 2019.12.19
7576번. 토마토  (0) 2019.12.17
1012번. 유기농 배추  (0) 2019.12.13
2667번. 단지번호붙이기  (0) 2019.12.13

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

 

 

BFS로 푸는 문제이다.

처음 접근방법은 처음부터 익은 토마토 리스트를 하나 만든 후에, 그 중 하나가 주변을 익힐 수 있을 만큼 모두 익히게 하고,

그 다음 처음부터 익은 토마토가 또 주변을 익힐 수 있게 하되, 더 이른 날짜에 익을 수 있으면 날짜를 교체하도록 했다.

그렇게 하니 보기 좋게 시간초과가 생겼고, 날짜를 그렇게 변경해나가는 것은 BFS가 아니라는 질문 글을 발견했다.

도착한 순간 최단 경로가 확정되어야 한다는 것이다. 즉, 재방문 하는 일이 없어야 한다.

잘못 생각했던 점은 한 토마토의 주변을 일단 모두 익게 하려고 했다는 점이다.

익은 각각의 토마토들의 상하좌우를 각각 한번씩만 익힌 후 그것을 모두 큐에 넣고, 날짜를 하나 증가시킨 후 또다시 각각의 상하좌우를 큐에 넣고...하는 식으로 하면 된다.

소스코드 상으로는 처음부터 익은 토마토를 큐의 초기값으로 잡으면 된다.

 

채점한 코드 중에 중요한 함수만 가지고 왔다.

def start_ripe_tomato():
    queue = collections.deque()
    queue.extend(initiate_tomato())  # 처음에 1인 토마토 리스트

    while queue:
        r, c = queue.popleft()
        for nR, nC in [[r + 1, c], [r - 1, c], [r, c + 1], [r, c - 1]]:  # 상하좌우
            if 0 <= nR < n and 0 <= nC < m:
                if box[nR][nC] == 0:  # 아직 안 익었다면
                    queue.append([nR, nC])  # 큐에 추가. 큐에는 방금 익은 리스트가 들어감
                    box[nR][nC] = 1  #  익음 체크
                    days[nR][nC] = days[r][c] + 1

2) 큐를 사용하기 위해서 collections의 덱을 사용한다.

3) initiate_tomato()는 처음에 주어진 입력에서 1인 토마토들을 모두 리스트에 저장해 반환하는 함수이다. queue에는 방금 익은 토마토들이 들어갈 리스트인데, 초기값으로는 처음부터 익어 있었던 (1이었던) 토마토들을 방금 익은 것이라고 가정하고 넣는다.

5) 방금 익은 토마토(queue)들이 없을 때까지 진행한다. 방금 익은 토마토가 없다면 더이상 진행이 불가능할 것이기 때문이다.

6~8) 각각의 토마토들의 상하좌우를 가져와서 out of index 체크를 한다.

9) 토마토가 아직 익지 않은 상태라면, 그것을 익히고 (11번줄), 방금 익은 토마토 리스트에 추가한다. (10번줄)

12) days는 각각의 토마토가 익을 때까지 걸리는 일수를 저장한 이차원 리스트인데, 부모 토마토가 익은 날짜+1이 된다.

 

다시 와서 생각해보면 BFS에서 크게 벗어나지 않은 문제인데, 미로찾기처럼 생각해서 헤맨 문제같다.

 

 

 

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

11403번. 경로 찾기  (0) 2019.12.19
2206번. 벽 부수고 이동하기  (0) 2019.12.18
1012번. 유기농 배추  (0) 2019.12.13
2667번. 단지번호붙이기  (0) 2019.12.13
2606번. 바이러스  (0) 2019.12.13

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

 

 

 

BFS와 DFS를 이해하고

2667번을 풀어봤다면 오히려 더 쉬운 문제이지만,

m와 n, x와 y, 가로와 세로, 행과 열에 있어서 헷갈렸던 문제...

import sys


def DFS(row, col):
    stack = []
    stack.append([row, col])

    while stack:
        r, c = stack.pop()
        if visited[r][c] == 0:
            visited[r][c] = 1
            for nR, nC in [[r+1, c],[r-1,c],[r,c+1],[r,c-1]]:
                if 0 <= nR < n and 0 <= nC < m:
                    if farm[nR][nC] == 1:
                        stack.append([nR, nC])


T = int(sys.stdin.readline())   # 테스트케이스 갯수

for _ in range(T):
    m, n, k = map(int, sys.stdin.readline().split())   # 가로, 세로, 배추갯수
    farm = [[0 for _ in range(m)] for _ in range(n)]
    visited = [[0 for _ in range(m)] for _ in range(n)]  # 배추벌레가 방문한 곳
    worm = 0  # 배추벌레의 갯수 카운트

    for _ in range(k):
        col, row = map(int, sys.stdin.readline().split())
        farm[row][col] = 1  # 배추가 위치하는 곳을 1로 표시

    #print(farm)
    for row in range(n):
        for col in range(m):
            if farm[row][col] == 1 and visited[row][col] == 0:
                DFS(row, col)
                worm += 1

    print(worm)

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

2206번. 벽 부수고 이동하기  (0) 2019.12.18
7576번. 토마토  (0) 2019.12.17
2667번. 단지번호붙이기  (0) 2019.12.13
2606번. 바이러스  (0) 2019.12.13
1260번. DFS와 BFS  (0) 2019.12.13

+ Recent posts