코딩테스트/구현(Implementation)

[Java] 백준 20056번 : 마법사 상어와 파이어볼

sujin7837 2022. 4. 27. 00:25
반응형

문제

어른 상어가 마법사가 되었고, 파이어볼을 배웠다.

마법사 상어가 크기가 N×N인 격자에 파이어볼 M개를 발사했다. 가장 처음에 파이어볼은 각자 위치에서 이동을 대기하고 있다. i번 파이어볼의 위치는 (ri, ci), 질량은 mi이고, 방향은 di, 속력은 si이다. 위치 (r, c)는 r행 c열을 의미한다.

격자의 행과 열은 1번부터 N번까지 번호가 매겨져 있고, 1번 행은 N번과 연결되어 있고, 1번 열은 N번 열과 연결되어 있다.

파이어볼의 방향은 어떤 칸과 인접한 8개의 칸의 방향을 의미하며, 정수로는 다음과 같다.

7 0 1
6   2
5 4 3

마법사 상어가 모든 파이어볼에게 이동을 명령하면 다음이 일들이 일어난다.

  1. 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
    • 이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.
  2. 이동이 모두 끝난 뒤, 2개 이상의 파이어볼이 있는 칸에서는 다음과 같은 일이 일어난다.
    1. 같은 칸에 있는 파이어볼은 모두 하나로 합쳐진다.
    2. 파이어볼은 4개의 파이어볼로 나누어진다.
    3. 나누어진 파이어볼의 질량, 속력, 방향은 다음과 같다.
      1. 질량은 ⌊(합쳐진 파이어볼 질량의 합)/5⌋이다.
      2. 속력은 ⌊(합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)⌋이다.
      3. 합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고, 그렇지 않으면 1, 3, 5, 7이 된다.
    4. 질량이 0인 파이어볼은 소멸되어 없어진다.

마법사 상어가 이동을 K번 명령한 후, 남아있는 파이어볼 질량의 합을 구해보자.

입력

첫째 줄에 N, M, K가 주어진다.

둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다.

서로 다른 두 파이어볼의 위치가 같은 경우는 입력으로 주어지지 않는다.

출력

마법사 상어가 이동을 K번 명령한 후, 남아있는 파이어볼 질량의 합을 출력한다.

제한

  • 4 ≤ N ≤ 50
  • 0 ≤ M ≤ N2
  • 1 ≤ K ≤ 1,000
  • 1 ≤ ri, ci ≤ N
  • 1 ≤ mi ≤ 1,000
  • 1 ≤ si ≤ 1,000
  • 0 ≤ di ≤ 7

예제 입력 1

4 2 1
1 1 5 2 2
1 4 7 1 6

예제 출력 1

8

예제 입력 2

4 2 2
1 1 5 2 2
1 4 7 1 6

예제 출력 2

8

예제 입력 3

4 2 3
1 1 5 2 2
1 4 7 1 6

예제 출력 3

0

예제 입력 4

7 5 3
1 3 5 2 4
2 3 5 2 6
5 2 9 1 7
6 2 1 3 5
4 4 2 4 2

예제 출력 4

9

 

