본문 바로가기

알고리즘

백준 - 20056 마법사 상어와 파이어볼(Java)

구현 문제입니다.

 

구현 순서는 다음과 같습니다.

  1. 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
  2. 중복된 위치의 파이어볼을 합치고 분해한다.
  3. 남아있는 파이어볼의 질량을 더한다.

첫 번째는 이동했을 때 위치가 배열 밖에 있을 수 있다는 것을 유의해야 합니다. 문제 조건에 의해 1번 행, 열은 N번 행, 열과 연결되어있습니다. 때문에, 배열 범위를 벗어나면 반대편으로 이동시켜야 합니다.

 

두 번째는 삼차원 배열을 사용했습니다 자세한 것은 주석으로 달아놨으니 읽어보시기 바랍니다.

 

세 번째는 남아있는 파이어볼의 중량(m)만 더하시면 됩니다.

 

[구현 코드]

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

public class Main {

    private static final int[] dx = {-1, -1, 0, 1, 1, 1, 0, -1}; // 상단부터 시계방향
    private static final int[] dy = {0, 1, 1, 1, 0, -1, -1, -1};

    private static int n, m, k;

    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 stz = new StringTokenizer(br.readLine(), " ");
        int answer = 0;

        n = Integer.parseInt(stz.nextToken());
        m = Integer.parseInt(stz.nextToken());
        k = Integer.parseInt(stz.nextToken());

        List<Fireball> fireballs = new ArrayList<>();

        for (int i = 0; i < m; i++) {
            stz = new StringTokenizer(br.readLine(), " ");

            int ri = Integer.parseInt(stz.nextToken()) - 1;
            int ci = Integer.parseInt(stz.nextToken()) - 1;
            int mi = Integer.parseInt(stz.nextToken());
            int si = Integer.parseInt(stz.nextToken());
            int di = Integer.parseInt(stz.nextToken());

            fireballs.add(new Fireball(ri, ci, mi, si, di));
        }

        while (k-- > 0) {
            int ballCount = fireballs.size();

            int[][][] map = new int[n][n][ballCount]; // n*n개의 좌표에 최대 파이어볼의 개수 만큼 존재할 수 있음

            // 1. 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
            moveTheFireball(fireballs, ballCount, map);

            // 2. 중복된 위치의 파이어볼을 합치고 분해한다.
            fireballs = splitTheFireball(fireballs, ballCount, map);
        }

        // 3. 남아있는 파이어볼의 질량을 더한다.
        for (Fireball fireball : fireballs) {
            answer += fireball.m;
        }

        bw.write(String.valueOf(answer));

        bw.flush();
        bw.close();
        br.close();
    }

    private static void moveTheFireball(List<Fireball> fireballs, int ballCount, int[][][] map) {
        for (int i = 0; i < fireballs.size(); i++) {
            Fireball fireball = fireballs.get(i);

            int sx = dx[fireball.d] * fireball.s; // di 방향으로 si 만큼 이동
            int sy = dy[fireball.d] * fireball.s;

            int nx = adjustCoordinate(fireball.r, sx); // 좌표 조정
            int ny = adjustCoordinate(fireball.c, sy);

            for (int j = 0; j < ballCount; j++) {
                if (map[nx][ny][j] == 0) { // nx, ny 좌표에 파이어볼을 위치시킴
                    map[nx][ny][j] = i + 1;
                    break;
                }
            }

            // 이동된 좌표로 바꿈
            fireball.r = nx;
            fireball.c = ny;
        }
    }

    private static int adjustCoordinate(int coordinate, int d) {
        if (d < 0) d = (Math.abs(d) % n) * -1;
        else d = d % n;

        coordinate += d;

        if (coordinate >= 0 && coordinate < n) {
            return coordinate;
        } else if (coordinate >= n) { // 좌표가 n보다 커질 때
            return coordinate - n;
        } else { // 좌표가 0보다 작을 때
            return n + coordinate;
        }
    }

    private static List<Fireball> splitTheFireball(List<Fireball> fireballs, int ballCount, int[][][] map) {
        List<Fireball> move = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (ballCount > 1 && map[i][j][1] > 0) { // i, j에 두 개 이상의 파이어볼이 있는 경우
                    int tm = 0;
                    int ts = 0;
                    int firstDirection = fireballs.get(map[i][j][0] - 1).d;
                    boolean isAllEvenOrOddDirection = true;
                    int cnt = 0;

                    for (int t = 0; t < ballCount; t++) {
                        if (map[i][j][t] == 0) break;

                        Fireball fireball = fireballs.get(map[i][j][t] - 1);
                        tm += fireball.m; // 합쳐진 파이어볼 질량의 합
                        ts += fireball.s; // 합쳐진 파이어볼 속력의 합

                        // 전부 홀수이거나 전부 짝수인지 확인
                        if (isAllEvenOrOddDirection) {
                            if (firstDirection % 2 != fireball.d % 2) {
                                isAllEvenOrOddDirection = false;
                            }
                        }
                        cnt++; // 합쳐진 파이어볼의 개수
                    }

                    for (int o = 0; o < 4; o++) {
                        int pm = tm / 5;
                        int ps = ts / cnt;

                        if (pm == 0) break;

                        if (isAllEvenOrOddDirection) {
                            move.add(new Fireball(i, j, pm, ps, o * 2));
                        } else {
                            move.add(new Fireball(i, j, pm, ps, (o * 2) + 1));
                        }
                    }
                } else if (ballCount > 0 && map[i][j][0] > 0) { // 한 개의 파이어볼만 있는 경우
                    move.add(fireballs.get(map[i][j][0] - 1));
                }
            }
        }

        return move;
    }

    private static class Fireball {
        int r, c, m, s, d;

        public Fireball(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;
        }
    }
}