본문 바로가기

백준/삼성기출

스타트택시 - 1923번(Java)

728x90

문제

스타트링크가 "스타트 택시"라는 이름의 택시 사업을 시작했다. 스타트 택시는 특이하게도 손님을 도착지로 데려다줄 때마다 연료가 충전되고, 연료가 바닥나면 그 날의 업무가 끝난다.

택시 기사 최백준은 오늘 M명의 승객을 태우는 것이 목표이다. 백준이 활동할 영역은 N×N 크기의 격자로 나타낼 수 있고, 각 칸은 비어 있거나 벽이 놓여 있다. 택시가 빈칸에 있을 때, 상하좌우로 인접한 빈칸 중 하나로 이동할 수 있다. 알고리즘 경력이 많은 백준은 특정 위치로 이동할 때 항상 최단경로로만 이동한다.

M명의 승객은 빈칸 중 하나에 서 있으며, 다른 빈칸 중 하나로 이동하려고 한다. 여러 승객이 같이 탑승하는 경우는 없다. 따라서 백준은 한 승객을 태워 목적지로 이동시키는 일을 M번 반복해야 한다. 각 승객은 스스로 움직이지 않으며, 출발지에서만 택시에 탈 수 있고, 목적지에서만 택시에서 내릴 수 있다.

백준이 태울 승객을 고를 때는 현재 위치에서 최단거리가 가장 짧은 승객을 고른다. 그런 승객이 여러 명이면 그중 행 번호가 가장 작은 승객을, 그런 승객도 여러 명이면 그중 열 번호가 가장 작은 승객을 고른다. 택시와 승객이 같은 위치에 서 있으면 그 승객까지의 최단거리는 0이다. 연료는 한 칸 이동할 때마다 1만큼 소모된다. 한 승객을 목적지로 성공적으로 이동시키면, 그 승객을 태워 이동하면서 소모한 연료 양의 두 배가 충전된다. 이동하는 도중에 연료가 바닥나면 이동에 실패하고, 그 날의 업무가 끝난다. 승객을 목적지로 이동시킨 동시에 연료가 바닥나는 경우는 실패한 것으로 간주하지 않는다.

                                                                              <그림 1>

<그림 1>은 택시가 활동할 영역의 지도를 나타내며, 택시와 세 명의 승객의 출발지와 목적지가 표시되어 있다. 택시의 현재 연료 양은 15이다. 현재 택시에서 각 손님까지의 최단거리는 각각 9, 6, 7이므로, 택시는 2번 승객의 출발지로 이동한다.

 

<그림2,3>

 

<그림 2>는 택시가 2번 승객의 출발지로 가는 경로를, <그림 3>은 2번 승객의 출발지에서 목적지로 가는 경로를 나타낸다. 목적지로 이동할 때까지 소비한 연료는 6이고, 이동하고 나서 12가 충전되므로 남은 연료의 양은 15이다. 이제 택시에서 각 손님까지의 최단거리는 둘 다 7이므로, 택시는 둘 중 행 번호가 더 작은 1번 승객의 출발지로 이동한다.

<그림 4,5>

<그림 4>와 <그림 5>는 택시가 1번 승객을 태워 목적지로 이동시키는 경로를 나타낸다. 남은 연료의 양은 15 - 7 - 7 + 7×2 = 15이다.

 

<그림 6,7>

<그림 6>과 <그림 7>은 택시가 3번 승객을 태워 목적지로 이동시키는 경로를 나타낸다. 최종적으로 남은 연료의 양은 15 - 5 - 4 + 4×2 = 14이다.

모든 승객을 성공적으로 데려다줄 수 있는지 알아내고, 데려다줄 수 있을 경우 최종적으로 남는 연료의 양을 출력하는 프로그램을 작성하시오.

입력

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

다음 줄부터 N개의 줄에 걸쳐 백준이 활동할 영역의 지도가 주어진다. 0은 빈칸, 1은 벽을 나타낸다.

다음 줄에는 백준이 운전을 시작하는 칸의 행 번호와 열 번호가 주어진다. 행과 열 번호는 1 이상 N 이하의 자연수이고, 운전을 시작하는 칸은 빈칸이다.

그다음 줄부터 M개의 줄에 걸쳐 각 승객의 출발지의 행과 열 번호, 그리고 목적지의 행과 열 번호가 주어진다. 모든 출발지와 목적지는 빈칸이고, 모든 출발지는 서로 다르며, 각 손님의 출발지와 목적지는 다르다.

출력

모든 손님을 이동시키고 연료를 충전했을 때 남은 연료의 양을 출력한다. 단, 이동 도중에 연료가 바닥나서 다음 출발지나 목적지로 이동할 수 없으면 -1을 출력한다. 모든 손님을 이동시킬 수 없는 경우에도 -1을 출력한다.

예제 입력 1 복사

6 3 15

0 0 1 0 0 0

0 0 1 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 1 0

