<BOJ> 16235 자바 (나무 재테크)

2024. 7. 20. 22:18Algorithm/BOJ

반응형

 

 

 

 

구현 문제를 구분하는 힘이 부족했다.

 

 

입력

  1. N M K
  2. N 개의 줄에 A 배열의 값 A[r][c]
  3. M 개의 줄에 나무의 위치(y, x), 나무의 나이(age)

봄: 자기 나이만큼 양분을 먹음, 나이 1증가

. 여러 개의 나무가 있음? 어린 나무부터 양분 섭취, => PQ 사용

. 자신 나이보다 양분이 적음? 즉시 죽음

여름: 봄에 죽은 나무가 양분으로 변함 (나이/2)

가을: 나무가 번식, 번식하는 나무의 나이는 5의 배수, 인접 8칸에 나이 1의 나무가 생김

겨울: 로봇이 땅을 돌아다니면서 땅에 양분을 추가, A[r][c]의 양이고 입력으로 주어짐

문제를 제대로 읽지 않아서 많이 헤맸는데.. 초기 양분 값이 5라는 걸 읽지 못하고 테스트 입출력이 잘못된 게 아닌가 싶었었다.

 

1차 풀이) 바로 시간 초과가 났다.

package algorithms.src;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class b16235_나무재테크 {
    static int N, M, K;
    static boolean flag;
    // 상 상우 우 하우 하 하좌 좌 상좌
    static int[] dy = {-1, -1, 0, 1, 1, 1, 0, -1};
    static int[] dx = {0, 1, 1, 1, 0, -1, -1, -1};
    static PriorityQueue<Tree> q = new PriorityQueue<>();

    static int[][] A, map;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());


        map = new int[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                map[i][j] = 5;
            }
        }

        A = new int[N][N];

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < N; j++) {
                A[i][j] = Integer.parseInt(st.nextToken());
            }
        }


        // 나무의 위치
        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int y = Integer.parseInt(st.nextToken());
            int x = Integer.parseInt(st.nextToken());
            int age = Integer.parseInt(st.nextToken());

            q.add(new Tree(y-1, x-1, age, 0, true));
        }

        System.out.println(BFS());

    }


    private static int BFS() {

        while (!q.isEmpty()) {
            Tree tree = q.poll();

            int ty = tree.y;
            int tx = tree.x;
            int age = tree.age;
            boolean alive = tree.alive;
            int weather = tree.weather % 4;

            if(tree.weather == K *4) {
                return q.size() +1;
            }

            switch (weather) {
                case 0 : // in spring
                    flag = false;
                    if(map[ty][tx] >= age) {
                        map[ty][tx] -= age;
                        age+=1;
                    } else alive = false;

                    q.add(new Tree(ty, tx, age, tree.weather+1, alive));
                    break;

                case 1:  // in summer , if false? age /2 +
                    if(!alive) map[ty][tx] += age/2;
                    else q.add(new Tree(ty, tx, age, tree.weather+1, alive));
                    break;

                case 2: // fall , 나무 번식
                    if(age %5 == 0) {
                        for (int i = 0; i < 8; i++) {
                            int ny = ty + dy[i];
                            int nx = tx + dx[i];
                            if (ny < 0 || ny >=N || nx < 0 || nx >= N) continue;
                            q.add(new Tree(ny, nx, 1, tree.weather+1, true));
                        }
                    }
                    q.add(new Tree(ty, tx, age, tree.weather+1, alive));

                    break;

                case 3: // winter, 양분 채워넣기
                    if(!flag) {
                        for (int i = 0; i < N; i++) {
                            for (int j = 0; j < N; j++) {
                                map[i][j] += A[i][j];
                            }
                        }
                        flag = true;
                    }
                    q.add(new Tree(ty, tx, age, tree.weather+1, alive));

                    break;
            }
        }

    return -1;
    }


    private static class Tree implements Comparable<Tree> {
        int y;
        int x;
        int age;
        int weather;
        boolean alive;

        public Tree(int y, int x, int age, int weather, boolean alive) {
            this.y = y;
            this.x = x;
            this.age = age;
            this.weather = weather;
            this.alive = alive;
        }

        @Override
        public int compareTo(Tree o) {
            if(this.weather != o.weather) {   // 1. 계절순으로
                return Integer.compare(this.weather, o.weather);
//                return this.weather - o.weather;
            } else
                return Integer.compare(this.age, o.age);
//                return this.age - o.age;   // 어린 나무부터 먹이자.

        }
    }
}

[1차코드 리뷰 및 2차 풀이]

😭 문제점)

  1. 너무 많은 큐 연산이 이뤄졌다. 각 계절마다 모든 나무를 PQ에 다시 추가하고 있었기 때문에 큐의 삽입/삭제 연산이 과도하게 많아졌다.
  2. 1번 문제에 의해 하나의 큐를 통해 처리하다 보니 불필요하고 복잡한 로직이 많이 늘어났다. 구현 문제만 맞닥드리면 나타나는 문제인듯 하다. 특히 계절 순환을 위한 weather 값을 계속 추적하는 부분이 비효율적이었다.

👀 해결방안)

1. 효율적인 데이터 구조사용

