본문 바로가기
문제풀이/백준 문제풀이

[ 알고리즘 ] 코딩 - 백준 16235 - 나무재태크.java

by 마늘아빠 2021. 4. 23.
728x90
반응형

문제 링크

www.acmicpc.net/problem/16235

 

16235번: 나무 재테크

부동산 투자로 억대의 돈을 번 상도는 최근 N×N 크기의 땅을 구매했다. 상도는 손쉬운 땅 관리를 위해 땅을 1×1 크기의 칸으로 나누어 놓았다. 각각의 칸은 (r, c)로 나타내며, r은 가장 위에서부터

www.acmicpc.net


문제 개요

BOJ 16235 - (자바) 나무재태크 - 시뮬레이션 / 구현

부동산 투자로 억대의 돈을 번 상도는 최근 N×N 크기의 땅을 구매했다. 상도는 손쉬운 땅 관리를 위해 땅을 1×1 크기의 칸으로 나누어 놓았다. 각각의 칸은 (r, c)로 나타내며, r은 가장 위에서부터 떨어진 칸의 개수, c는 가장 왼쪽으로부터 떨어진 칸의 개수이다. r과 c는 1부터 시작한다.

상도는 전자통신공학과 출신답게 땅의 양분을 조사하는 로봇 S2D2를 만들었다. S2D2는 1×1 크기의 칸에 들어있는 양분을 조사해 상도에게 전송하고, 모든 칸에 대해서 조사를 한다. 가장 처음에 양분은 모든 칸에 5만큼 들어있다.

나무 재테크란 작은 묘목을 구매해 어느정도 키운 후 팔아서 수익을 얻는 재테크이다. 상도는 나무 재테크로 더 큰 돈을 벌기 위해 M개의 나무를 구매해 땅에 심었다. 같은 1×1 크기의 칸에 여러 개의 나무가 심어져 있을 수도 있다.

이 나무는 사계절을 보내며, 아래와 같은 과정을 반복한다.

에는 나무가 자신의 나이만큼 양분을 먹고, 나이가 1 증가한다. 각각의 나무는 나무가 있는 1×1 크기의 칸에 있는 양분만 먹을 수 있다. 하나의 칸에 여러 개의 나무가 있다면, 나이가 어린 나무부터 양분을 먹는다. 만약, 땅에 양분이 부족해 자신의 나이만큼 양분을 먹을 수 없는 나무는 양분을 먹지 못하고 즉시 죽는다.

여름에는 봄에 죽은 나무가 양분으로 변하게 된다. 각각의 죽은 나무마다 나이를 2로 나눈 값이 나무가 있던 칸에 양분으로 추가된다. 소수점 아래는 버린다.

가을에는 나무가 번식한다. 번식하는 나무는 나이가 5의 배수이어야 하며, 인접한 8개의 칸에 나이가 1인 나무가 생긴다. 어떤 칸 (r, c)와 인접한 칸은 (r-1, c-1), (r-1, c), (r-1, c+1), (r, c-1), (r, c+1), (r+1, c-1), (r+1, c), (r+1, c+1) 이다. 상도의 땅을 벗어나는 칸에는 나무가 생기지 않는다.

겨울에는 S2D2가 땅을 돌아다니면서 땅에 양분을 추가한다. 각 칸에 추가되는 양분의 양은 A[r][c]이고, 입력으로 주어진다.

K년이 지난 후 상도의 땅에 살아있는 나무의 개수를 구하자.

 


로직

정말 지긋지긋한 시간초과를 만났다. 처음에 여름 과정에서 죽은 나무를 2차원 배열에 누적해서 더하는 형식으로 했었는데, 이게 시간을 많이 잡아 먹었다. 로직은 위에 말한 그대로를 구현하면 되서 상당히 심플하다. 하지만 시간이 굉장히 짧은 것을 유의하자.

Step 00. 나무의 정보를 저장할 class, 양분정보를 저장할 2차원 배열, S2D2의 정보를 저장할 2차원배열을 생성한다. 현재 심어져있는 모든 나무는 하나의 우선순위큐(tree_pQueue)를 이용해서 관리할 것이다. 입력의 위치에 나이를 저장하자.

본격적으로 사계절에 돌입하기 전에, 죽은 나무(dead Queue)와 나이가 5배수가 되는 나무(asc_age_queue Queue)는 각자의 Queue를 이용해서 관리 할 것이다. 

Step 01. . 모든 나무는 양분을 먹는다. 이 작업은 기존 우선순위큐(tree_pQueue)가 아닌 새로운 우선순위큐를 생성해서 해야한다. 양분보다 나이가 많다면, 해당 나무는 죽고 dead Queue에 위치와 나이를 저장해준다. 양분을 먹었다면 나이를 +1 해주고, 지도에서 나이만큼 빼주자. 만약 나이가 +1 되었는데 5의 배수에 해당한다면 asc_age_queue Queue에 저장해서 관리해주자.

Step 02. 여름. dead Queue에 저장된 정보를 이용해서 지도에 양분을 늘려주자.

Step 03. 가을. asc_age_queue Queue에 저장된 모든 나무는 8방향으로 묘목을 심는다. 위치 정보만을 알아내서 tree_pQueue에 저장해주자.

Step 04. 겨울. S2D2에 저장된 양분 정보를 이용해서 양분을 늘려준다.

