코딩/백준

[백준]2206번 벽 부수고 이동하기 - C++

최선을 다하는 2022. 11. 4. 17:00

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

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

www.acmicpc.net

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다. (1, 1)과 (N, M)은 항상 0이라고 가정하자.

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.


두 점 사이의 최단 거리이므로 다익스트라 알고리즘을 생각할 수 있다. 다만 이 문제의 키 포인트는 벽을 단 한번 부술 수 있다는 것이다. 보통은  이러한 조건이 없으므로 dist 배열이 주어진 행렬과 같은 차원으로 설정하면 되겠지만 이것은 벽을 부수는 기회를 사용한 거리인지 아닌 거리인지 판단하기 위해 차원 1개를 더한다. dist[i][j][0] 는 벽을 한 번도 부수지 않고 해당 지점까지의 최단 거리, dist[i][j][1]은 벽을 한번 부수고 해당 지점까지의 최단거리로 설정을 한다. 벽을 한 번도 안 부쉈을 때는 가고 싶은 블록이 1 이어도 비용을 내고 갈 수 있게 해 준다. 부순 경우는 무조건 다음 블록이 0 일 때만 이동이 가능하다.

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define INF 987654321

int N, M, res = INF;
int mat[1010][1010];
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,1,-1};
int dist[1010][1010][2]; 

class Info{
    public : 
    int x;
    int y;
    int cost;
    int coin;
    Info (int _x, int _y, int _cost, int _coin){
        x = _x;
        y = _y;
        cost = _cost;
        coin = _coin;
    }
    
};

bool operator< (const Info& s1, const Info& s2){
    return s1.cost > s2.cost;
}

void solve(){
    priority_queue <Info> pq;
    dist[1][1][0] = 1;
    pq.push(Info(1,1,1,0));
    while(!pq.empty()){
        Info cur = pq.top(); pq.pop();
        if(cur.x == N && cur.y == M){
            res =  cur.cost;
            break;
        }
        //cout << cur.x << " " << cur.y << "\n";
        for(int k = 0 ; k < 4;k++){
            int nx = dx[k] + cur.x;
            int ny = dy[k] + cur.y;
            if(mat[nx][ny] == 0 && dist[nx][ny][cur.coin] > cur.cost + 1){
                dist[nx][ny][cur.coin] = cur.cost + 1;
                pq.push(Info(nx,ny,cur.cost + 1,cur.coin));
            }
            else if(mat[nx][ny] == 1 && cur.coin == 0 && dist[nx][ny][1] > cur.cost + 1){
                dist[nx][ny][1] = cur.cost + 1;
                pq.push(Info(nx,ny,cur.cost + 1,1));
            }
        }
    }
}

int main (){
    cin >> N >> M;
    for(int i  = 0 ; i <= N + 1; i++){
        for(int j = 0 ; j <= M + 1; j++){
            if( i == 0 || i == N + 1 || j == 0 || j == M + 1)
                mat[i][j] = 2;
            else
                scanf("%1d",&mat[i][j]);
            dist[i][j][0] = INF;
            dist[i][j][1] = INF;
        }
    }
    solve();
    if(res != INF)
        cout << res;
    else
        cout << -1;
}

처음에 3차원 배열을 두는 것 까지 생각을 하고 코드를 짰다. 이 정도는 dfs로 풀 수 있을 것 같아 작성하였지만 시간 초과가 났다. 어차피 두 점 사이의 최소 거리이므로 다익스트라 알고리즘을 활용하여 코드를 작성하였다. 이전이었으면 다익스트라 알고리즘을 사용한다는 생각까지 하고 코드를 바꾸기 무서워하였을 텐데 이번에는 연습이 많이 되어서 쉽게 바꿀 수 있었다! 다만 여태껏 풀어온 문제에서는 cost와 pos 두 가지 원소만 있어도 되었지만 이번에는 이외에도 벽을 부순 횟수도 저장하기 위해 class를 활용하여 priority_queue를 사용하였다. 

'코딩 > 백준' 카테고리의 다른 글

[백준] 2011번 암호코드 - C++  (0) 2022.11.15
[백준]17298번 오큰수 - C++  (0) 2022.11.09
[백준]1261번 알고스팟 - C++  (0) 2022.11.03
[백준]1504번 특정최단경로 - C++  (0) 2022.11.02
[백준]13549번 숨바꼭질3 - C++  (0) 2022.11.01