0 0 0 1 0 0

6 5

2 2 5 6

5 4 1 6

4 2 3 5

예제 출력 1 복사

14

 

package Samsung;

import java.util.*;
import java.io.*;

public class 스타트택시 {

	
	static int n,m,k;
	static int[][] map;
	static int cx,cy;
	static Ctmr[] carr;
	static int rk;
	
	static int[] dx = {-1,1,0,0}; 
	static int[] dy = {0,0,-1,1};
	
	static class Ctmr{
		
		int sx ,sy,ex,ey;
		Ctmr(int sx, int sy , int ex , int ey){
			
			this.sx = sx;
			this.sy = sy;
			this.ex = ex;
			this.ey = ey;
			
		}
	}
	
	static class Pair {
		
		int x, y;
		Pair(int x , int y){
			this.x =x;
			this.y =y;
		}
	}
	
	static class Info implements Comparable<Info>{
		
		int x;
		int y;
		int num;
		int dist; 

		
		Info(int num, int dist, int x,int y){
			
			this.num = num;
			this.dist =dist;
			this.x =x;
			this.y =y;
		}

		@Override
		public int compareTo(Info o) {
		
			if(this.dist==o.dist) {
				
				if(this.x==o.x) {
					
					return this.y-o.y;
					
				}
				return this.x-o.x;
				
			}
			
			return this.dist-o.dist;
		}
		
		
		
	}
	
	public static void print() {
		
		System.out.println("car:"+cx+","+cy);
		for(int i=0; i<carr.length; i++) {
			if(carr[i]!=null) {
			System.out.println(i+":"+carr[i].sx+","+carr[i].sy);
		}
		}
	}
	
	public static boolean cango(int x, int y) {
		
		return x>=0 && x<n && y>=0 && y<n;
		
	}
	
	static boolean[][] check;
	
	/*어려웠던점1: 일단 모든 승객을 한번의 bfs로 탐색 후 priority queue 에 넣어서 가장 가까운 승객을 찾아준다.=> 시간초과 해결 방법*/
	/*현재 지점에서 가장 가까운 사람을 찾기 위한 bfs*/
	
	public static void bfs(Pair s, PriorityQueue<Info> pq) {

		check = new boolean[n][n];
		Queue<Pair> q = new LinkedList<>();
		q.add(new Pair(s.x,s.y));
		
		check[s.x][s.y]=true;
		int dist=0;
		if(map[s.x][s.y]!=-1 && map[s.x][s.y]!=0) { //출발지점과 목적지점이 같을 경우 pq 에 넣어줘야함 /*어려웠던점2*/
			pq.add(new Info(map[s.x][s.y],dist,s.x,s.y));
		}
		
		while(!q.isEmpty()) {
			
			int sz = q.size();
			
			if(dist>=rk) {break;} //rk 를 넘어가면 더이상 사람을 태울 수 없게 된다. 
			
			for(int i=0; i<sz; i++) {
				
				Pair p = q.poll();
				
				for(int t=0; t<4; t++) {
				
				int nx = p.x + dx[t];
				int ny = p.y +dy[t];
				
				if(cango(nx,ny) && map[nx][ny]!=-1 && check[nx][ny]==false) {
					
					check[nx][ny]=true;
					q.add(new Pair(nx,ny));
					
					if(map[nx][ny]!=0) {  //현재 해당 부분이 사람이 있는 곳이라면
						pq.add(new Info(map[nx][ny],dist+1,nx,ny)); // 그사람의대한 정보를 pq에 넣어준다.
					}
					
				}
				}
			
			}
			
			dist++;

			
		}
	}
	
	 /*현재 태운 사람을 목적지로 향하는데 가는 거리를 구해준다.*/
	public static int bfs2(Pair s, Pair e) { 

		check = new boolean[n][n];
		if(s.x==e.x && s.y==e.y) {return 0;} //출발지점과 목적지점이 같을 경우 바로 0을 반환 /*어려웠던점2*/
		
		Queue<Pair> q = new LinkedList<>();
		q.add(new Pair(s.x,s.y));
		
		check[s.x][s.y]=true; //방문 여부를 알려주는 check 배열
		boolean tg = false; //목적지에 도달했는지 알려주는 토글 버튼
		int dist=0; 
		
		while(!q.isEmpty()) {
			
			int sz = q.size(); //같은 depth 끼리 탐색하기 위한 sz설정
			if(dist>=rk) {break;}  // 가는 거리가 연료를 넘어서게 되면 탐색을 중단한다
			for(int i=0; i<sz; i++) {
				
				Pair p = q.poll();
				
				for(int t=0; t<4; t++) { //4방향 탐색 
				
				int nx = p.x + dx[t];
				int ny = p.y +dy[t];
				
				if(cango(nx,ny) && map[nx][ny]!=-1 && check[nx][ny]==false) { //벽이아니고 지난 적 이 없다면
					
					if(nx == e.x && ny== e.y) {//목적지에 도달을 했다.
				
					 tg=true; break; //목적지 도달 토글 true로 만듬
					}
					check[nx][ny]=true;  //방문 여부를 알려주는 check 배열
					q.add(new Pair(nx,ny)); 
					
				}
				
				}
				if(tg)break; //목적지 도달 했으니까 break
			}
			
			dist++; 
			if(tg)break; //목적지 도달 했으니까 break
			
		}
		
		if(!tg)dist=-1; //목적지에 도달하지 않고 탐색이 끝나거나, 가는 도중에 연료가 다닳은 경우 -1을 리턴한다.
		
		return dist;
		
	}
	
	
	public static boolean done() { //모든 승객을 다 데려다줬는지 확인		
		for(int i=0; i<m; i++) {
			
			if(carr[i]!=null) {return false;} 
		}
		
		return true;
		
	}
	

