분류 : bfs, 3차원 bfs /
문제
문제 설명
건설회사의 설계사인 죠르디
는 고객사로부터 자동차 경주로 건설에 필요한 견적을 의뢰받았습니다.
제공된 경주로 설계 도면에 따르면 경주로 부지는 N x N
크기의 정사각형 격자 형태이며 각 격자는 1 x 1
크기입니다.
설계 도면에는 각 격자의 칸은 0
또는 1
로 채워져 있으며, 0
은 칸이 비어 있음을 1
은 해당 칸이 벽으로 채워져 있음을 나타냅니다.
경주로의 출발점은 (0, 0) 칸(좌측 상단)이며, 도착점은 (N-1, N-1) 칸(우측 하단)입니다. 죠르디는 출발점인 (0, 0) 칸에서 출발한 자동차가 도착점인 (N-1, N-1) 칸까지 무사히 도달할 수 있게 중간에 끊기지 않도록 경주로를 건설해야 합니다.
경주로는 상, 하, 좌, 우로 인접한 두 빈 칸을 연결하여 건설할 수 있으며, 벽이 있는 칸에는 경주로를 건설할 수 없습니다.
이때, 인접한 두 빈 칸을 상하 또는 좌우로 연결한 경주로를 직선 도로
라고 합니다.
또한 두 직선 도로
가 서로 직각으로 만나는 지점을 코너
라고 부릅니다.
건설 비용을 계산해 보니 직선 도로
하나를 만들 때는 100원이 소요되며, 코너
를 하나 만들 때는 500원이 추가로 듭니다.
죠르디는 견적서 작성을 위해 경주로를 건설하는 데 필요한 최소 비용을 계산해야 합니다.
예를 들어, 아래 그림은 직선 도로
6개와 코너
4개로 구성된 임의의 경주로 예시이며, 건설 비용은 6 x 100 + 4 x 500 = 2600원 입니다.
또 다른 예로, 아래 그림은 직선 도로
4개와 코너
1개로 구성된 경주로이며, 건설 비용은 4 x 100 + 1 x 500 = 900원 입니다.
도면의 상태(0은 비어 있음, 1은 벽)를 나타내는 2차원 배열 board가 매개변수로 주어질 때, 경주로를 건설하는데 필요한 최소 비용을 return 하도록 solution 함수를 완성해주세요.
[제한사항]
- board는 2차원 정사각 배열로 배열의 크기는 3 이상 25 이하입니다.
- board 배열의 각 원소의 값은 0 또는 1 입니다.
- 도면의 가장 왼쪽 상단 좌표는 (0, 0)이며, 가장 우측 하단 좌표는 (N-1, N-1) 입니다.
- 원소의 값 0은 칸이 비어 있어 도로 연결이 가능함을 1은 칸이 벽으로 채워져 있어 도로 연결이 불가능함을 나타냅니다.
- board는 항상 출발점에서 도착점까지 경주로를 건설할 수 있는 형태로 주어집니다.
- 출발점과 도착점 칸의 원소의 값은 항상 0으로 주어집니다.
입출력 예
board | result |
---|---|
[[0,0,0],[0,0,0],[0,0,0]] | 900 |
[[0,0,0,0,0,0,0,1],[0,0,0,0,0,0,0,0],[0,0,0,0,0,1,0,0],[0,0,0,0,1,0,0,0],[0,0,0,1,0,0,0,1],[0,0,1,0,0,0,1,0],[0,1,0,0,0,1,0,0],[1,0,0,0,0,0,0,0]] | 3800 |
[[0,0,1,0],[0,0,0,0],[0,1,0,1],[1,0,0,0]] | 2100 |
[[0,0,0,0,0,0],[0,1,1,1,1,0],[0,0,1,0,0,0],[1,0,0,1,0,1],[0,1,0,0,0,1],[0,0,0,0,0,0]] | 3200 |
입출력 예에 대한 설명
입출력 예 #1
본문의 예시와 같습니다.
입출력 예 #2
위와 같이 경주로를 건설하면 직선 도로
18개, 코너
4개로 총 3800원이 듭니다.
입출력 예 #3
위와 같이 경주로를 건설하면 직선 도로
6개, 코너
3개로 총 2100원이 듭니다.
입출력 예 #4
붉은색 경로와 같이 경주로를 건설하면 직선 도로
12개, 코너
4개로 총 3200원이 듭니다.
만약, 파란색 경로와 같이 경주로를 건설한다면 직선 도로
10개, 코너
5개로 총 3500원이 들며, 더 많은 비용이 듭니다.
풀이
#include <vector>
#include <queue>
using namespace std;
struct p { int x, y, d, cost; };
bool comp(p p1, p p2) { return p1.cost > p2.cost; }
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
int cost[25][25][4];
int solution(vector<vector<int>> board) {
int dest = board.size()-1;
priority_queue<p,vector<p>,decltype(&comp)> pq(comp);
pq.push({0, 0, -1, -500});
board[0][0] = 1;
while(!pq.empty()) {
p cur = pq.top(); pq.pop();
for(int d=4; d--;) {
if(cur.d != d && (cur.d%2 == d%2)) continue;
int nx = cur.x + dx[d], ny = cur.y + dy[d];
if(nx<0 || nx>dest || ny<0 || ny>dest) continue;
if(board[nx][ny] == 1) continue;
int ncost = cur.cost + 100;
if(cur.d != d) ncost += 500;
if(cost[nx][ny][d] == 0 || cost[nx][ny][d] > ncost) {
cost[nx][ny][d] = ncost;
pq.push({nx, ny, d, ncost});
}
}
}
int ans = ~(1<<31);
for(int c:cost[dest][dest])
if(c > 0 && c < ans) ans = c;
return ans;
}
문제를 보고 제일 먼저 든 생각은 다익스트라. 그래서 처음에 다익스트라로 구현했다.
제출하고 생각해 보니 다익스트라를 이용한 풀이는 방향에 따른 가중치는 고려 했지만 문제점이 있다.
경로 A -> B -> C 에서, A -> B의 최단 경로가 A -> B -> C의 최단 경로의 부분 경로가 아닐 수 있다.
다익스트라는 출발점에서 해당 노드까지 한번 최단거리가 계산되면 더 이상 최단거리를 업데이트하지 않는다.
뭐 하려면 할 수는 있다. 이것도 다익스트라라고 부르는 지는 모르겠지만 노드에 비용을 저장하지 않고 모든 경로가 목적지에 도달할 때까지 경로가 비용을 갖게하고 돌리면 된다.
무한루프라고 생각할 수 있지만 힙 자료구조를 사용해 하나라도 도착한 경우의 도착한 비용의 최솟값보다 크면 버리는 식으로 구현하면 유한시간에 해를 얻을 수 있다. 그렇게 해서 제출해 보니 맞긴 하지만 7개 정도 테스트케이스에서 시간초과가 났다.
C++에서 시간초과? 그냥 잘못 푼 거다.
위 풀이에서 경우의 수가 압도적으로 늘어나는 이유는 두 개의 경로가 하나의 노드를 지날 때 각 경로가 해당 노드를 어떤 방향으로 들어오고 나가냐에 따라 비용이 더 큰 값이 최적의 해가 될 수 있기 때문에 노드를 지날때 마다 3가지 경우로 증식한다. 지수적으로 경우의 수가 늘어나는 것도 문제지만 가장 결정적인 문제는 최대 칸의 수를 넘어서도 증식하는 것이다. 최소 비용을 노드에 저장하지 않고 각 경로에 저장하기 때문에 경로간의 최소 비용이 공유되지 않는다. 그러다보니 부분 경로도 계속 경로탐색이 진행된다. 이게 참 골치 아팠다. 노드에 최소 비용을 저장할 수 없는 이유는 위에서도 말했듯 노드의 최소 비용이 전체의 최소 비용이 아닐 수 있기 때문이다. 아래와 같은 상황을 예로 들 수 있다.
초록색 칸은 파란색 경로의 비용이 빨간색 경로의 비용이 더 작다. 하지만 목적지에서는 빨간색 경로의 비용이 더 작다.
부분의 최적 해가 전체의 최적해가 아닐 수 있기 때문에 최적해를 노드에 저장할 수 없고 모든 경로에 대해 최적해를 계산해야 한다.
어떻게 하는지 알 거 같은데 시간초과가 날 땐 메모리를 많이 쓰면 된다. 메모리로 시간을 사버려. 시간과 메모리의 트레이드오프!
방향마다 경로의 값이 달라지는 게 문제다. 이걸 다른 말로 하면 방향마다 경로는 최적해를 계산할 수 있다. 즉 각 노드마다 각 방향에서 4개의 최적해를 가질 수 있다.
이를 조금 다르게 생각하면 문제의 그래프는 2차원 무방향 그래프가 아닌 3차원 가중 방향 그래프로 이해할 수 있다.
그리기 힘들어서 생략했지만 각 방향에 대해 레이어가 있어 총 4개의 레이어가 있고 레이어 간 이동은 층수와 상관없이 500의 가중치, 같은 레이어에서는 각 레이어의 방향으로 만 이동 가능하고 100의 가중치를 갖는 그래프 문제로 이해할 수 있다. 즉 문제의 2차원 노드 한 개는 4개의 레이어에서 각각 해당 노드에 해당되는 4개의 노드를 의미한다.
이를 3차원 배열로 계산할 수 있다.
코드에서 while문 마지막의 `cost[nx][ny][d]`는 `d`레이어의 `(nx, ny)`위치의 노드를 의미하며 위 3차원 그래프로 BFS를 수행하면 문제를 해결할 수 있다.
'Problem Solving > Programmers' 카테고리의 다른 글
[프로그래머스 - 148653] 마법의 엘리베이터 - C++ (0) | 2023.10.17 |
---|---|
[프로그래머스 - 49189] 가장 먼 노드 - C++ (1) | 2023.10.11 |
[프로그래머스 - 12936] 줄 서는 방법 - C++ (1) | 2023.10.05 |
[프로그래머스 - 43164] 여행경로 - Python (0) | 2023.09.11 |
[프로그래머스 - 72410] 신규 아이디 추천 - Java (0) | 2023.09.09 |