1일1알

백준 9328번 열쇠 C++ (골드1) 본문

알고리즘

백준 9328번 열쇠 C++ (골드1)

영춘권의달인 2024. 5. 26. 17:41

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

 

크게 보면 단순 bfs 구현 문제인데, 어떤 열쇠가 있어야 특정 문을 열 수 있고, 맵 밖으로 나갔다고 끝이 아니라 다른 외곽 지역으로 들어올 수 있다는 추가 조건이 있는 문제이다.

 

우선 방문 표시를 할때 단순히 true, false로 표시하면 안된다.

어떤 열쇠를 보유하지 않고 특정 문에 도달했을때와 보유한 상태에서 도달했을 때 차이가 있기 때문이다.

그래서 비트연산을 이용해서 어떤 열쇠들을 가진 상태로 방문했는지를 표시하였다.

예를들어 a열쇠와 c열쇠가 있다면 101(cba)이고 10진수로 변환하면 5가 된다.

이렇게 하면 정수 하나로 어떤 열쇠들을 가지고 방문했는지 표시할 수 있다.

 

그리고 어떤 열쇠를 가지고 있는지 확인할 수 있어야 한다.

보유한 열쇠들도 위와 마찬가지로 비트연산을 통해 저장하였고, 이렇게 하면 문에 도달했을때 해당 문을 여는데 필요한 열쇠와 보유한 열쇠들을 or연산을 통해 문을 열 수 있는지 확인할 수 있다.

 

이후에는 bfs길찾기 문제처럼 해결하면 되고, 만약 다음에 도달할 좌표가 맵 밖으로 나가게 된다면 다시한변 외곽을 조사하여 큐에 넣어준다.

 

사실 삽질을 좀 할 줄 알았는데 한번에 통과되어서 다행이었다.

#include <iostream>
#include <string>
#include <vector>
#include <math.h>
#include <algorithm>
#include <utility>
#include <stack>
#include <queue>
#include <math.h>
#include <set>
#include <map>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <iomanip>
#include <limits.h>

using namespace std;
using int64 = long long;

int r, c;
int myKeyBit = 0;
int dRow[4] = { -1,0,1,0 };
int dCol[4] = { 0,1,0,-1 };
queue<pair<int, int>> q;
vector<vector<char>> board;
vector<vector<int>> found;

void AddKey(char key)
{
    int keyNum = key - 'a';
    myKeyBit |= (1 << keyNum);
}

bool HaveKey(char key)
{
    int keyNum = key - 'a';
    bool haveKey = ((1 << keyNum) & (myKeyBit));
    return haveKey;
}

bool CanGo(int row, int col)
{
    // 벽이면 못감
    if (board[row][col] == '*') return false;
    // 빈공간/문서가 있는 공간이면 갈수있음
    if (board[row][col] == '.' || board[row][col] == '$') return true;
    // 열쇠가 있는 공간이면 갈수있음
    if (board[row][col] >= 'a' && board[row][col] <= 'z') return true;

    // 문이면 열쇠가 있으면 갈수있음
    char door = board[row][col];
    char requiredKey = tolower(door);
    if (HaveKey(requiredKey)) return true;
    return false;
}

// 외곽지역들 갈 수 있는지 검사하여 큐에 넣어준다.
void PushEntrancePos() {
    for (int i = 0; i < r; i++)
    {
        if (CanGo(i, 0))
        {
            if (found[i][0] != myKeyBit)
            {
                q.push({ i,0 });
                found[i][0] = myKeyBit;
            }
        }
        if (CanGo(i, c - 1))
        {
            if (found[i][c - 1] != myKeyBit)
            {
                q.push({ i,c - 1 });
                found[i][c - 1] = myKeyBit;
            }
        }
    }
    for (int i = 0; i < c; i++)
    {
        if (CanGo(0, i))
        {
            if (found[0][i] != myKeyBit)
            {
                q.push({ 0,i });
                found[0][i] = myKeyBit;
            }
        }
        if (CanGo(r - 1, i))
        {
            if (found[r - 1][i] != myKeyBit)
            {
                q.push({ r - 1,i });
                found[r - 1][i] = myKeyBit;
            }
        }
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    
    int t;
    cin >> t;
    while (t--)
    {
        int ans = 0;
        cin >> r >> c;
        board = vector<vector<char>>(r, vector<char>(c));
        found = vector<vector<int>>(r, vector<int>(c, -1));
        for (int i = 0; i < r; i++)
        {
            for (int j = 0; j < c; j++)
            {
                cin >> board[i][j];
            }
        }
        string initialKeys;
        cin >> initialKeys;

        if (initialKeys != "0")
        {
            for (int i = 0; i < initialKeys.length(); i++)
            {
                char key = initialKeys[i];
                AddKey(key);
            }
        }

        PushEntrancePos();

        while (!q.empty())
        {
            auto a = q.front();
            q.pop();
            if (board[a.first][a.second] == '$')
            {
                // 문서가 있다면 훔친 문서의 개수를 1 증가시키고 해당 공간을 빈 공간으로 밀어준다.
                board[a.first][a.second] = '.';
                ans++;
            }

            if (board[a.first][a.second] >= 'a' && board[a.first][a.second] <= 'z')
            {
                // 열쇠가 있다면 열쇠를 주워서 보유한 열쇠를 나타내는 비트에 해당 열쇠를 추가하고 빈 공간으로 밀어준다.
                AddKey(board[a.first][a.second]);
                board[a.first][a.second] = '.';
            }

            // 현재 위치에서 상하좌우 검사
            for (int i = 0; i < 4; i++)
            {
                int nextRow = a.first + dRow[i];
                int nextCol = a.second + dCol[i];
                if (nextRow < 0 || nextRow >= r) {
                    // 밖으로 나가게 된다면 외곽 지역들을 검사하여 갈 수 있는 공간들을 큐에 넣어준다.
                    PushEntrancePos();
                    continue;
                }
                if (nextCol < 0 || nextCol >= c) {
                    // 밖으로 나가게 된다면 외곽 지역들을 검사하여 갈 수 있는 공간들을 큐에 넣어준다.
                    PushEntrancePos();
                    continue;
                }
                // 다음 공간으로 갈 수 있고 현재 열쇠들을 가진 상태로 아직 방문하지 않았다면 큐에 넣어주고 방문 표시를 해준다.
                if (CanGo(nextRow, nextCol) && found[nextRow][nextCol] != myKeyBit) {
                    q.push({ nextRow,nextCol });
                    found[nextRow][nextCol] = myKeyBit;
                }
            }
        }
        cout << ans << "\n";
        myKeyBit = 0;
    }
}