백준

[백준_20056번] 마법사 상어와 파이어볼_삼성 SW 역량테스트

빙수빈수 2021. 9. 9. 14:23

https://www.acmicpc.net/problem/20056

 

20056번: 마법사 상어와 파이어볼

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

www.acmicpc.net

[문제]

어른 상어가 마법사가 되었고, 파이어볼을 배웠다. 마법사 상어가 크기가 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칸 만큼 이동한다.
    1. 이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.
  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로 이루어져 있다.
  • 서로 다른 두 파이어볼의 위치가 같은 경우는 입력으로 주어지지 않는다.

 

[코드]

import java.util.*;

class FireBall{
    int m, d, s;
    
    FireBall(int m, int d, int s) {
        this.m = m;
        this.d = d;
        this.s = s;
    }
}
public class BaekJoon_20056 {
	static int n;
	static LinkedList<FireBall> map[][];
	static int dx[] = {-1,-1,0,1,1,1,0,-1};
	static int dy[] = {0,1,1,1,0,-1,-1,-1};
	public static void main(String[] args) throws Exception {
		Scanner sc=new Scanner(System.in);
		       
		n =sc.nextInt();
		int M=sc.nextInt();
		int K=sc.nextInt();
		map = new LinkedList[n][n];
		
		// 각 배열의 좌표마다 연결리스트 생성
		for(int i=0;i<n;i++)
		    for(int j=0;j<n;j++)
		        map[i][j]=new LinkedList<>();
		        
		for(int i=0;i<M;i++) {
		    int r=sc.nextInt()-1;
		    int c=sc.nextInt()-1;
		    int m=sc.nextInt();
		    int s=sc.nextInt();
		    int d=sc.nextInt();
		    
		    // r행 c열에 질량 m, 방향 d, 속도 s 파이어볼 저장
		    map[r][c].add(new FireBall(m,d,s));
		}
		
		// k번 파이어볼 이동 수행
		for(int i=0;i<K;i++)
		     	move();
		
		// 모든 파이어볼의 질량의 합 구하기
		System.out.println(sum());
	}
		    
	public static void move() {
		// 이동한 파이어볼의 정보를 저장할 임시 배열 
		LinkedList<FireBall> next[][]=new LinkedList[n][n];
		// 배열 초기화 
		for(int i=0;i<n;i++)
		    for(int j=0;j<n;j++)
		        next[i][j] = new LinkedList<>();
		 
		// 파이어볼 이동
		for(int i=0;i<n;i++) {
		    for(int j=0;j<n;j++) {
		    	// 파이어볼이 1개 이상 있다면
		        if(map[i][j].size()>=1) {
		        	// i행 j열에 배열에 있는 모든 파이어볼 이동
		           for(FireBall fb : map[i][j]) {
		               int distance=fb.s%n; // 이동할 속력이 n보다 큰 경우 반복을 방지하기 위해 모듈러 값 사용
		               // 현재 방향에 속력을 곱해 다음 좌표 값 계산
		               int x=i+dx[fb.d]*distance; 
		               int y=j+dy[fb.d]*distance;
		               
		               // 범위를 벗어난 경우 인덱스 조정
		               if(x>=n) x-=n; // 행이 아랫쪽으로 넘어간 경우
		               else if(x<0) x+=n; // 행이 윗쪽으로 넘어간 경우
		               if(y>=n)  y-=n; // 열이 오른쪽으로 넘어간 경우
		               else if(y<0) y+=n; // 열이 왼쪽으로 넘어간 경우
		               
		               /*
		                * 임시 배열의 이동한 좌표 x행 y열에 새로운 파이어볼 저장 
		                * 해당 함수는 단순히 파이어볼을 이동시키는 것이기 때문에 기존의 질량,방향,속도를 가져간다.
		                */
		               next[x][y].add(new FireBall(fb.m, fb.d, fb.s));
		           	}
		        }
		    }
		}	
		map = next; // 배열 덮어씌우기
		split(); // 파이어볼 합치기
	}
		    
