알고리즘 문제풀이/백준

[백준 19238번] 스타트 택시

슥지니 2022. 9. 4. 16:22
반응형

문제 링크: https://www.acmicpc.net/problem/19238

 

19238번: 스타트 택시

첫 줄에 N, M, 그리고 초기 연료의 양이 주어진다. (2 ≤ N ≤ 20, 1 ≤ M ≤ N2, 1 ≤ 초기 연료 ≤ 500,000) 연료는 무한히 많이 담을 수 있기 때문에, 초기 연료의 양을 넘어서 충전될 수도 있다. 다

www.acmicpc.net

<문제 풀이> 시뮬레이션, 우선순위 큐, BFS, 최단거리

먼저 택시를 기준으로 BFS를 사용해서 최단거리를 갱신합니다. 이때 다음 좌표로 이동할 때 택시의 연료가 부족하면 이동하면 안 됩니다. 현재 연료로 다음 좌표로 이동할 수 있으면 해당 손님의 좌표를 다음과 같은 기준으로 우선순위 큐에 기록합니다.

1. 현재 위치에서 최단거리가 가장 짧은 승객을 고른다.

2. 그런 승객이 여러 명이면 그중 행 번호가 가장 작은 승객을 고른다.

3. 그런 승객도 여러  명이면 그중 열 번호가 가장 작은 승객을 고른다.

아래 코드와 같이 우선순위 큐를 선언하면 우선순위 큐의 top에는 위 조건을 만족하는 손님이 존재하게 됩니다.

 

priority_queue<tuple<intintint>vector<tuple<intintint> >, greater<tuple<intintint> > > pq;

 

BFS가 끝나면 우선순위 큐의 top에 존재하는 손님까지의 거리를 리턴하면 되고 만약 우선순위 큐가 비어있다면 연료가 부족해서 손님을 태우지 못한 경우이니 -1을 리턴하면 됩니다.

 

택시 BFS가 끝나면 이제 손님 BFS를 사용하면 됩니다.

 

손님 BFS에서 출발점과 도착점을 알고 있어야 하는데 단순히 2차원 배열에 손님의 도착 정보를 특정 값 형태로 저장하게 되면 안 됩니다.

 

왜냐하면 문제 조건에서 "모든 출발지는 서로 다르며, 각 손님의 출발지와 목적지는 다르다."라는 부분을 잘 이해하면 어떤 손님의 목적지가 어떤 손님의 출발지일 수도 있다는 걸 알 수 있습니다. 그래서 겹치는 부분을 처리하기 위해 해시맵을 이용해서 각 손님의 id와 목적지 좌표를 <key, value> 형태로 저장해야 합니다.

 

그리고 위 조건 때문에 택시가 목적지에 도착하자마자 바로 승객을 태울 수도 있기 때문에 이 부분도 예외처리를 해야 합니다.

 

<C++ 소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include<iostream>
#include<queue>
#include<tuple>
#include<functional>
#include<utility>
#include<algorithm>
#include<unordered_map>
using namespace std;
 
#define X first
#define Y second
 
int n, m, fuel;
int board[21][21];
int visited[21][21];
int tx, ty; //택시 위치
int dx[4= { 001-1 };
int dy[4= { 1-100 };
unordered_map<intpair<intint> > um; //각 손님의 도착 위치
priority_queue<tuple<intintint>vector<tuple<intintint> >, greater<tuple<intintint> > > pq;
 
int taxiBFS() {
    fill(&visited[0][0], &visited[20][21], -1);
    queue<pair<intint> > Q;
    Q.push({ tx, ty });
    visited[tx][ty] = 0;
    if (board[tx][ty] > 0)pq.push({ visited[tx][ty], tx, ty }); //손님을 바로 태우는 경우
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        for (int dir = 0; dir < 4; dir++) {
            int nx = cur.X + dx[dir];
            int ny = cur.Y + dy[dir];
            if (nx < 1 || ny < 1 || nx > n || ny > n)continue;
            if (visited[nx][ny] != -1 || board[nx][ny] == 1)continue//이미 방문했거나 벽이면 무시
            if (visited[nx][ny] > fuel) continue//다음 위치로 이동하는데 연료가 부족하면 무시
            visited[nx][ny] = visited[cur.X][cur.Y] + 1;
            if (board[nx][ny] > 0)pq.push({ visited[nx][ny], nx, ny }); // 다음 위치에 손님이 있는 경우
            Q.push({ nx, ny });
        }
    }
    if (pq.empty()) return -1// 연료가 부족해서 손님을 못 태우는 경우
    else return get<0>(pq.top()); // 손님까지 거리를 리턴
}
 
int personBFS() {
    auto cur = pq.top(); pq.pop();
    pq = {};
    int d, x, y;
    tie(d, x, y) = cur;
    fill(&visited[0][0], &visited[20][21], -1);
    queue<pair<intint> > Q;
    Q.push({ x, y });
    visited[x][y] = 0;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        for (int dir = 0; dir < 4; dir++) {
            int nx = cur.X + dx[dir];
            int ny = cur.Y + dy[dir];
            if (nx < 1 || ny <1 || nx > n || ny > n)continue;
            if (visited[nx][ny] != -1 || board[nx][ny] == 1)continue;
            visited[nx][ny] = visited[cur.X][cur.Y] + 1;
            Q.push({ nx, ny });
            if (nx == um[board[x][y]].X && ny == um[board[x][y]].Y) {//목적지에 도착
                tx = nx;
                ty = ny;//택시 위치 목적지로 갱신
                board[x][y] = 0//해당 승객 처리
                if (visited[nx][ny] > fuel) return -1//만약 연료가 부족하면 실패
                return visited[nx][ny]; //return 소비한 연료
            }
        }
    }
    if (board[x][y]) return -1// 목적지에 도착하지 못하면 승객을 처리하지 못함 -> return -1
}
 
void solution() {
    int cnt = 0;
    while (cnt < m) { //승객을 모두 처리할 때까지
        int consumeTaxi = taxiBFS();
        if (consumeTaxi == -1) {
            fuel = -1;
            return;
        }
        fuel -= consumeTaxi; //승객이 있는 곳까지 연료 감소
        int consumePerson = personBFS();
        if (consumePerson == -1) {
            fuel = -1;
            return;
        }
        fuel += consumePerson; // 목적지까지 연료 감소 후 소모한 연료 양 두 배 충전
        cnt++;
    }
    
}
 
int main(void) {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
     
    cin >> n >> m >> fuel;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> board[i][j];
        }
    }
    cin >> tx >> ty;
    for (int i = 0; i < m; i++) {
        int sx, sy, ex, ey;
        cin >> sx >> sy >> ex >> ey;
        board[sx][sy] = i + 2// 각 손님의 시작 위치 => 손님은 2이상의 key값으로 구분
        um[i + 2= { ex, ey }; // 각 손님 식별 key는 2이상의 수
    }
    solution();
    cout << fuel;
    return 0;
}
cs

 

반응형