본문 바로가기

숨막히는 알고말고/문제 풀이

[Baekjoon] 마법사 상어와 파이어볼

👀 문제 설명

문제

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

마법사 상어가 크기가 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

 

✍🏻풀이

 

구현 문제이다.

 

먼저 파이어볼의 정보를 담고 있는 클래스 Fireball을 만들었다.

N * N 배열의 크기를 하는 ArrayList의 이차원 배열을 만들고, 각 자리에 ArrayList를 사용해 그 자리에 파이어볼들을 저장한다.

-> ArrayList<Fireball>[N][N] fireballs

 

move() 메소드는 파이어볼을 이동시켜주는 메소드이다. (r, c)자리에 하나씩 접근해 해당 자리에 있는 파이어볼을 이동시켜준다.

이 때, 이동시킨 후의 파이어볼들을 따로 저장하는 newFireballs를 사용해야 한다. (그대로 fireballs에 저장하면, 이동시킨 파이어볼을 한 번 더 이동시키게 될 수 있다.)

다 이동시켰다면, newFireballs의 파이어볼들을 fireballs로 옮긴다.

 

combineFireball은 (r, c)에 2개 이상의 파이어볼이 있을 경우, 파이어볼들을 합치고 4개의 파이어볼을 새로 생성해주는 메소드이다.

나는 이 부분에서 파이어볼들의 방향이 모두 짝수이거나, 모두 홀수인지 확인하기 위해 파이어볼들의 방향을 다 더하고, % 2를 사용해 0인 경우, 모두 홀수 or 짝수인 것이고, 1일 경우 다르다고 구해줬는데, 이렇게 하면 제대로 구해지지 않는다.

-> 방향이 0, 1, 3, 2인 경우

그러므로 매번 방향을 체크해서 이전에는 짝수였는데, 현재 홀수인지 확인해줘야 한다.

 

move()와 combineFireball()을 K번 반복한 후, 남은 파이어볼들의 질량의 합을 구하면 된다.

 

코드

package boj;

// 마법사 상어와 파이어볼 (https://www.acmicpc.net/problem/20056 )

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

public class BOJ_20056 {
	
	private static class Fireball {
		int m, s, d;
		
		Fireball(int m, int s, int d) {
			this.m = m;
			this.s = s;
			this.d = d;
		}

		@Override
		public String toString() {
			return "Fireball [m=" + m + ", s=" + s + ", d=" + d + "]";
		}
	}
	
	private static int N, M, K;
	private static int[][] map;
	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 ArrayList<Fireball>[][] fireballs;
	private static ArrayList<Fireball>[][] newFireballs;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		K = Integer.parseInt(st.nextToken());
		map = new int[N][N];
		fireballs = new ArrayList[N][N];
		for (int i = 0; i < M; i++) {
			st = new StringTokenizer(br.readLine());
			
			int r = Integer.parseInt(st.nextToken()) - 1;
			int c = Integer.parseInt(st.nextToken()) - 1;
			int m = Integer.parseInt(st.nextToken());
			int s = Integer.parseInt(st.nextToken());
			int d = Integer.parseInt(st.nextToken());
			
			if (fireballs[r][c] == null) {
				fireballs[r][c] = new ArrayList<Fireball>();
			}
			
			fireballs[r][c].add(new Fireball(m, s, d));
		}
		
		for (int i = 0; i < K; i++) {
			move(); // 1. 이동
			combineFireball(); // 2. 2개 이상의 파이어볼이 있을 경우
		}
		
		bw.write(getAns() + "");

		bw.flush();
		bw.close();
	}
	
	// 1. 파이어볼 이동
	private static void move() {
		newFireballs = new ArrayList[N][N];
		
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (fireballs[r][c] == null) continue;
				
				for (int i = fireballs[r][c].size() - 1; i >= 0; i--) {
					Fireball cur = fireballs[r][c].get(i);
					
					int nextR = Math.abs(r + dx[cur.d] * cur.s + 1000 * N) % N;
					int nextC = Math.abs(c + dy[cur.d] * cur.s + 1000 * N) % N;
					
					if (newFireballs[nextR][nextC] == null) 
						newFireballs[nextR][nextC] = new ArrayList<Fireball>();
					
					newFireballs[nextR][nextC].add(cur);
					fireballs[r][c].remove(i);
				}
			}
		}
		
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (newFireballs[r][c] != null) {
					if (fireballs[r][c] == null) 
						fireballs[r][c] = new ArrayList<Fireball>();
					
					for (int i = 0; i < newFireballs[r][c].size(); i++) {
						fireballs[r][c].add(newFireballs[r][c].get(i));
					}
				}
			}
		}
	}
	
	// 2. 2개 이상의 파이어볼이 있는 칸
	private static void combineFireball() {
		newFireballs = new ArrayList[N][N];
		
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (fireballs[r][c] != null && fireballs[r][c].size() >= 2) { // 파이어볼이 2개 이상 있을 경우
					int newM = 0; // 나누어진 파이어볼의 질량 구하기
					int newS = 0; // 나누어진 파이어볼의 속력 구하기
					int isOdd = fireballs[r][c].get(0).d % 2;
					boolean newDir = true; // newDir이 true이면, 방향은 0, 2, 4, 6 그렇지 않으면 1, 3, 5, 7
					
					for (int i = 0; i < fireballs[r][c].size(); i++) {
						Fireball cur = fireballs[r][c].get(i);
						newM += cur.m;
						newS += cur.s;

						if (cur.d % 2 != isOdd) {
							newDir = false;
						}
					}
					
					newM /= 5;
					if (newM == 0) { // 새로운 질량이 0이면 4개로 나눠진 파이어볼 소멸
						fireballs[r][c].clear();
						continue;
					}
					
					newS /= fireballs[r][c].size();
					fireballs[r][c].clear();
					
					// 4개로 나눠진 파이어볼 추가
					if (newDir) {
						for (int dir = 0; dir < 8; dir += 2) {
							fireballs[r][c].add(new Fireball(newM, newS, dir));
						}
					}
					else {
						for (int dir = 1; dir < 8; dir += 2) {
							fireballs[r][c].add(new Fireball(newM, newS, dir));
						}
					}
				}
			}
		}
		
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (newFireballs[r][c] != null) {
					if (fireballs[r][c] == null) 
						fireballs[r][c] = new ArrayList<Fireball>();
					
					for (int i = 0; i < newFireballs[r][c].size(); i++) {
						fireballs[r][c].add(newFireballs[r][c].get(i));
					}
				}
			}
		}
	}
	
	private static int getAns() {
		int ans = 0;
		
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (fireballs[r][c] == null) continue;
				
				for (int i = 0; i < fireballs[r][c].size(); i++) {
					ans += fireballs[r][c].get(i).m;
				}
			}
		}
		
		return ans;
	}

}