	public static void split() {
		for(int i=0;i<n;i++) {
		    for(int j=0;j<n;j++) {
		    	// 배열을 탐색하면서 파이어볼이 두개 이상인 곳을 찾는다.
		        if(map[i][j].size()>=2) {
		           int mSum=0; // 질량 합 
		           int sSum=0; // 속도 합
		           boolean even=true,odd=true; 
		           
		           // 모든 파이어볼 탐색
		           for(FireBall fb:map[i][j]) {
		              mSum+=fb.m; // 질량 합 누적
		              sSum+=fb.s; // 속도 합 누적
		              
		              /*
		               * 파이어볼의 위치가 모두 짝수라면 even 값이 true, 모두 홀수라면 odd 값이 true일 것이고,
		               * 짝수와 홀수가 섞여있다면 even, odd 값이 모두 false일 것이다. 
		               * 
		               * 이후 나누어진 파이어볼을 배치할 때 even 또는 odd 중 하나라도 true 값이 있다면 0,2,4,6에 배치하고
		               * 두 값중 true가 하나도 없다면 1,3,5,7에 배치하면 된다. 
		               */
		              if(fb.d%2==0) 
		                 odd=false;
		              else 			
		                 even=false;
		          }
		           
		           // 나누어질 파이어볼의 질량, 속도를 구한다.
		           int m=mSum/5; // 질량은 (합쳐진 파이어볼 질량의 합)/5
		           int s=sSum/map[i][j].size(); // 속력은 (합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)
		                   
		           map[i][j].clear(); // 해당 좌표의 파이어볼은 각 방향으로 나뉘기 때문에 배열 비우기
		           
		           // 질량이 0이면 소멸되기 때문에 질량이 0이 아니라면 파이어볼 배치
		           if(m>0) {		                        
		        	   for(int a=0;a<4;a++) {		                            
		        		   // 합쳐지는 파이어볼의 방향이 모두 홀수이거나 짝수이면 0,2,4,6에 파이어볼 배치
		        		   if(odd||even)		                                
		        			   map[i][j].add(new FireBall(m,0+2*a,s));		                            
		        		   // 합쳐지는 파이어볼의 방향이 모두 홀수이거나 짝수가 아니면 1,3,5,7에 파이어볼 배치
		        		   else		                                
		        			   map[i][j].add(new FireBall(m,1+2*a,s));
		                        
		        	   }		                    
		           }		           		                
		        }		            
		    }		        
		}		    
	}
		    
	// 모든 파이어볼의 질량 합 구하기
	public static long sum() {
		 long sum=0;
		 for(int i=0;i<n;i++) {
		     for(int j=0;j<n;j++){
		         for(FireBall fb:map[i][j])
		             sum+=fb.m;
		     }
		}		        
		return sum;
	}
}

 

[고찰]

 이번 문제는 스스로 구현하기에는 헷갈리는 부분이 많아 다른 사람의 코드를 참고하여 해결하였다. 이전에 풀었던 문제들보다는 살짝 더 어렵다고 느껴 우선 문제를 구조화해보았다. 

 

*** 0) LinkedList<FireBall> map[][] 자료구조 사용

여러 문제를 풀어보면서 이런 자료구조를 사용했던 경험이 없어 당황했다. 2차원 배열 각각의 인덱스에 파이어볼의 질량, 방향, 속도를 저장하고 있는 클래스 FireBall을 여러개 저장할 수 있는 연결리스트를 선언하는 자료구조이다. 이를 사용하면 배열의 인덱스와, 각 칸에 저장된 모든 파이어볼을 동시에 다룰 수 있기 때문에 해당 문제에 최적화 된 자료구조이다. 

 

*** 1) main 함수

1-1) 초기의 파이어볼의 위치를 입력받는다. 이때 문제는 배열의 인덱스를 1부터 count하기 때문에 좌표에 각각 -1한 값에 저장해야 함을 명심해야 한다. 

1-2) 입력받은 k만큼 파이어볼을 이동시키는 move 함수를 실행한다.

1-3) 모든 이동이 종료되면 전체 배열을 탐색해 모든 파이어볼의 질량 합을 구하는 함수를 실행해 결과 값을 출력한다. 

 

*** 2) 파이어볼 이동(move 함수)

2-1) 오작동을 방지하기 위해 파이어볼을 이동시킬 때 map에 있는 파이어볼들을 이동 후 임시 배열에 저장하고, 모든 이동이 끝났을 때 map 배열에 덮어씌운다.

2-2) 모든 배열을 탐색하면서 파이어볼이 1개 이상 있는 곳의 모든 파이어볼 이동을 시작한다. 이때 파이어볼의 속력이 배열의 범위 n보다 크다면 반복을 방지하기 위해 n으로 나눈 나머지만큼만 이동한다. 

2-3) 위에서 구한 속력만큼만 파이어볼의 방향으로 이동하여 좌표를 구해준다. 이때 맵은 양옆과 위아래가 이어져있기 때문에 배열의 범위를 벗어난 경우는 인덱스 조정을 해주어야 한다. 

2-4) 모든 과정이 끝났다면 임시배열을 map에 덮어씌우고, 파이어볼을 나눠주는 함수(split)를 실행한다.

 

*** 3) 파이어볼 나눠주기(split 함수)

3-1) 배열을 탐색하면서 파이어볼이 2개 이상인 칸을 찾는다.

3-2) 배열에 저장된 모든 파이어볼의 질량과 속도의 합을 구하면서 파이어볼의 위치에 따라 even, odd 변수의 값을 바꿔준다. 

3-3) 누적 질량과 속도 값을 사용하여 각 파이어볼에 할당할 질량과 속도를 구하고, 이때 질량이 0을 넘는다면 even과 odd 변수에 따라 나뉘어질 파이어볼의 방향에 파이어볼을 배치한다.

 

 이번 문제는 처음 사용해보는 자료구조와 구현해야 하는 조건들이 많은 어려운 문제였다. 복습을 통해 내것으로 만들어야겠다.