developerU

[백준] 20056 - 마법사 상어와 파이어볼 자바 본문

Algorithm/BaekJoon

[백준] 20056 - 마법사 상어와 파이어볼 자바

developerU 2022. 4. 28. 03:47

문제

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

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

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

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

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

  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

 

예제 입력

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

예제 출력

9

 

풀이 방법

1. 2차원 배열의 요소로 Queue를 이용했다. Queue<Fireball> map;

2. map 완탐을 하며 queue의 크기가 0보다 크면 Fireball이 있는 것이므로 각각의 Fireball을 모두 이동시켰다.

   이때 이동한 Fireball과 이동 전 Fireball의 구분을 위해 이동 후 Fireball은 새로운 newMap을 이용

   (map에 있는 Fireball을 이동 시킬 때 poll()을 이용 -> 이동 후 map에 있는 queue는 모두 empty가 된다.)

3. 이동 후 newMap을 완탐을 하며 queue의 크기가 1이면 newMap의 Fireball을 poll()해서 map에 offer()

    queue의 크기가 2이상이면 문제에서 설명한 대로 조건을 적용한다.

 

 

함정이란 함정에는 다 빠지면서 풀었다.

가장 크게 틀렸던 이유는 3가지이다.

 

1. 모듈러 연산 범위를 벗어나는 문제

ex) 7개의 칸(0~6), 현재 위치가 1 , 왼쪽으로 9칸 이동 -> 이동 후 위치 6번째 칸이어야 한다.

     계산 식으로 보면 (1 - 9 + 7) % 7 = -1

     음수를 수정하기 위해 -9%7을 해주면 된다.

     => (1 + (- 9 % 7) + 7) % 7 = 6

   

  반례) 답 : 22

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

 

2. 파이어볼이 2개 이상 있을 때 방향을 정하는 부분에서 문제

   합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고,

   그렇지 않으면 1, 3, 5, 7이 된다.

 

3. 이동 후 연산

   처음에 푼 방법은 map에서 빼고 이동하는 newMap의 위치에 Fireball이 있으면 바로 계산해서 4개로 나눠버렸다.

   이렇게 되면 이동 후 한 자리(queue)에 4개가 들어온다고 할 때 4개를 한번에 계산하는 것이 아니라

   처음 2개 계산 후 4개로 나누어진 것들과 다음에 들어온 것과 계산 후 또 4개로 나누고... 말도안되는 풀이이다.

   이동을 전부 끝낸 후 Fireball이 2개 이상일 때만 4개로 나누어주자!!

 

   반례) 답 : 4

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

 

내 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main_20056 {
	static int N, M, K;
	
	static class FireBall{
		int m, s, d;	//질량, 속력, 방향

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

		@Override
		public String toString() {
			return "[m=" + m + ", s=" + s + ", d=" + d + "]";
		}
	}
	
	static Queue<FireBall>[][] map;
	static Queue<FireBall>[][] newMap;
	
	static int[] dy = {-1,-1,0,1,1,1,0,-1};	//상, 우상, 우, 우하, 하, 좌하, 좌, 좌상
	static int[] dx = {0,1,1,1,0,-1,-1,-1};
	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());
		
		map = new LinkedList[N][N];
		newMap = new LinkedList[N][N];
		
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				map[i][j] = new LinkedList<>();
				newMap[i][j] = new LinkedList<>();
			}
		}
		
		int r,c,m,s,d;
		for (int i = 0; i < M; i++) {	//파이어볼 M개
			st = new StringTokenizer(br.readLine(), " ");
			r = Integer.parseInt(st.nextToken())-1;
			c = Integer.parseInt(st.nextToken())-1;
			m = Integer.parseInt(st.nextToken());
			s = Integer.parseInt(st.nextToken());
			d = Integer.parseInt(st.nextToken());
			map[r][c].offer(new FireBall(m,s,d));
		}

		for (int i = 0; i < K; i++) {	//상어가 이동 K번 명령
			for (int y = 0; y < N; y++) {
				for (int x = 0; x < N; x++) {
					if(map[y][x].size() > 0)
						move(y,x);
				}
			}
			
			for (int y = 0; y < N; y++) {
				for (int x = 0; x < N; x++) {
					if(newMap[y][x].size() == 1) {	//1개만 있을 때는 그대로
						map[y][x].offer(newMap[y][x].poll());
					}
					else if(newMap[y][x].size() >= 2)	//2개 이상부터는 계산
						calc(y,x);
				}
			}
			
			
		}

		int sum_all = 0;
		for (int y = 0; y < N; y++) {
			for (int x = 0; x < N; x++) {
				if(map[y][x].size() > 0) {
					while(!map[y][x].isEmpty()) {
						sum_all += map[y][x].poll().m;
					}
				}
			}
		}
		
		System.out.println(sum_all);
		
		br.close();

	}

	static void move(int y, int x) {
		while(!map[y][x].isEmpty()) {
			FireBall cur = map[y][x].poll();
			//이동할 곳
			int ny = (y + (dy[cur.d] * cur.s)%N + N) % N;
			int nx = (x + (dx[cur.d] * cur.s)%N + N) % N;
			
			newMap[ny][nx].offer(cur);
		}
	}

	static void copy() {
		for (int y = 0; y < N; y++) {
			for (int x = 0; x < N; x++) {
				map[y][x].clear();
				while(!newMap[y][x].isEmpty())
					map[y][x].offer(newMap[y][x].poll());
			}
		}
	}
	
	private static void calc(int y, int x) {
		int sum_m = 0;
		int sum_s = 0;
		boolean odd = false;
		boolean even = false;
		int flag = 0;
		int cnt = 0;
		
		//위치에 있는 FireBall 다 꺼내서 계산
		while(!newMap[y][x].isEmpty()) {
			FireBall temp = newMap[y][x].poll();
			
			sum_m += temp.m;
			sum_s += temp.s;
			if(temp.d%2 == 1) odd = true;
			else even = true;
			cnt++;
		}
		
		if(odd && even) flag = 1;
		
		int res_m = sum_m/5;
		int res_s = sum_s/cnt;
		
		if(res_m > 0) {	//질량이 0보다 클 때만 queue에 넣어주기
			for (int i = flag; i < 8; i+=2) {	//짝수면 0부터, 홀수면 1부터
				map[y][x].offer(new FireBall(res_m, res_s, i));
			}
		}
	}
}
Comments