소스코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {

	private static BufferedReader br;
	private static StringTokenizer st;
	
	private static int N, M, K;
	private static int []dx= {-1,-1,0,1,1,1,0,-1};
	private static int []dy= {0,1,1,1,0,-1,-1,-1};
	private static PriorityQueue<FireBall> fireBall;
	
	static class FireBall implements Comparable<FireBall> {
		int r, c, m, s, d;

		public FireBall(int r, int c, int m, int s, int d) {
			super();
			this.r = r;
			this.c = c;
			this.m = m;
			this.s = s;
			this.d = d;
		}

		@Override
		public int compareTo(FireBall o) {
			if(this.r==o.r) return Integer.compare(this.c, o.c);
			return Integer.compare(this.r, o.r);
		}

		@Override
		public String toString() {
			return "FireBall [r=" + r + ", c=" + c + ", m=" + m + ", s=" + s + ", d=" + d + "]";
		}
		
	}
	
	public static void main(String[] args) throws IOException {
		br=new BufferedReader(new InputStreamReader(System.in));
		st=new StringTokenizer(br.readLine());
		N=Integer.parseInt(st.nextToken());
		M=Integer.parseInt(st.nextToken());
		K=Integer.parseInt(st.nextToken());
		fireBall=new PriorityQueue<>();
		
		for(int i=0;i<M;i++) {
			st=new StringTokenizer(br.readLine());
			int r=Integer.parseInt(st.nextToken());
			int c=Integer.parseInt(st.nextToken());
			int m=Integer.parseInt(st.nextToken());
			int s=Integer.parseInt(st.nextToken());
			int d=Integer.parseInt(st.nextToken());
			fireBall.add(new FireBall(r, c, m, s, d));
		}
		
		for(int i=0;i<K;i++) {	// K번의 명령 수행
			start();
		}
		int result=0;
		while(!fireBall.isEmpty()) {	// 최종적으로 남아있는 파이어볼의 질량의 합
			FireBall fb=fireBall.poll();
			result+=fb.m;
		}
		System.out.println(result);
	}

	private static void start() {
		move();	// 1. 파이어볼 이동
		sameCheck();	// 2. 2개 이상의 파이어볼이 있는 칸이 있는지 확인 및 조정
	}
	
	private static List<FireBall> shareBall(List<FireBall> same) {	// 같은 좌표에 있는 파이어볼들을 조정함
		List<FireBall> tmp=new ArrayList<>();
		int r=same.get(0).r;
		int c=same.get(0).c;
		int m=0;
		int s=0;
		boolean allOdd=true;
		boolean allEven=true;
		for(int i=0;i<same.size();i++) {
			m+=same.get(i).m;	// 파이어볼들의 질량의 합
			s+=same.get(i).s;	// 파이어볼들의 속력의 합
			if((same.get(i).d)%2==0) allOdd=false;	// 모두 홀수가 될 수 없는 경우
			if((same.get(i).d)%2!=0) allEven=false;	// 모두 짝수가 될 수 없는 경우
		}
		m/=5;
		s/=same.size();
		if(m==0) return null;	// 질량이 0이 되면 파이어볼 리스트에 추가하지 않음
		if(allOdd || allEven) {	// 방향 설정 (0, 2, 4, 6)
			for(int i=0;i<8;i+=2) {
				tmp.add(new FireBall(r, c, m, s, i));
			}
		} else {	// 방향 설정 (1, 3, 5, 7)
			for(int i=1;i<8;i+=2) {
				tmp.add(new FireBall(r, c, m, s, i));
			}
		}
		return tmp;
	}
	
	private static void sameCheck() {	// 같은 좌표에 볼이 여러 개인 경우 탐색
		List<FireBall> tmp=new ArrayList<>();	// 같은 좌표의 파이어볼을 조정한 최종 파이어볼을 저장할 리스트
		List<FireBall> same=new ArrayList<>();	// 같은 좌표의 파이어볼들을 저장할 리스트
		FireBall fb=fireBall.poll();
		while(!fireBall.isEmpty()) {
			FireBall fb2=fireBall.poll();
			if(fb.r==fb2.r && fb.c==fb2.c) {	// 같은 좌표에 있을 때
				same.add(fb);
				if(fireBall.isEmpty()) {	// 다음 파이어볼이 존재하지 않으면 같은 좌표의 파이어볼들을 조정해줌
					same.add(fb2);
					List<FireBall> get=shareBall(same);
					if(get!=null) tmp.addAll(get);	// 조정한 파이어볼의 질량이 0이 아니면 최종 파이어볼 리스트에 추가
					same.clear();
				}
			}
			else {	// 같은 좌표가 아닐 때
				if(same.size()>0) {	// 이전 좌표까지 같은 좌표였을 경우 같은 좌표의 파이어볼들을 조정함
					same.add(fb);
					List<FireBall> get=shareBall(same);
					if(get!=null) tmp.addAll(get);	// 조정한 파이어볼의 질량이 0이 아니면 최종 파이어볼 리스트에 추가
					same.clear();
				}
				else tmp.add(fb);
				if(fireBall.isEmpty()) tmp.add(fb2);
			}
			fb=fb2;
		}
		fireBall.addAll(tmp);
	}
	
	private static void move() {	// 파이어볼 이동
		List<FireBall> tmp=new ArrayList<>();
		while(!fireBall.isEmpty()) {
			FireBall fb=fireBall.poll();
			int s=fb.s%N;	// 이동 속도가 격자 크기보다 크지 않도록 조정
			int nx=fb.r+dx[fb.d]*s;
			int ny=fb.c+dy[fb.d]*s;
			if(nx<=0) nx+=N;	// 이동한 좌표가 격자 범위를 벗어나면 좌표 조정
			else if(nx>N) nx-=N;
			if(ny<=0) ny+=N;
			else if(ny>N) ny-=N;
			tmp.add(new FireBall(nx, ny, fb.m, fb.s, fb.d));
		}
		fireBall.addAll(tmp);
	}
}

1. 파이어볼 정보를 저장할 클래스를 만들고, 전체 파이어볼들을 관리하기 위해 우선순위 큐에 저장해주었습니다.

우선순위 큐에 저장하는 기준을 행과 열로 정하면, 같은 좌표에 있는 파이어볼들끼리 큐 안에서 인접하게 되므로 처리가 용이해집니다.

2. 파이어볼의 이동, 2개 이상의 파이어볼이 있는 칸 확인, 그리고 같은 칸의 파이어볼 관리 각각에 대한 메서드를 만들어줍니다.

 

위 과정을 통해 구현해주어 해결이 가능했던 문제였습니다. 이동 시 좌표 관리나 같은 칸에 파이어볼이 2개 이상인 경우를 확인할 때 조건을 모두 고려해주는 것이 조금 시간이 걸렸던 것 같습니다.

반응형