	public static int simulate() {
		

		boolean doneright = true;  // 탐색 결과 승객을 태울 수 있거나 바래다 줄수 있다면 true 아니면 false
		
		while(!done()) { // 모든승객을 바래다 줄때 까지 while문 실행
		
		int min = Integer.MAX_VALUE; // 가장 가까운 승객의 거리
		Ctmr custo =null; //가장 가까운 승객의 정보 저장
		
		
		PriorityQueue<Info> pq = new PriorityQueue<>(); //가장 짧고 행좌표가 가장작고 열좌표가 가장 작은 고객을 찾기 위한 heap
		
		bfs(new Pair(cx,cy), pq); //고객을 찾기 위해 bfs 를 돌림
		if(pq.isEmpty()) { //주어진 연료로 갈수 태울수 있는 고객이 없을때 pq는 안에 아무것도 없다.
			doneright = false;
			break;
		}
		
		Info dest = pq.poll(); //탐색결과 태울 수 있는 승객들을 담고 그중 가장 가깝고 조건에 잘맞는 승객을 pq 의 가장 앞에서 찾는다.
		
		min = dest.dist; 
		custo = carr[dest.num-1];
		cx = dest.x; //택시의 위치를 고객의 취치로 갱신
		cy  = dest.y;//택시의 위치를 고객의 취치로 갱신
		
		rk-=min; //고객에게 가는 거리만큼 빼준다.
		
	
		int doneres = bfs2(new Pair(cx,cy),new Pair(custo.ex,custo.ey)); //고객의 현재위치에서 목적지까지 가는데 필요한 거리 
		
		if(doneres==-1) { //가는 길에 연료가 다 닳을 경우 -1 return 됨 이때 while문 빠져나와야한다.
			doneright = false; break;
		}
		
		rk-=doneres; //쓴 연료만 큼 빼주고
		rk += (doneres*2); //쓴연료의 두배만큼 더해준다.

		cx=custo.ex;
		cy= custo.ey;
		carr[dest.num-1]=null; //데려다준 고객은 null로한다.
		map[custo.sx][custo.sy]=0; //데려다준 고객은 map 에서 지워버린다.
		
		}
		if(doneright==false) {return -1;} //탐색 결과 태울 승객이 없거나 태워도 바래다 줄수 없다면 -1리턴
		return rk;
	}
	
	
	
	public static void main(String[] args) throws IOException {
		
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		n = Integer.parseInt(st.nextToken());
		m = Integer.parseInt(st.nextToken());
		k = Integer.parseInt(st.nextToken());
		
		rk=k;
		
		map= new int[n][n];
		carr = new Ctmr[m];
		
		for(int i=0; i<n; i++) {
			st = new StringTokenizer(br.readLine());
			for(int j=0; j<n; j++) {
				
				map[i][j] = Integer.parseInt(st.nextToken());
				if(map[i][j]==1) {
					map[i][j]=-1;
				}
			
			}
			
		}
		st = new StringTokenizer(br.readLine());
		cx =  Integer.parseInt(st.nextToken())-1;
		cy =  Integer.parseInt(st.nextToken())-1;
		
		for(int i=0; i<m;i++) {
			
			st = new StringTokenizer(br.readLine());
			carr[i]= new Ctmr(Integer.parseInt(st.nextToken())-1,Integer.parseInt(st.nextToken())-1,Integer.parseInt(st.nextToken())-1,Integer.parseInt(st.nextToken())-1);
			
			int x = carr[i].sx;
			int y = carr[i].sy;
			
			map[x][y]=i+1;
			
		}
		
		System.out.println(simulate());
	
		
	}
	
	
}

 

- 어려웠던점1: 일단 모든 승객을 한번의 bfs로 탐색 후 priority queue 에 넣어서 가장 가까운 승객을 찾아준다.=> 시간초과 해결 방법

- 어려웠던점2: bfs 에선 출발지점과 목적지점이 같을 경우 pq 에 넣어줘야함, bfs2 에선 출발지점과 목적지점이 같을 경우 바로 0을 반환