ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백준 9328번 열쇠
    알고리즘 2023. 8. 10. 15:41
    728x90
    반응형
    SMALL

    빈칸, 벽, 문서, 열쇠, 문이 있는 평면도가 주어졌을 때, 최대로 가질 수 있는 문서의 개수를 구하는 문제입니다.

    문과 같은 경우 초반에는 모두 잠겨있으므로 열쇠를 먼저 얻어야지만 문을 통과할 수 있습니다. 초반에 열쇠가 주어질 수도, 안 주어질 수도 있습니다.

    처음 들어갈 수 있는 위치는 평면도의 가장자리입니다.

     

    사용 변수들

    X # 행 크기
    Y # 열 크기
    maps # 문제에서 주어진 평면도. 크기 : maps[X][Y]
    keys # 열쇠를 저장할 집합
    door # 평면도 탐색 중 열지 못한 문의 좌표 저장
    visited # 평면도 방문 여부 저장. 크기 : visited[X][Y]

     

    시작 위치 선정

    들어갈 수 있는 부분은 평면도의 가장자리입니다. 그렇기 때문에 반복문을 통해 시작점의 위치를 찾을 수 있도록 했습니다.

    열을 고정하고 행을 바꾸도록하고, 행을 고정하고 열을 바꾸는 식으로 가장자리를 모두 탐색할 수 있도록 했습니다.

    # X: 행 크기, Y: 열 크기, find: 문서 찾기 시작 함수
    for i in range(X):
        find(i,0)
        find(i,Y-1)
    for i in range(1,Y-1):
        find(0,i)
        find(X-1,i)

     

    시작점의 경우의 수

    평면도의 가장자리에서 시작할 경우 시작하는 부분이 무엇인지 확인을 한 뒤, 탐색을 이어나가야 합니다.

    시작점이 혹은 이미 방문한 적이 있는 곳이라면 탐색을 이어나가지 않습니다.

    def find(x,y):
        now=maps[x][y]
        
        if maps[x][y]=='*' or visited[x][y]:
            return

    시작점이 빈 공간이라면 탐색을 시작합니다.

    if now=='.':
        visited[x][y]=True
        dfs(x,y)

    시작점이 소문자 알파벳이라면 keys 집합에 현재 소문자 알파벳을 넣어준 뒤 탐색을 시작합니다.

    elif 'a'<=now<='z':
        visited[x][y]=True
        keys.add(now)
        dfs(x,y)

    시작점이 대문자 알파벳이라면 두 가지 경우가 있습니다.

    • 해당 알파벳의 소문자를 keys에 가지고 있다면 해당 문을 열고 나아갈 수 있으므로 탐색을 시작합니다.
    • 해당 알파벳의 소문자를 keys에 가지고 있지 않다면 해당 문의 좌표를 door에 저장한 뒤 return합니다.
    elif 'A'<=now<='Z':
        if now.lower() in keys:
            visited[x][y]=True
            maps[x][y]='.'
            dfs(x,y)
        else:
            door[now].append((x,y))

    시작점이 문서라면 탐색을 시작합니다.

    elif now=='$':
        visited[x][y]=True
        dfs(x,y)

     

    전체 코드

    def find(x,y):
        now=maps[x][y]
    
        if maps[x][y]=='*' or visited[x][y]:
            return 
            
        if now=='.':
            visited[x][y]=True
            dfs(x,y)
        elif 'a'<=now<='z':
            visited[x][y]=True
            keys.add(now)
            dfs(x,y)
        elif 'A'<=now<='Z':
            if now.lower() in keys:
                visited[x][y]=True
                maps[x][y]='.'
                dfs(x,y)
            else:
                door[now].append((x,y))
        elif now=='$':
            visited[x][y]=True
            dfs(x,y)

    위와 같이 시작점의 종류에 따라 처리를 해줄 수 있도록 해주었습니다.

     

     

    탐색

    평면도를 탐색하는 방법은 dfs, bfs 중 어떤 것이든 상관없을 것 같습니다. 저와 같은 경우 dfs를 사용했습니다.

    저와 같은 경우 문제에서 탐색 할 수 있는 모든 좌표는 dfs 함수에 거쳐가도록 했기 때문에 dfs 함수 초반에 현재 좌표가 문서일 경우 answer를 1 증가해주었습니다.

    def dfs(x,y):
        global X,Y,answer
        
        if maps[x][y]=='$':
            answer+=1

    그 다음 평면도 상에서 상하좌우로 움직일 수 있도록 했습니다.

    상 하 좌 우로 움직이지 않는 경우는 다음과 같습니다.

    • 다음 좌표가 평면도의 범위를 넘어가는 경우
    • 다음 좌표가 일 경우
    • 다음 좌표를 이미 방문한 경우
    for i in range(4):
        nx,ny=moves[i][0]+x,moves[i][1]+y
    
        if not (0<=nx<X and 0<=ny<Y) or visited[nx][ny] or maps[nx][ny]=='*':
            continue

     

    이제, 평면도에서 (nx,ny) 좌표의 종류에 따라 처리해주도록 했습니다.

    • 대문자 알파벳인 경우, 문에 대한 키가 존재한다면 해당 좌표를 빈칸('.')으로 변경시켜주었습니다. 문에 대한 키가 존재하지 않다면 해당 좌표를 door에 넣어준 뒤 다음 반복문으로 넘어가도록 했습니다.
    now=maps[nx][ny]
    if 'A'<=now<='Z':
        if now.lower() in keys:
            maps[nx][ny]='.'
        else:
            door[now].append((nx,ny))
            continue
    • 소문자 알파벳인 경우 해당 키를 keys 집합에 넣어주었습니다.
    if 'a'<=now<='z':
        keys.add(now)
    • 대문자 알파벳이고 해당 문에 대한 키가 존재하지 않는 경우를 제외하고는 다음 좌표로 이동 할 수 있는 경우이므로 visited값을 True로 바꿔주고 다음 좌표를 통해 dfs 함수를 재귀 호출했습니다.
    visited[nx][ny]=True
    dfs(nx,ny)

     

    전체 코드

    def dfs(x,y):
        global X,Y,answer
        
        if maps[x][y]=='$':
            answer+=1
            
        for i in range(4):
            nx,ny=moves[i][0]+x,moves[i][1]+y
            
            if not (0<=nx<X and 0<=ny<Y) or visited[nx][ny] or maps[nx][ny]=='*':
                continue
            
            now=maps[nx][ny]
            if 'A'<=now<='Z':
                if now.lower() in keys:
                    maps[nx][ny]='.'
                else:
                    door[now].append((nx,ny))
                    continue
            
            if 'a'<=now<='z':
                keys.add(now)
            
            visited[nx][ny]=True
            dfs(nx,ny)

     

    후 처리

    위와 같은 방식으로 가장자리에서부터 탐색을 하게 된다면 가장자리에서부터 갈 수 있는 모든 좌표를 갈 수 있습니다. 하지만, 아직 가지 못한 부분도 존재합니다.

    탐색을 하다보면 새로운 키를 찾기도 하는데 새로운 키보다 해당 키의 문을 먼저 찾았다면, 실제적으로는 해당 문을 통과 할 수 있으나 위의 로직 상에서는 문을 열지 못하게 됩니다.

    그러므로, 탐색 중 찾은 문의 좌표들 중 가지고 있는 키로 열 수 있는 문이 있다면 또 탐색 할 수 있도록 해주었습니다.

    while True:
        for key in list(keys):
            for x,y in door[key.upper()]:
                if visited[x][y]:
                    continue
    
                maps[x][y]='.'
                visited[x][y]=True
                dfs(x,y)

    저와 같은 경우 위의 반복문을 탈출하는 조건 key의 개수로 파악했습니다.

    위의 반복문은 현재까지 찾은 key와 맞는 문 중 방문하지 않은 문만 탐색합니다. 이 과정에서 새로운 키를 찾게 된다면, 그리고 그 새로운 키가 이전의 키들과 겹치지 않은 키라면 또 새로운 문을 열 수 있는 가능성이 생기게 되는 것입니다. 그러므로, keys 집합의 길이를 미리 저장해두고 모든 탐색이 끝난 다음, 두 길이를 비교해 반복문 탈출 여부를 정했습니다.

    • 이전의 keys 집합의 길이와 현재 keys 집합의 길이가 같다면 새로운 키를 찾지 못했으므로 현재 키로 또 열 수 있는 문은 없다는 뜻이므로 반복문을 탈출합니다.
    • 이전의 keys 집합의 길이와 현재 keys 집합의 길이가 다르다면 새로운 키를 찾았으므로 아직 열리지 않은 문 중 열 수 있는 문이 존재할 수 있다는 뜻이므로 다시 탐색하도록 했습니다.
    while True:
        prevKeyLength=len(keys)
    
        ...
    
        if prevKeyLength==len(keys):
            break

     

    dfs 함수 내에서 현재 좌표에 문서가 존재하면 정답을 1 증가시켜줬으므로, 마지막으로 답을 출력해주면 됩니다.

     

    + 리팩토링

    시작점의 경우의 수에서 같은 코드가 많이 반복되는 것을 확인 할 수 있습니다. 이를 좀 더 간소화해주도록 했습니다.

    각 if문 내부에서 방문 여부를 바꿔주고 dfs 함수를 실행해주는 부분이 반복되고 있으므로 이를 마지막으로 빼줌으로써 return 되지 않는 한 모든 조건에서 실행될 수 있도록 했습니다. 그리고 각 조건에서만 실행하는 부분은 if문을 통해 해당 조건에서만 실행해줄 수 있도록 바꾸어주었습니다.

    def find(x,y):
        global answer
        now=maps[x][y]
        
        if maps[x][y]=='*' or visited[x][y]:
            return 
        
        if 'A'<=now<='Z':
            if now.lower() in keys:
                maps[x][y]='.'
            else:
                door[now].append((x,y))
                return
        
        if 'a'<=now<='z':
            keys.add(now)
    
        visited[x][y]=True
        dfs(x,y)

     

     

    해결

    조건들을 조금씩 빼먹고 코드를 작성하다보니 실수를 많이 했던 문제입니다. 그래도 스스로 문제를 풀어보고 리팩토링까지 해봄으로써 해결할 수 있었습니다.👍

     

    문제 링크 : https://www.acmicpc.net/problem/9328

     

    9328번: 열쇠

    상근이는 1층 빌딩에 침입해 매우 중요한 문서를 훔쳐오려고 한다. 상근이가 가지고 있는 평면도에는 문서의 위치가 모두 나타나 있다. 빌딩의 문은 모두 잠겨있기 때문에, 문을 열려면 열쇠가

    www.acmicpc.net

     

    반응형
    LIST

    '알고리즘' 카테고리의 다른 글

    백준 3109번 빵집 (Python)  (2) 2023.08.31
    백준 23290번 마법사 상어와 복제 (Python)  (2) 2023.08.11
    백준 2042번 구간 합 구하기  (2) 2023.08.09
    백준 1069번 집으로  (0) 2023.08.08
    백준 2470번 두 용액  (0) 2023.08.07

    댓글

Designed by Tistory.