[백준] 스타트 택시
✔️ 문제
- 문제 링크 : https://www.acmicpc.net/problem/19238
- 2020 상반기 삼성전자 SW 역량테스트 기출 문제다.
✔️ 문제 이해
- NxN 격자형 맵이 주어진다. 각 칸은 비어있거나(0) 벽(1)이 놓여있다.
- 택시는 상하좌우 인접한칸으로 이동가능하고 태워야할 승객이 M명 존재한다.
- 택시는 승객을 1명만 태울 수 있다. 따라서 승객을 태우고, 목적지로 이동하는 일을 M번 반복해야한다.
- 승객은 움직이지 않는다. 승객은 출발지에서만 택시를 타고 목적지에서 내릴 수만 있다.
- 택시는 승객을 태울때 현재 위치에서 최단거리가 가장 짧은 승객을 태운다.
- 그런 승객이 여러명일 경우 행번호가 적은순, 열번호가 적은 순으로 태운다.
- 택시는 승객을 목적지로 이동시킨다.
- 한 칸 이동할 때마다 연료가 1 소모된다.
- 승객을 목적지로 이동시키면 승객을 태워 이동하면서 소모한 연료량의 두 배가 충전된다.
- 이동하는 중에 연료가 바닥나면 실패다. 승객을 목적지로 이동시킨 동시에 연료가 바닥나는 경우는 실패가 아니다.
- N, M, 초기 연료량 그리고 손님의 출발지 위치와 목적지 위치가 주어졌을 때 모든 손님을 이동시키고 연료를 충전했을 때 남은 연료량을 출력해야한다.
- 연료가 바닥나서 출발지 또는 목적지로 이동 불가한 경우와 모든 손님을 이동시킬 수 없는 경우는 -1을 출력한다.
✔️ 알고리즘 설계
- 두 개의 BFS 를 사용하여 구현한다.
- 승객을 태우러 가는 BFS
- 승객을 태우고 목적지로 가는 BFS
- 알고리즘
-
최단거리에 있는 손님을 태운다.
1-1. 연료가 바닥나거나 벽으로 막혀서 손님을 못태우면 -1을 출력하고 종료한다.
-
승객을 태웠으면 목적지로 간다.
2-1. 연료가 바닥나거나 벽으로 막혀서 목적지로 못데려다 주면 -1을 출력하고 종료한다.
-
1,2를 손님 수 m 만큼 반복한다.
✔️ 상세 구현 설명
- 위치 정보는 다음과 같이 구조체로 관리한다.
x좌표
,y좌표
,x,y 좌표까지 이동했을때 남은 연료량 f
- 최단 거리에 있는 고객은
BFS
로 찾으면 된다.BFS
는 가중치가 없는 그래프에서 최단거리를 찾아준다.- 최단 거리가 있는 고객이 여러명이라면
vector
에 모두 넣어둔다. - 그리고 연료량이 큰 순, 행이 적은순, 열이 적은 순으로 정렬한뒤 맨 앞 원소에 있는 고객을 태운다.
- 최단 거리가 있는 고객이 여러명이라면
- 승객을 태우고 목적지로 가는 것 역시
BFS
로 하면 된다.hash map
으로 승객의 목적지 정보를 저장해두고BFS
탐색시 매번 목적지 도달 여부를 파악한다.
👨🏻💻 소스 코드
#include<iostream>
#include<unordered_map>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef struct {
int x, y, f;
}client;
int n, m, fuel, tx, ty;
int map[20][20];
int clientMap[20][20];
unordered_map<int, pair<int, int>> destInfo;
const int dx[] = {-1,1,0,0 }, dy[] = {0,0,-1,1};
// 최단거리가 같은 손님이 여러명일 경우 아래 기준으로 sort 하면된다.
bool comp(client a, client b) {
if (a.f == b.f) {
if (a.x == b.x) {
return a.y < b.y; // 열번호가 적은순으로
}
else
return a.x < b.x; // 행번호가 적은순으로
}
else
return a.f > b.f; // 연료가 많은 순으로(거리가 가까운순으로)
}
// 손님을 목적지(=destNum)에 데려다주는 bfs
bool goToDest(int destNum) {
queue<client> q;
bool check[20][20];
int maxVal = -0x7fffffff;
vector<client> candi;
memset(check, 0, sizeof(check));
q.push({ tx,ty,fuel});
while (!q.empty()) {
int x = q.front().x, y = q.front().y,
f = q.front().f;
q.pop();
// 도착지라면
if (destInfo[destNum].first == x
&& destInfo[destNum].second == y) {
// 택시 위치, 연료 정보 업데이트
tx = x, ty = y, fuel = f + (fuel - f) * 2; // 소모한 연료의 2배 추가.
return true;
}
if (f == 0) continue; // 연료가 0일 경우
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
int nf = f;
if (nx >= n || nx < 0 || ny >= n || ny < 0) continue;
if (map[nx][ny]) continue;
if (check[nx][ny]) continue;
nf--; // 연료 감소
check[nx][ny] = true;
q.push({ nx, ny, nf });
}
}
return false;
}
bool rideClient(){ // 최단거리에 있는 손님들 태우러가는 bfs 함수
queue<client> q;
bool check[20][20];
int maxVal = -0x7fffffff;
vector<client> candi;
memset(check, 0, sizeof(check));
q.push({ tx,ty,fuel});
while (!q.empty()) {
int x = q.front().x, y = q.front().y,
f = q.front().f;
q.pop();
if (f == 0) continue; // 연료가 0일 경우
if (clientMap[x][y] > 0) { // 손님을 발견한 경우.
if (f >= maxVal) { // v연료를 덜 소모해야 가까운 곳이다.
candi.push_back({ x,y,f }); // 손님 후보군을 넣는다.
}
continue;
}
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
int nf = f;
if (nx >= n || nx < 0 || ny >= n || ny < 0) continue;
if (map[nx][ny]) continue;
if (check[nx][ny]) continue;
nf--; // 연료 감소
check[nx][ny] = true;
q.push({ nx,ny, nf });
}
}
if (candi.size() == 0) // 손님 후보군이 없다면
return false;
// 정렬을 사용해 행, 열이 적은 손님을 찾는다.
sort(candi.begin(), candi.end(), comp);
int x = candi[0].x, y = candi[0].y, f = candi[0].f;
int destNum = clientMap[x][y];
clientMap[x][y] = 0; // clientMap 정보 업데이트
tx = x, ty = y, fuel = f; // 택시 정보 업데이트
return goToDest(destNum); // 태울 손님 번호(=목적지 번호)를 넣는다.
}
int solve(){
for (int i = 0; i < m; i++) {
if (!rideClient())
return -1;
}
return fuel;
}
int main() {
cin >> n >> m >> fuel;
for (int i = 0; i < n; i++) { // map 정보를 입력받는다.
for (int j = 0; j < n; j++) {
cin >> map[i][j];
}
}
cin >> tx >> ty; // 택시 정보 입력
tx--, ty--;
for (int i = 1; i <= m; i++) {
int sx, sy, ex, ey;
cin >> sx >> sy >> ex >> ey;
clientMap[sx - 1][sy - 1] = i; // 손님 위치 정보를 2차원 배열로 관리
destInfo[i] = { ex - 1, ey - 1 }; // 손님의 도착지 정보를 hash map으로 관리
}
cout << solve();
return 0;
}
✔️ 문제 회고
- BFS 두 개를 사용하면 되는 문제다. 문제 조건에 따라 구현하는 시뮬레이션 문제였다.
- 복잡할 수 있는 문제 조건을 하나하나 파악하고 깔끔하게 처리해야한다.
set
또는map
을 사용할때 key 값으로 구조체를 넣는 경우 구조체 우선순위 결정을 위한 비교함수를 인자로 넣어줘야한다. 우선순위 기준을 알려줘야 구조체를set
,map
내부에서 관리할 수 있다.