- 봄/여름 통합, PQ를 효율적으로 사용, 각 계절별로 나무 리스트를 갱신하여 큐 연산 최소화

2. 로직 단순화

- 계절별로 메서드 분리, 불필요 연산 제거

3. 한번의 순회

- 각 계절을 처리할 때 나무 리스트를 한 번만 순회.

package algorithms.src;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class b16235_나무재테크 {
    static int N, M, K;
    static final int[] dy = {-1, -1, 0, 1, 1, 1, 0, -1}; // 방향 벡터 (상, 상우, 우, 하우, 하, 하좌, 좌, 상좌)
    static final int[] dx = {0, 1, 1, 1, 0, -1, -1, -1}; // 방향 벡터 (상, 상우, 우, 하우, 하, 하좌, 좌, 상좌)
    static PriorityQueue<Tree> treeQueue = new PriorityQueue<>(); // 나무들을 관리하는 우선순위 큐 (나이가 어린 나무부터)
    static int[][] nutrients, addNutrients; // 현재 양분과 겨울에 추가될 양분 배열

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

        nutrients = new int[N][N];
        addNutrients = new int[N][N];

        // 초기 양분 설정: 모든 칸에 5만큼의 양분이 있습니다.
        for (int i = 0; i < N; i++) {
            Arrays.fill(nutrients[i], 5);
        }

        // 겨울에 추가될 양분을 입력받습니다.
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < N; j++) {
                addNutrients[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        // 나무의 초기 상태를 입력받아 우선순위 큐에 추가합니다.
        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int y = Integer.parseInt(st.nextToken()) - 1;
            int x = Integer.parseInt(st.nextToken()) - 1;
            int age = Integer.parseInt(st.nextToken());
            treeQueue.add(new Tree(y, x, age));
        }

        // K년 후 살아남은 나무의 수를 출력합니다.
        System.out.println(simulateSeasons());
    }

    // K년 동안의 계절 순환을 시뮬레이션합니다.
    private static int simulateSeasons() {
        for (int year = 0; year < K; year++) {
            springAndSummer(); // 봄과 여름을 처리합니다.
            fall();            // 가을을 처리합니다.
            winter();          // 겨울을 처리합니다.
        }
        return treeQueue.size(); // 살아남은 나무의 수를 반환합니다.
    }

    // 봄과 여름을 처리하는 메서드
    private static void springAndSummer() {
        PriorityQueue<Tree> nextQueue = new PriorityQueue<>();
        List<Tree> deadTrees = new ArrayList<>();

        // 봄: 나무가 자신의 나이만큼 양분을 먹고 나이가 1 증가합니다.
        while (!treeQueue.isEmpty()) {
            Tree tree = treeQueue.poll();
            if (nutrients[tree.y][tree.x] >= tree.age) {
                nutrients[tree.y][tree.x] -= tree.age;
                tree.age++;
                nextQueue.add(tree);
            } else {
                deadTrees.add(tree);
            }
        }

        // 여름: 봄에 죽은 나무가 양분으로 변합니다.
        for (Tree deadTree : deadTrees) {
            nutrients[deadTree.y][deadTree.x] += deadTree.age / 2;
        }

        treeQueue = nextQueue; // 다음 계절을 위한 나무 리스트 갱신
    }

    // 가을을 처리하는 메서드: 나무가 번식합니다.
    private static void fall() {
        List<Tree> newTrees = new ArrayList<>();

        // 나이가 5의 배수인 나무가 인접한 8개의 칸에 나이가 1인 나무를 번식시킵니다.
        for (Tree tree : treeQueue) {    // poll을 하는 것이 아니라 단순 접근만 하는 것!
            if (tree.age % 5 == 0) {
                for (int i = 0; i < 8; i++) {
                    int ny = tree.y + dy[i];
                    int nx = tree.x + dx[i];
                    if (ny >= 0 && ny < N && nx >= 0 && nx < N) {
                        newTrees.add(new Tree(ny, nx, 1));
                    }
                }
            }
        }

        treeQueue.addAll(newTrees); // 새로운 나무를 기존 나무 리스트에 추가
    }

    // 겨울을 처리하는 메서드: 양분을 추가합니다.
    private static void winter() {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                nutrients[i][j] += addNutrients[i][j];
            }
        }
    }

    // 나무 객체를 정의하는 클래스
    private static class Tree implements Comparable<Tree> {
        int y, x, age;

        Tree(int y, int x, int age) {
            this.y = y;
            this.x = x;
            this.age = age;
        }

        // 나이가 어린 순으로 정렬하기 위해 compareTo 메서드 구현
        @Override
        public int compareTo(Tree other) {
            return Integer.compare(this.age, other.age);
        }
    }
}

반응형

'Algorithm > BOJ' 카테고리의 다른 글

<BOJ> 17142 자바(연구소3)  (0) 2024.07.21
<BOJ> 14502 자바 (연구소)  (4) 2024.07.20
<BOJ> 13913 자바(숨바꼭질4)  (1) 2024.07.20
<BOJ> 5427 자바 (불)  (2) 2024.07.13
<BOJ> 16236 자바 (아기상어)  (0) 2024.07.13