Problem Solving/백준

[BOJ, 삼성 SW 역량 테스트 기출 문제] 19238번 : 스타트 택시

돌돌김 2020. 10. 9. 18:53

www.acmicpc.net/problem/19238

 

19238번: 스타트 택시

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

www.acmicpc.net

 

구현사항만 잘 지키면 되는 전형적인 시뮬레이션 문제였다.

 

손님을 태우러 택시가 가는 것과, 손님을 태우고 목적지로 가는 것은 같은 방식으로 구현을 할 수 있으므로 flag 변수를 통해 1개의 함수로 처리했다.

 

 

문제해결
  • 현재 손님의 위치, 목적지를 입력받고 각각 다른 배열에 저장한다.
  • 비교함수를 만들어 문제의 조건을 처리한다.
    • 가장 가까운 승객 / 행 번호 작은 승객 / 열 번호 작은 승객 순으로 정렬
  • 가장 가까운 손님에게 택시 이동
    • 연료 1씩 감소
  • 손님을 목적지로 이동
    • 연료 1씩 감소
    • 목적지 도착 시 연료 사용량*2 추가

 

 

소스코드
#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
using namespace std;

struct TAXI { // 택시의 상태
	int y;
	int x;
	int gas; // 남은 가스
};

struct PEOPLE { // 손님의 상태
	int y;
	int x;
	int dist; // 현재 택시로부터 거리
	int idx;
};

struct TARGET { // 각 손님의 목적지	
	int y;
	int x;
};
bool cmp(PEOPLE a, PEOPLE b) { // 다음 손님 정렬 : 내림차순으로 정렬해서 pop_back()로 가장 가까운 승객을 없애주자
	if (a.dist == b.dist) { 
		if (a.y == b.y) { 	// 그런 승객이 여러 명이면 그중 행 번호가 가장 작은 승객을, 그런 승객도 여러 명이면 그중 열 번호가 가장 작은 승객을 고른다.
			if (a.x == b.x) {
				return a.idx > b.idx;
			}
			return a.x > b.x;
		}
		return a.y > b.y;
	}
	return a.dist > b.dist; 
}



int n, m;
TAXI taxi;
TARGET target[401]; // i번째 손님의 목적지
vector<PEOPLE> peo;
int map[21][21]; // 인덱스 1부터 시작
int dy[4] = { 0,0,-1,1 };
int dx[4] = { -1,1,0,0 };
bool visited[21][21];

int check_dist(int ry, int rx) {	
	queue<pair<int, pair<int, int>>>q;
	memset(visited, false, sizeof(visited));
	q.push({ 0,{ taxi.y, taxi.x } });
	while (!q.empty()){
		int y = q.front().second.first;
		int x = q.front().second.second;
		int d = q.front().first;
		if (y == ry && x == rx) return d; // 목적지까지 거리 구함
		visited[y][x] = true;
		q.pop();
		for (int dir = 0; dir < 4; dir++) {
			int ny = y + dy[dir];
			int nx = x + dx[dir];
			if (1 > ny || ny > n || 1 > nx || nx > n || visited[ny][nx])continue;
			if (map[ny][nx] == 1) continue;
			int nd = d + 1;
			q.push({ nd, { ny,nx } });
			visited[ny][nx] = true;
		}
	}	
	return -1; // 해당 위치까지 택시 못감
}

bool go_taxi(int ry, int rx, bool flag) { // 택시가 움직인다. 도중에 연료 떨어지면 false 리턴
	// flag : true => 택시가 손님을 태우고 움직임. 최종목적지 도달시 연료 2배 충전
	int use_gas = 0; // 이동하는 데 든 연료
	queue<pair<pair<int,int>, pair<int, int>>>q;
	memset(visited, false, sizeof(visited));
	q.push({ {taxi.gas, use_gas}, { taxi.y, taxi.x } });
	while (!q.empty()) {
		int g = q.front().first.first;
		int u = q.front().first.second;
		int y = q.front().second.first;
		int x = q.front().second.second;
		//cout << "taxi : " << taxi.y << ' ' << taxi.x << ' ' << taxi.gas << endl;

		if (g < 0) { // 연료 다 떨어짐
			return false;
		}
		if (y == ry && x == rx) { // 손님한테 도착 or 목적지에 도착
			if (flag) { // 목적지 도착 : 연료 2배충전
				taxi.gas += u * 2;
			}
			taxi.y = y; 
			taxi.x = x;
			taxi.gas -= u;
			return true;
		}
		visited[y][x] = true;
		q.pop();
		for (int dir = 0; dir < 4; dir++) {
			int ny = y + dy[dir];
			int nx = x + dx[dir];
			int ng = g - 1;
			int nu = u + 1;
			if (1 > ny || ny > n || 1 > nx || nx > n || visited[ny][nx])continue;
			if (map[ny][nx] == 1) continue;
			q.push({ {ng, nu}, {ny,nx} });
			visited[ny][nx] = true;
			
		}
	}
	return false; // 해당 위치까지 택시 못감	
}
int main() {

	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie();
	//freopen("input.txt", "r", stdin);
	cin >> n >> m >> taxi.gas;
	
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> map[i][j];
		}
	}	
	cin >> taxi.y >> taxi.x; 
	for (int i = 1; i <= m; i++) {
		int y, x, d_y, d_x;
		cin >> y >> x >> d_y >> d_x;
		peo.push_back({ y,x,0,i }); // 현재 손님의 위치
		target[i]={ d_y, d_x }; // 현재 손님의 목적지
	}	

	// 태울 승객을 고를 때는 현재 위치에서 최단거리가 가장 짧은 승객을 고른다. 	
	while (!peo.empty())
	{		
		for (int i = 0; i < peo.size(); i++) {
			peo[i].dist = check_dist(peo[i].y, peo[i].x);
			if (peo[i].dist == -1) { // 해당 손님한테 못감
				cout << -1;
				return 0;
			}
		}
		sort(peo.begin(), peo.end(), cmp);
    
		// 가장 가까운 손님에게 택시 이동		
		if (!go_taxi(peo.back().y, peo.back().x, false)) {
			cout << -1;
			return 0;
		}
		// 손님을 목적지로 이동 : 
		if (!go_taxi(target[peo.back().idx].y, target[peo.back().idx].x, true)) {
			cout << -1;
			return 0;
		}
		// 이동 완료 시 손님 삭제
		peo.pop_back();
	}
	cout << taxi.gas;
}