[프로그래머스] 경주로 건설

✔️ 문제

✔️ 문제 이해

  • 경주로 부지에 자동차 경주로를 건설 해야한다.
  • 경주로는 N x N 크기의 정사각형 격자 형태이며 각 격자는 1 x 1 크기다.
  • 각 격자의 칸은 0 또는 1 로 채워져 있으며, 0은 칸이 비어 있음을 1은 해당 칸이 벽으로 채워져 있음을 나타낸다.
  • 경주로의 출발점은 (0, 0)이다.
  • 경주로의 도착점은 (N-1, N-1) 이다.
  • 경주로는 끊기지 않아야한다.
  • 경주로는 상, 하, 좌, 우로 인접한 두 빈 칸을 연결하여 건설할 수 있고, 벽이 있는 칸은 경주로를 건설할 수 없다.
    • 인접한 두 빈 칸을 상하 또는 좌우로 연결한 경주로를 직선 도로 라고 한다. (1개당 비용 100원)
    • 직선 도로가 서로 직각으로 만나는 지점을 코너 라고 한다. (1개당 비용 500원)
  • 경주로를 건설하는데 필요한 최소 비용을 계산 해야한다.

✔️ 알고리즘 설계

  1. BFS를 사용해 (0, 0) 부터 (N-1, N-1) 까지 최단거리를 구한다.

✔️ 상세 구현 설명

  • 정점은 다음과 같이 정의한다.
    • x좌표, y좌표, straight(직선도로 개수), corner(코너 개수), cost(비용), dist(방향)
  • 정점의 상태 공간을 다음 4가지 요소로 정의한다.
    • x좌표, y좌표, straight(직선도로 개수), corner(코너 개수)
  • bfs를 시작하기 전, queue에 (0,0) 위치의 정점을 넣고 bfs를 돌린다.
    • 정점의 방향과 새로 이동할 방향을 비교해서 코너 여부를 판별한다.
    • 설치되는 직선도로와 코너 개수에 따라 비용을 증가시킨다.
    • 새롭게 이동되는 좌표, 직선 도로 개수, 코너 개수, 비용, 방향을 업데이트하고 정점을 만들어서 queue에 넣는다.
    • (N-1, N-1) 에 도착한 정점을 발견하면 최소 비용인지 확인한다.

👨🏻‍💻 소스 코드

#include <string>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;

typedef struct {
	// xy 좌표 직선도로 개수, 코너 개수, 비용, 방향
	int x, y, straight, corner, cost, dist;
}road;

// x좌표, y좌표, 직선도로 개수, 코너 개수! 이렇게 4가지로 그래프의 정점을 정의한다.
bool check[25][25][500][500];
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,-1,1 };

int bfs(vector<vector<int>> board) {
	int ret = 0x7fffffff; // (n-1, n-1)까지의 최소 비용을 저장할 변수
	int size = board[0].size();

	queue<road> q;
	q.push({ 0,0,0,0,0,0 }); // (0,0)에서 시작한다. 
	check[0][0][0][0] = true;

	while (!q.empty()) {
		int x = q.front().x, y = q.front().y,
			straight = q.front().straight, corner = q.front().corner,
			cost = q.front().cost, dist = q.front().dist;
		q.pop();

		if (ret <= cost) continue; // cost가

		// (n-1, n-1)에 도착했다면, 최소비용인지 확인한다!
		if (x == size - 1 && y == size - 1) {
			ret = min(ret, cost);
			continue;
		}

		// 4방향 BFS 탐색
		for (int i = 0; i < 4; i++) {
			int nx = x + dx[i], ny = y + dy[i];
			
			// board 범위를 초과할 경우
			if (nx >= size || nx < 0 || ny >= size || ny < 0) continue;
			// 방문할 곳에 벽이 있을 경우
			if (board[nx][ny]) continue; 
			// nCost : 도로 건설 비용은 100이다.
			int nStraight = straight, nCorner = corner, nCost = cost + 100;

			// (0,0) 에서 시작하는 경우 방향을 아래와 같이 보정해준다. 
			if (x == 0 && y == 0) dist = i; 

			if (i == dist) { // 직선 도로일 경우(방향이 안바뀌는 경우)
				nStraight++; // 직선 도로개수 증가
			}
			else { // 코너일경우 
				corner++; // 코너 개수 증가
				nCost += 500; // 코너 건설비용 500 추가
			}

			// 이미 방문했던 정점이라면(이미 확인했던 경우의 수라면), 스킵
			if (check[nx][ny][nStraight][corner]) continue; 
			check[nx][ny][nStraight][corner] = true;
			q.push({ nx,ny,nStraight, nCorner, nCost, i });
		}
	}

	return ret;
}

int solution(vector<vector<int>> board) {
	int answer = 0;
	return bfs(board);
}

✔️ 문제 회고

  • BFS 알고리즘에 대한 개념을 알고 활용을 할 수 있어야 풀 수 있는 문제다.
  • 정점의 상태를 4가지 요소로 정의하는 것이 포인트다.
  • 다익스트라 알고리즘으로도 풀 수 있는데 나중에 풀어봐야겠다.