마법사 상어와 파이어볼
1 초 | 512 MB | 24111 | 9803 | 6057 | 36.906% |
문제
어른 상어가 마법사가 되었고, 파이어볼을 배웠다.
마법사 상어가 크기가 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 |
마법사 상어가 모든 파이어볼에게 이동을 명령하면 다음이 일들이 일어난다.
- 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
- 이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.
- 이동이 모두 끝난 뒤, 2개 이상의 파이어볼이 있는 칸에서는 다음과 같은 일이 일어난다.
- 같은 칸에 있는 파이어볼은 모두 하나로 합쳐진다.
- 파이어볼은 4개의 파이어볼로 나누어진다.
- 나누어진 파이어볼의 질량, 속력, 방향은 다음과 같다.
- 질량은 ⌊(합쳐진 파이어볼 질량의 합)/5⌋이다.
- 속력은 ⌊(합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)⌋이다.
- 합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고, 그렇지 않으면 1, 3, 5, 7이 된다.
- 질량이 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. 1번행과 N번 행은 연결, 1번열과 N번 열은 연결되어있습니다.
2. 마법사 상어는 문제의 조건에 맞게 파이어볼을 발사합니다.
3. K번 이동 명령을 진행한 후, 남아있는 파이어볼 질량의 합을 결과로 출력합니다.
알고리즘 진행 순서.
1. 입력된 정보를 저장합니다.
2. K번 이동명령을 진행합니다.
3. 남아있는 파이어볼 질량의 합을 결과로 출력합니다.
파이어볼 이동명령
1번행과 N번 행은 연결, 1번열과 N번열은 연결?
※처음에 이 문장에 대해서 이해하지 못하였지만 예제입력을 시뮬레이션 돌려보면서 이해가 되었습니다.
파이어볼(↓) |
위에 표에서 파이어볼이 4번 방향으로 1칸 이동할 때 결과
파이어볼 | ||
칸을 이동할 때 범위를 넘어가면 다시 1이나 N으로 반복되는 것을 뜻합니다.
파이어볼 이동방향
7(-1, -1) | 0(-1, 0) | 1(-1, 1) |
6(0, -1) | 파이어볼 | 2(0, 1) |
5(1, -1) | 4(1, 0) | 3(1, 1) |
방향을 변경값을 표현하는 배열
dr[] = {-1, -1, 0, 1, 1, 1, 0, -1}
dc[] = {0, 1, 1, 1, 0, -1, -1, -1}
이동 순서.
1. 모든 파이어볼 속도(s)와 방향(d)에 맞게 이동합니다.
2. 파이어볼 2개 이상있는 칸 조건에 맞게 분열 진행!
1) 나눠질 때 질량 : 질량의 합 / 5
2) 나눠질 때 속도 : 속도의 합 / 파이어볼 개수
3) 나눠질 때 방향 : 모든 방향이 홀수이거나 짝수일 때 {0, 2, 4, 6}, 아닐 때 {1, 3, 5, 7}
※저는 문제를 잘못 해석해서 분열된 파이어볼을 이동하는 것처럼 구현해서 시간 낭비를 했습니다 ㅠ^ㅠ 파이어볼 방향만 바뀝니다!
예제입력 3.
1. 입력된 정보를 저장합니다.
N : 4
M : 2
K : 3
초기 파이어볼 정보
{5, 2, 2} | {7, 1, 6} | ||
2. K번 이동명령을 진행합니다.
1번째 이동명령 진행!
{5, 2, 2}, {7, 1, 6} | |||
분열 진행!
분열될 질량 : 12 / 5 = 2
분열될 속도 : 3 / 2= 1
분열될 방향 : {2, 6} 모두 짝수 = {0, 2, 4, 6}
분열 완료!
{2, 1, 0}, {2, 1, 2}, {2, 1, 4}, {2, 1, 6} | |||
2번째 이동명령 진행!
{2, 1, 6} | {2, 1, 2} | ||
{2, 1, 4} | |||
{2, 1, 0} |
분열할 파이어볼 존재 X
3번째 이동명령 진행!
{2, 1, 6}, {2, 1, 2} | |||
{2, 1, 4}, {2, 1, 0} | |||
(1, 1) 분열 진행!
분열될 질량 : 4 / 5 =0
분열될 질량이 0이므로 분열 종료!
(3, 3) 분열 진행!
분열될 질량 : 4 / 5 =0
분열될 질량이 0이므로 분열 종료!
분열 완료!
3. 남아있는 파이어볼 질량의 합을 결과로 출력합니다.
남아있는 파이어볼 개수 : 0개
0을 결과로 출력합니다.
- BufferedReader를 사용하여 입력되는 정보를 저장합니다.
- StringTokenizer를 이용하여 파이어볼들의 정보를 띄어쓰기 기준 나누었습니다.
- K번 meteorMove와 meteorFire를 실행하여 모든 파이어볼 이동과 분열을 진행합니다.
- meteorCal을 실행하여 남은 파이어볼 질량의 합을 결과로 BufferedWriter에 저장하였습니다.
- BufferedWriter에 저장된 결과값을 출력하였습니다.
- meteorMove함수는 파이어볼의 이동을 진행합니다.
- meteorFire함수는 2개 이상의 파이어볼이 같이 있으면 분열을 진행합니다.
- meteorCal함수는 남은 파이어볼 질량의 합을 구합니다.
결과코드
import java.sql.Array;
import java.util.*;
import java.io.*;
public class Main {
//파이어볼 정보 클래스
static class meteor{
int r, c, m, s, d;
public meteor(int r, int c, int m, int s, int d){
this.r = r;
this.c = c;
this.m = m;
this.s = s;
this.d = d;
}
}
static int[] dr = {-1, -1, 0, 1, 1, 1, 0, -1}; //방향 r값 변경값
static int[] dc = {0, 1, 1, 1, 0, -1, -1, -1}; //방향 c값 변경값
static ArrayList<meteor>[][] map; //파이어볼 이동했을 때 정보
static ArrayList<meteor> meteors = new ArrayList<>(); //모든 파이어볼 정보
public static void main(String[] args) throws IOException {
//입력값 처리하는 BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//결과값 출력하는 BufferedWriter
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
map = new ArrayList[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
map[i][j] = new ArrayList<>();
}
//입력되는 파이어볼 정보 저장
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());
meteors.add(new meteor(r, c, m, s, d));
}
//K번 이동명령 진행
for (int i = 0; i < K; i++) {
meteorMove(N);
meteorFire(N);
}
bw.write(meteorCal() + ""); //메테오 질량의 합 BufferedWriter 저장
bw.flush(); //결과 출력
bw.close();
br.close();
}
//파이어볼 이동시키는 함수
static void meteorMove(int N) {
for (meteor cur : meteors) {
//r, c값 변경
// +N을 하는 이유는 이동하였을 때 음수가 나올 수 있기 때문입니다.
int tempR = (cur.r + N + dr[cur.d] * (cur.s%N)) % N;
int tempC = (cur.c + N + dc[cur.d] * (cur.s%N)) % N;
cur.r = tempR;
cur.c = tempC;
//이동한 파이어볼 저장
map[cur.r][cur.c].add(cur);
}
}
//파이어볼 분열 진행
static void meteorFire(int N){
for(int r = 0; r<N;r++){
for(int c = 0; c<N;c++) {
//파이어볼 개수가 2개 미만일 때
if(map[r][c].size() < 2){
map[r][c].clear();
continue;
}
//파이어볼 2개 이상일 때
int mSum = 0, sSum = 0, oddCount = 0, evenCount = 0;
int size = map[r][c].size();
//분열 진행!
for(meteor cur : map[r][c]){
mSum += cur.m; //질량 더하기
sSum += cur.s; //속도 더하기
if(cur.d % 2 == 1) //방향 홀수일 때
oddCount++;
else //방향 짝수일 때
evenCount++;
meteors.remove(cur); //분열될 파이어볼 제거!
}
map[r][c].clear();
mSum /= 5; //분열될 질량 구하기
if(mSum == 0) //분열될 질량이 0이면 분열 실패!
continue;
sSum /= size; //분열될 속도 구하기
//모든 파이어볼 방향이 짝수이거나 홀수일 때
if(oddCount == size || evenCount == size){
for(int i=0;i<8;i+=2) //{0, 2, 4, 6} 방향 분열
meteors.add(new meteor(r,c,mSum, sSum, i));
}else{
for(int i=1;i<8;i+=2) //{1, 3, 5, 7} 방향 분열
meteors.add(new meteor(r,c,mSum, sSum, i));
}
}
}
}
//파이어볼 질량의 합 구하는 함수
static int meteorCal(){
int result = 0;
//모든 질량 더하기!
for(meteor cur : meteors)
result += cur.m;
return result;
}
}