study/coding test

백준 11451 - 팩맨

Acver 2021. 12. 21. 15:32


1. 문제 정리
  - 팩맨 두개가 존재한다.

  - 팩맨은 동서남북 4개의 방향으로 움직일 수 있다.

  - 하나의 팩맨을 움직이면 다른 팩맨 또한 같은 방향으로 움직인다.

  - 벽('X')이 존재하는 칸으로 가려하면 해당 팩맨은 움직이지 않는다.

  - 유령('G')이 존재하는 칸으로는 이동할 수 없다.

  - 팩맨이 화면 밖으로 나가면 반대에서 나타난다.

  - 두개의 팩맨의 좌표를 갖게 할 수 있을 때, 해당 지점까지 이동 방향의 문자열과 이동 횟수를 출력한다.


2. 접근
  - BFS로 풀 수 있는 문제라 생각하였다.

  - 하나의 팩맨을 기준으로 움직일 때 다른 팩맨의 움직임도 계산을 해주었다.

  - 방문 기록을 위한 4차원 배열을 만들었다.

 
3. 풀이

//2021.12.21
#include<iostream>
#include<vector>
#include<queue>
#include<set>
using namespace std;

struct pack{
    int x, y;
    string ans = "";
}typedef pack;

int n, m;
char a[50][50];
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
char d[] = {'N', 'S', 'W', 'E'};
int check[50][50][50][50] = {0, };
vector<pack> p;

void bfs(){
    queue<pair<pack, pack> > q;
    q.push({p[0], p[1]});
    while(!q.empty()){
        pack node1 = q.front().first;
        pack node2 = q.front().second;
        q.pop();
        if(node1.x == node2.x && node1.y == node2.y) {
            cout << check[node1.x][node1.y][node2.x][node2.y]-1 << ' ' << node1.ans << '\n';
            return;
        }
        for(int i = 0; i < 4; i++){
            pack next1 = {node1.x+dx[i], node1.y+dy[i], node1.ans+d[i]};
            pack next2 = {node2.x+dx[i], node2.y+dy[i], node2.ans+d[i]};
            if(next1.x < 0) next1.x = n-1;
            else if(next1.x >= n) next1.x = 0;
            if(next1.y < 0) next1.y = m-1;
            else if(next1.y >= m) next1.y = 0;
            if(next2.x < 0) next2.x = n-1;
            else if(next2.x >= n) next2.x = 0;
            if(next2.y < 0) next2.y = m-1;
            else if(next2.y >= m) next2.y = 0;
            if(a[next1.x][next1.y] == 'G' || a[next2.x][next2.y] == 'G') continue;
            if(a[next1.x][next1.y] == 'X'){
                next1.x = node1.x; next1.y = node1.y;
            }
            if(a[next2.x][next2.y] == 'X'){
                next2.x = node2.x; next2.y = node2.y;
            }
            if(check[next1.x][next1.y][next2.x][next2.y] > check[node1.x][node1.y][node2.x][node2.y]+1){
                check[next1.x][next1.y][next2.x][next2.y] = check[node1.x][node1.y][node2.x][node2.y]+1;
                q.push({next1, next2});
            }
        }
    }
    cout << "IMPOSSIBLE" << '\n';
    return;
}

int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;

    for(int i = 0; i < t; i++){
        cin >> n >> m;
        for(int j = 0; j < n; j++){
            for(int k = 0; k < m; k++){
                cin >> a[j][k];
                if(a[j][k] == 'P'){
                    a[j][k] = '.';
                    p.push_back({j, k, ""});
                }
            }
        }
        for(int j = 0; j < n; j++){
            for(int k = 0; k < m; k++){
                for(int s = 0; s < n; s++){
                    for(int l = 0; l < m; l++){
                        check[j][k][s][l] = 2100000000;
                    }
                }
            }
        }
        check[p[0].x][p[0].y][p[1].x][p[1].y] = 1;
        bfs();
        p.clear();
    }

}


  - int dx[], dy[] : 각 x 방향, y 방향으로의 이동을 위한 배열이다.

  - char d[] : 이동한 방향에 따른 경로 저장의 편의를 위한 배열이다.

  - int check[][][][] : 순서대로 x1, y1, x2, y2를 이용하여  방문한 좌표인지 확인하고, 해당 좌표까지 최소 이동 횟수를 저장한다.

  - void bfs() : bfs를 수행하며, 팩맨의 좌표를 의미하는 node1, node2의 각 x, y 좌표가 일치할 경우에는 node1에 저장된 경로 문자열, check[node1.x][node1.y][node2.x][node2.y] 값을 출력하고, 그러지 않고 더 이상 bfs를 수행하지 못할 경우에는 "IMPOSSIBLE"을 출력하고 반환한다

    -> for문을 이용하여 dx, dy 배열을 활용해 각 팩맨의 위치를 변환한다. 그 중에 팩맨의 변환된 위치에 유령이 존재할 경우는 해당 과정을 뛰어넘고(continue), 벽이 존재하거나 화면 밖으로 나갈 경우 좌표를 재지정해준다. 그리고 해당 좌표의 check 배열을 검사하여 방문한 적이 없거나, 방문하기까지의 이동 횟수가 더 적을 경우 방문한 것으로 간주하여 queue에 팩맨의 좌표를 저장하고 check 배열의 값을 변화시킨다.
 
4. 소감

  오랜만에 푸는 bfs문제였다. 사실 완전탐색을 찾아보다가 풀게 된 문제인데, 표시된 난이도에 비해 그렇게 어렵게 느껴지지 않고 한번에 풀이 방법이 생각나고 그렇게 적용할 수 있었던 것에 대해 실력이 늘었구나라는 생각과 문제 풀이의 재미도 더 느낄 수 있었다.