Step 05. Step 01 ~ Step 04의 과정을 K번 반복한 후 tree_pQueue의 사이즈를 출력하면 정답!

 

우선순위 큐를 이용하지 않고, Deque를 이용하는 방법도 있다. 그 이유는 처음에 모든 나무의 위치는 중복이 없고, 새로 추가되는 나무는 무조건 1의 크기이기 때문에, 굳이 우선순위큐를 이용할 이유가 없다. 


코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	static class Tree {
		int y, x, age;

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

		@Override
		public String toString() {
			return "age: " + this.age;
		}
	}

	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer stt = new StringTokenizer(br.readLine());
		int N = Integer.parseInt(stt.nextToken());
		int M = Integer.parseInt(stt.nextToken());
		int K = Integer.parseInt(stt.nextToken());

		int[][] robot = new int[N + 1][N + 1];
		int[][] map = new int[N + 1][N + 1];

		for (int i = 1; i <= N; i++) {
			stt = new StringTokenizer(br.readLine());
			for (int j = 1; j <= N; j++) {
				robot[i][j] = Integer.parseInt(stt.nextToken());
				map[i][j] = 5;
			}
		}

		// 해당 좌표에 나무 저장 관리 PQ
		PriorityQueue<Tree> tree_pQueue = new PriorityQueue<Tree>((o1, o2) -> (o1.age - o2.age));

		for (int i = 0; i < M; i++) {
			stt = new StringTokenizer(br.readLine());
			int y = Integer.parseInt(stt.nextToken());
			int x = Integer.parseInt(stt.nextToken());
			int age = Integer.parseInt(stt.nextToken());
			tree_pQueue.offer(new Tree(y, x, age));
		}

		int res = process(map, tree_pQueue, robot, N, K);

		System.out.println(res);
	}

	private static int process(int[][] map, PriorityQueue<Tree> tree_pQueue, int[][] robot, int n, int k) {
		Queue<Tree> dead = new LinkedList<Tree>();
		Queue<Tree> asc_age_queue = new LinkedList<Tree>();
		// K년 반복
		for (int i = 0; i < k; i++) {
			// 봄
			tree_pQueue = spring(map, tree_pQueue, asc_age_queue, dead, n);

			// 여름
			summer(map, dead, n);

			// 가을
			autumn(tree_pQueue, asc_age_queue, n);

			// 겨울
			winter(map, robot, n);
		}

		// 반복이 모두 끝나고 난 후 나무의 갯수 카운팅
		int res = count_trees(tree_pQueue, n);
		return res;
	}

	// 남은 나무의 수를 카운팅
	private static int count_trees(PriorityQueue<Tree> tree_pQueue, int n) {
		int sum = tree_pQueue.size();
		return sum;
	}

	// 겨울. 로봇이 양분을 뿌린다
	private static void winter(int[][] map, int[][] robot, int n) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				map[i][j] += robot[i][j];
			}
		}
	}

	// 가을. 나이가 5의 배수인 나무는 8방향으로 씨를 뿌린다.
	private static void autumn(PriorityQueue<Tree> tree_pQueue, Queue<Tree> asc_age_queue, int n) {
		// 북쪽부터 반시계방향
		int[] dy = { -1, -1, 0, 1, 1, 1, 0, -1 };
		int[] dx = { 0, -1, -1, -1, 0, 1, 1, 1 };

		while (!asc_age_queue.isEmpty()) {
			Tree tree = asc_age_queue.poll();
			int y = tree.y;
			int x = tree.x;

			for (int d = 0; d < 8; d++) {
				int next_y = y + dy[d];
				int next_x = x + dx[d];

				if (rangeCheck(next_y, next_x, n))
					continue;
				tree_pQueue.offer(new Tree(next_y, next_x, 1));
			}
		}
	}

	// 범위 체크
	private static boolean rangeCheck(int next_y, int next_x, int n) {
		return next_y <= 0 || next_y >= n + 1 || next_x <= 0 || next_x >= n + 1;
	}

	// 여름. 죽은 나무의 나이/2 한만큼 양분이 늘어난다.
	private static void summer(int[][] map, Queue<Tree> dead, int n) {
		while (!dead.isEmpty()) {
			Tree tree = dead.poll();
			int y = tree.y;
			int x = tree.x;
			int age = tree.age;
			map[y][x] += age;
		}
	}

	// 봄. 양분과 나이를 먹는다
	private static PriorityQueue<Tree> spring(int[][] map, PriorityQueue<Tree> tree_pQueue, 
    							Queue<Tree> asc_age_queue, Queue<Tree> dead, int n) {
		// 모든 나무 순회
		PriorityQueue<Tree> tmp_pQueue = new PriorityQueue<Tree>((o1, o2) -> o1.age - o2.age);
		while (!tree_pQueue.isEmpty()) {
			Tree tree = tree_pQueue.poll();
			int y = tree.y;
			int x = tree.x;
			int age = tree.age;
			if (age > map[y][x]) {// 나이 확인
				// 양분을 먹지 못하면 나무는 죽음
				dead.offer(new Tree(y, x, age / 2));
				continue;
			}
			// 양분 먹을 수 있음
			map[y][x] -= age; // 양분 감소
			tree.age++; // 나이 증가
			if (tree.age % 5 == 0) // 여릉메 성장 가능
				asc_age_queue.offer(tree);
			tmp_pQueue.offer(tree);

		} // 나무 순회 end
		return tmp_pQueue;
	}
}
반응형