알고리즘 공부하기/백준

백준 16235 나무 재테크 JAVA

개발중인 감자 2023. 6. 7. 16:08

💡 나무 재테크

난이도 : 골드 3

 

 

16235번: 나무 재테크

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

www.acmicpc.net

 


🔎 후기 

초기 구현은 다소 쉬웠으나, 시간 복잡도를 줄이는 과정이 정말 어려웠다 ㅜㅜㅜ

 

💻 알게 된 사실

🔎 시간 복잡도를 줄이려면 반복문을 최소로 줄여야한다. 

🔎 봄 여름 가을 겨울 -> 1년 동안 4개의 큰 반복문을 도는 것 같지만, 

봄&여름, 가을&겨울 묶어서 반복문을 돌리면 훨씬 시간이 절약된다. 

🔎 나무의 나이가 가장 어린 순부터 처리를 해야하므로, 우선순위 큐를 이용하여 각 배열마다 나무의 나이를 자동으로 오름차순 정렬되도록 하였다. 

🔎 Java에서는 Scanner보다 BufferedReader를 쓰는 것이 시간이 더 절약된다. 

🔎 봄, 여름 반복문에서 (나이%5==0) 만족하는 나무의 갯수를 미리 세어 가을반복문에서 시간 절약하도록 하였다. 

 

😂 초기 구현에서는 봄,여름,가을,겨울 마다 함수를 만들어서 구현하였다. 하지만 당연히 시간초과가 났다..

봄&여름을 합해도 시간초과가 나고... 봄&여름&가을 합하니 값이 틀리고... 주절주절.. 여러 고생 끝에 완성..!

 

📌 결론적으로 반복문을 줄이기 위해서

나무의 나이를 담은 2차원 배열들 K번 반복하여 반복문을 최소화했다

 

✨ 코드 

 

import java.io.*;
import java.util.*;
import java.util.Map.Entry;

public class Main {	
	static int N,M,K;
	static int[][] new_A, A; 
	static int[][] cnt_v;
	static PriorityQueue[][] trees; 
	/*
	 * 1) 봄 
	 * 자신의 나이만큼 양분을 먹고, 나이 +1
	 *  -> 여러 나무가 하나의 칸에 있다면, 가장 어린나무부터 양분을 먹음. 
	 *  양분이 부족해, 자신의 나이만큼 양분을 못 먹으면 죽음. 
	 * 
	 * 2) 여름
	 * 봄에 죽은 나무가 양분으로 변함. 죽은 나무의나이 /2 -> 양분 추가. (소수점 아래 버림)
	 * 
	 * 3) 가을 
	 * 나무 번식 (단, 나무 % 5 == 0) 때만, 인접한 8개의 칸에 나이 1인 나무가 생김. 
	 * 
	 * 4) 겨울 
	 * 땅에 양분을 추가
	 */
	
	private static int spring_summer(int i, int j) {
		int cnt = 0;
		PriorityQueue<Integer> qu = trees[i][j];
		PriorityQueue<Integer> new_q = new PriorityQueue<>();
		if (qu.isEmpty()) return 0;
	
		while (!qu.isEmpty()) {	
			/* 여름 과정 */
			if (A[i][j] < qu.peek()) { //나무 죽이는 과정. 
				while(!qu.isEmpty()) {
					A[i][j] += qu.poll()/2;
				}
				break;
			}
			int age = qu.poll();
			A[i][j] -= age;
			if ((age+1) % 5 == 0) {
				cnt++;
			}
			new_q.add(age+1); //자신의 나이만큼 나무의 나이는 1 //새로운 나무를 넣음!.
			
		}
		
		trees[i][j] = new_q;
		return cnt;
	}
	
	private static void fall(int i, int j, int cnt) {
		int[] dx = {-1,-1,-1,0,0,1,1,1}, dy = {-1,0,1,-1,1,-1,0,1};
		
		for (int k = 0; k < 8; k++) {
			int xx = i+dx[k];
			int yy = j+dy[k];
			if (xx <= 0 || yy <= 0 || xx > N || yy > N) continue;
			
			PriorityQueue<Integer> tmp = trees[xx][yy];
			for (int kk = 0; kk < cnt; kk++) {
				tmp.add(1);
			}
			trees[xx][yy] = tmp;
		}
	}	
	public static void main(String[] args) throws Exception {		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		K = Integer.parseInt(st.nextToken()); //년도
		new_A = new int[N+1][N+1];
		A = new int[N+1][N+1]; //양분. 
		trees = new PriorityQueue[N+1][N+1]; //나무의 나이
		cnt_v = new int[N+1][N+1]; // 나이 % 5 만족하는 나무의 갯수 보관
		
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				PriorityQueue<Integer> qu = new PriorityQueue<>();
				trees[i][j] = qu;
				A[i][j] = 5; // 맨 처음 양분의 값. : 5. 
			}
		}
		
		
		for (int i = 1; i <= N; i++) {
			st = new StringTokenizer(br.readLine(), " ");
			for (int j = 1; j <= N; j++) {
				new_A[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		
		while (M-- > 0) {
			st = new StringTokenizer(br.readLine(), " ");
			int x = Integer.parseInt(st.nextToken());
			int y = Integer.parseInt(st.nextToken());
			int age = Integer.parseInt(st.nextToken());
			PriorityQueue<Integer> qu = trees[x][y];
			qu.add(age);
			trees[x][y] = qu;
			
		}
		
		while (K-- > 0) {
			//봄 여름 
			for (int i = 1; i <= N; i++) {
				for (int j = 1; j <= N; j++) {
					cnt_v[i][j]= spring_summer(i, j);
				}
			}
			
			//가을 겨울 
			for (int i = 1; i <= N; i++) {
				for (int j = 1; j <= N; j++) {
					A[i][j] += new_A[i][j]; //겨울 .
					if (cnt_v[i][j] == 0) continue;
					fall(i,j,cnt_v[i][j]);
					cnt_v[i][j] = 0;
				}
			}
			
		}
		
		int ans = 0;
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				ans += trees[i][j].size();
			}
		}
		System.out.println(ans);
	}
}