[알고리즘 풀이 분석] BOJ 20056 마법사 상어와 파이어볼

오늘 풀어본 문제는 BOJ 20056 마법사 상어와 파이어볼 이다. 시뮬레이션 문제인데, 난 꽤 어려웠다,, 난 시뮬레이션에 참 약한 것 같다 ㅜ




BOJ 20056 마법사 상어와 파이어볼

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

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

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

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

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

  1. 모든 파이어볼이 자신의 방향 di로 속력 si칸 만큼 이동한다.
  • 이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.
  1. 이동이 모두 끝난 뒤, 2개 이상의 파이어볼이 있는 칸에서는 다음과 같은 일이 일어난다.
  • 같은 칸에 있는 파이어볼은 모두 하나로 합쳐진다.
  • 파이어볼은 4개의 파이어볼로 나누어진다.
  • 나누어진 파이어볼의 질량, 속력, 방향은 다음과 같다.
    • 질량은 ⌊(합쳐진 파이어볼 질량의 합)/5⌋이다.
    • 속력은 ⌊(합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)⌋이다.
    • 합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고, 그렇지 않으면 1, 3, 5, 7이 된다.
  1. 질량이 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



나의 풀이

문제를 보자마자 쉽지 않겠구나 싶었다 후

특별한 알고리즘이 사용되는 문제보다 이렇게 여러개의 요소를 꼼꼼히 확인하고 정확하게 다루는 문제가 더 어려운 것 같다.

먼저 문제는 크게 두 단계로 나뉜다.

파이어볼 이동시키기 + 파이어볼 합쳐서 다시 나누기

또한 이 문제의 특징은 파이어볼에 주어지는 요소가 여러개이다. 파이어볼의 위치를 나타내는 좌표와 질량, 속도, 이동 방향을 모두 기억해야 한다. 나의 경우에는 질량, 속도, 이동 방향을 프로퍼티로 갖는 구조체 FireBall을 선언하고 N*N 크기의 좌표마다 FireBall을 벡터로 가지는 2차원 배열 map을 선언하여 관리 하였다.

이렇게 자료구조를 선언하는 방식이 효율적인지 조금 의문스러웠지만 입력 범위가 많이 크지 않았기 때문에 단순하고 정확하게 푸는 쪽을 택했다.


이동

2차원 배열 map을 돌면서 그 자리에 위치한 볼들 속력, 이동 방향을 사용해 새로운 좌표로 이동 시키고 이동후의 볼들을 기록하는 새로운 2차원 좌표 moved 에 반영시킨다.

새로운 좌표를 구하는 과정에서 애를 좀 먹었는데 이동하는 속도가 N 보다 클 경우에는 mod 연산을 통해 그 값을 줄여주고, 이동하는 방향이 경계를 넘어가 이동 후 좌표 값이 0보다 작거나 N-1 보다 클 경우에는 순환 할 수 있도록 처리해 주는 부분이 중요했다.


합쳐서 다시 나누기

이동한 볼들을 저장한 2차원 배열 moved를 동일하게 탐색하면서 진행한다.

이때 좌표에 위치한 볼이 1개일 경우에는 합치고 나눈 뒤 새롭게 반영될 map에 바로 넣어주고 2개 이상일 경우에만 진행한다.

위치한 볼들의 질량합, 속도 합을 구해주고, 이동 방향들이 모두 홀/짝이 일치 하는지를 검사한 뒤 새로운 질량, 속도, 이동 방향을 반영해 map 에 넣어준다.

이 과정을 K 번 진행하면서 마지막 수행일 경우 map 에 최종적으로 들어가는 볼들의 질량을 더해 반환한다.


순환되는 좌표 부분을 좀더 다루는 연습이 필요한 것 같다..!




코드

#include <iostream>
#include <vector>

// boj 20056 마법사 상어아 파이어볼, 골드 5, simulation
using namespace std;

int dy[8] = {-1,-1,0,1,1,1,0,-1};
int dx[8] = {0,1,1,1,0,-1,-1,-1};

typedef struct fireball {
    int mass;
    int speed;
    int dir;

    fireball(int m, int s, int d) {
        this->mass = m;
        this->speed = s;
        this->dir = d;
    }
}FireBall;

vector<vector<vector<FireBall>>> map;

int moveFireBall(int N, int M, int K){
    int left_mass = 0;
    int order = K;

    while (order>0){
        order--;
        vector<vector<vector<FireBall>>> moved(N+1, vector<vector<FireBall>>(N+1));

        // 1. 이동
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (map[i][j].empty()) continue;

                for (int k = 0; k < map[i][j].size(); ++k){
                    int m = map[i][j][k].mass;
                    int s = map[i][j][k].speed;
                    int d = map[i][j][k].dir;

                    int nr = i + dy[d] * (s % N);
                    if (nr<1) nr += N;
                    if (nr>N) nr -= N;

                    int nc = j + dx[d] * (s % N);
                    if(nc<1) nc += N;
                    if (nc>N) nc -= N;

                    moved[nr][nc].push_back(FireBall(m, s, d));
                }
            }
        }

        // 2. 정리
        map.clear();
        map.assign(N+1, vector<vector<FireBall>>(N+1));

        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (moved[i][j].empty()) continue;

                if (moved[i][j].size()==1){
                    map[i][j].push_back(FireBall(moved[i][j][0].mass,moved[i][j][0].speed,moved[i][j][0].dir));
                    if(order==0) left_mass += moved[i][j][0].mass;
                    continue;
                }

                int mass_sum = 0, speed_sum = 0;
                bool same_dir = true;

                for (int k = 0; k < moved[i][j].size(); ++k) {
                    mass_sum += moved[i][j][k].mass;
                    speed_sum += moved[i][j][k].speed;

                    if(k>0 && same_dir){
                        if (moved[i][j][k-1].dir%2 != moved[i][j][k].dir%2) same_dir = false;
                    }

                }

                mass_sum = mass_sum/5;
                if (mass_sum == 0) continue;

                speed_sum = speed_sum/moved[i][j].size();

                int dir_base = -1;
                if (same_dir) dir_base = 0;
                else dir_base = 1;

                for (int k = 0; k <=6 ; k += 2) {
                    map[i][j].push_back(FireBall(mass_sum, speed_sum, dir_base+k));
                    if (order==0) left_mass += mass_sum;
                }
            }
        }

    }

    return left_mass;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int N, M, K;
    cin>>N>>M>>K;

    map.assign(N+1, vector<vector<FireBall>>(N+1));

    int r, c, m, s, d;
    for (int i = 0; i < M; ++i) {
        cin>>r>>c>>m>>s>>d;
        map[r][c].push_back(FireBall(m, s, d));
    }

    cout<<moveFireBall(N,M,K);

    return 0;
}

좋은 웹페이지 즐겨찾기