알고리즘

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

ksb-dev 2023. 3. 21. 18:00

구현 문제입니다.

 

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

  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;
        }
    }
}