본문 바로가기

알고리즘/코드트리

나무박멸[G4] (틀림 - 널포인터 이슈가 있는 듯)

목차

     

    최초로 풀은 문제. 

    난이도가 타 문제 대비 쉬운 편이라고 생각한다.

    처음으로 메서드 단위로 짤라서 모듈화를 통해 풀은 문제지만, 이름을 맞추고 쓸데없는 곳에 시간을 많이 들인 듯.

     

    https://www.codetree.ai/training-field/frequent-problems/problems/tree-kill-all/description?page=1&pageSize=20

     

    코드트리 | 코딩테스트 준비를 위한 알고리즘 정석

    국가대표가 만든 코딩 공부의 가이드북 코딩 왕초보부터 꿈의 직장 코테 합격까지, 국가대표가 엄선한 커리큘럼으로 준비해보세요.

    www.codetree.ai

     

    package codeTree;
    
    너무 깊숙히 코드를 예쁘게 다듬는데 몰입하지 말자
    
    /*
     나무박멸
     https://www.codetree.ai/training-field/frequent-problems/problems/tree-kill-all/description?page=1&pageSize=20
     12:57 start
     */
    
    import java.util.*;
    import java.io.*;
    
    public class G4_removeTree {
    	static int N, M, K, C, res;
    	static int[][] map;
    	static final int[] DR = new int[] {0, -1, 0, 1}; //LURD
    	static final int[] DC = new int[] {-1, 0, 1, 0};
    	
    	static final int[] CR = new int[] {-1, -1, 1, 1}; //cross 좌상 우상 좌하 우하
    	static final int[] CC = new int[] {-1, 1, -1, 1}; //cross
    	
    	static ArrayDeque<Jecho> jechoQueue = new ArrayDeque<>();
    	static ArrayDeque<Tree> treeQueue = new ArrayDeque<>(); //나무 보관
    	static ArrayDeque<Tree> tempTreeQueue = new ArrayDeque<>(); //아직 정확한 수치가 정해지지 않은 나무 임시 보관
    	
    	public static void main(String[] args) throws Exception {
    		//입력받기
    		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());
    		C = Integer.parseInt(st.nextToken());
    		
    		map = new int[N][N];
    		for(int i = 0; i < N; i++) {
    			st = new StringTokenizer(br.readLine());
    			for(int j = 0; j < N; j++) {
    				map[i][j] = Integer.parseInt(st.nextToken());
    			}
    		}
    		
    		solve(); //메인 로직
    		System.out.println(res);
    	}
    	
    	static void solve() {
    		while(M > 0) {
    //			log();
    			growTree(); //상하좌우 개수만큼 탐색하여 해당 나무 성장
    			makeTree(); //마찬가지로 상하좌우 중 0인 칸(이면서 제초제가 없는 칸)에 번식 진행		
    			saveTreeToMap(); //TreeQueue의 정보를 맵에 저장 : 번식 값 심기
    			removeLastJecho(); //이전 년도 제초제가 있다면(현재 존재하는 제초제가 있다면) -1 년 진행 후 0년 되면 제거
    			dropNowJecho(); //가장 많은 제거 가능 칸 제초제 투하
    			M--;
    		}
    	}
    	
    	static void dropNowJecho() {
    		//1. 가장 많은 제초 효과를 거둘 수 있는 장소 찾기
    		int jechoCount = -1; //나무 소멸 카운트
    		int jr = 9999, jc = 9999; //최고 소멸 카운트 위치
    		
    		for(int i = 0; i < N; i++) {
    			for(int j = 0; j < N; j++) {
    				//여기 뿌렸을때 제거되는 나무
    				int nowCount = dropNowJecho_checkMaxJechoCount(i, j);
    				
    				//1. 최고 기록 갱신 || 같을때 행 우선 || 같을때 열 우선
    				if(jechoCount < nowCount || (jechoCount == nowCount && i < jr) || (jechoCount == nowCount && i == jr && j <= jc)) {
    					jechoCount = nowCount;
    					jr = i; jc = j;
    				}
    			}
    		}
    		
    		//2. 해당 장소에 제초제 투하 후, 뿌려지는 사이드이펙트까지 고려
    		//2-1 일단 해당 장소 제초
    		res += map[jr][jc]; //점수
    		map[jr][jc] = 0;
    		jechoQueue.add(new Jecho(jr, jc, C)); //제초제
    		
    		//4방향 전파 로직
    		for(int i = 0; i < 4; i++) {
    			int val = 1; //가중치
    			while(true) {
    				if(val > K) break; //제초제 범위 이상임
    				int nr = jr + (val * CR[i]);
    				int nc = jc + (val * CC[i]);
    				
    				//장애물이거나, 나무가 없거나, 맵밖
    				if(cantGo(nr, nc) || map[nr][nc] <= 0) {
    					if(cantGo(nr, nc)) break;
    					if(map[nr][nc] == 0) jechoQueue.add(new Jecho(nr, nc, C)); //일단 제초제를 뿌리긴 해야함
    					break;
    				}
    								
    				res += map[nr][nc]; //점수
    				map[nr][nc] = 0; //나무 제거
    				jechoQueue.add(new Jecho(nr, nc, C)); //제초제
    				val++; //다음 위치로
    			}
    		}
    		
    	}
    	
    	static int dropNowJecho_checkMaxJechoCount(int r, int c) {
    		if(map[r][c] <= 0) return 0; //해당 위치는 아무것도 없음
    		int count = map[r][c]; //해당 위치 일단 제거
    		
    		
    		//4방향 전파 로직
    		for(int i = 0; i < 4; i++) {
    			int val = 1; //가중치
    			while(true) {
    				if(val > K) break; //제초제 범위 이상임
    				int nr = r + (val * CR[i]);
    				int nc = c + (val * CC[i]);
    				
    				//장애물이거나, 나무가 없거나, 맵밖
    				if(cantGo(nr, nc) || map[nr][nc] <= 0) {
    					break;
    				}
    								
    				count += map[nr][nc]; //제거되는 나무들
    				val++; //다음 위치로
    			}
    		}
    		
    		return count;
    	}
    	
    	static void removeLastJecho() {
    		//제초제 큐 안에 있는 년도 줄이기
    		int size = jechoQueue.size();
    		while(size > 0) {
    			Jecho now = jechoQueue.poll();
    			now.year -= 1; //1년 지남
    			
    			if(now.year == 0) {
    				size--;
    				continue; //해당 제초제는 만료됨
    			}
    			jechoQueue.offer(now);
    			size--;
    		}
    	}
    	
    	static void makeTree() {
    		//모든 좌표 순회하며 나무일 경우 로직 실행
    		for(int i = 0; i < N; i ++) {
    			for(int j = 0; j < N; j++) {
    				if(map[i][j] > 0) makeTree_checkMakeTree(i, j);
    			}
    		}
    	}
    	
    	static void saveTreeToMap() {
    		while(!treeQueue.isEmpty()) {
    			Tree t = treeQueue.poll();
    			if(growTree_growThisPlace_isJecho(t.r, t.c)) continue;
    			map[t.r][t.c] += t.grow; //해당 좌표에 나무 추가
    		}
    	}
    	
    	static void makeTree_checkMakeTree(int r, int c) {
    		//1. 번식이 가능한 칸 체크, tempTreeQueue에 일단 나무들 좌표가 저장되긴 했음. (수치는 저장 안됨 좌표만)
    		int count = makeTree_checkMakeTree_countNearBy(r, c);
    		
    		//2. tempTreeQueue에서 treeQueue로 이관하며 번식 정보 추가 : 결국 새로 생긴 나무들이 트리큐에 저장됨
    		if(count == 0) return; //번식 안함
    		int grow = map[r][c] / count; //나무 번식 수치
    		makeTree_checkMakeTree_addTreeQueue(grow);
    	}
    	
    	static void makeTree_checkMakeTree_addTreeQueue(int grow) {
    		while(!tempTreeQueue.isEmpty()) {
    			Tree tree = tempTreeQueue.poll();
    			tree.grow = grow;
    			treeQueue.add(tree);
    		}
    	}
    	
    	static int makeTree_checkMakeTree_countNearBy(int r, int c) {
    		int count = 0;
    		FORWAY:
    		for(int i = 0; i < 4; i++) {
    			int nr = r + DR[i];
    			int nc = c + DC[i];
    			if(cantGo(nr, nc) || map[nr][nc] != 0) continue; //맵밖이거나, 공터가 아닌 경우
    			
    			//제초제가 없을 경우도 판정, 제초제 큐 사이즈만큼 반복하여 해당 위치가 제초제 뿌린 것인지 판정
    			int size = jechoQueue.size();
    			for(int j = 0; j < size;j++) {
    				Jecho now = jechoQueue.poll();
    				if(nr == now.r && nc == now.c) { //만약 제초제 좌표와 현재 좌표가 똑같다면, 그 곳은 카운팅할 수 없음
    					jechoQueue.add(now); //다시 집어넣기
    					continue FORWAY;
    				}
    				jechoQueue.add(now); //다시 집어넣기
    			}
    			
    			count++; //점수 추가
    			tempTreeQueue.add(new Tree(nr, nc, 0)); //아직 정확한 value를 모름
    		}
    		return count;
    	}
    	
    	static void growTree() {
    		//인접한 네개의 칸 중 나무가 있는 칸만큼 성장
    		for(int i = 0; i < N; i++) {
    			for(int j = 0; j < N; j++) {
    				//나무가 있는 곳 탐색
    				if(map[i][j] > 0) growTree_growThisPlace(i, j);
    			}
    		}
    	}
    	
    	static void growTree_growThisPlace(int r, int c) {
    		//해당 스팟의 4방 탐색 후 성장
    		int count = 0;
    		for(int i = 0; i < 4; i++) {
    			int nr = r + DR[i];
    			int nc = c + DC[i];
    			if(cantGo(nr, nc) || map[nr][nc] <= 0) continue;
    			if(growTree_growThisPlace_isJecho(nr, nc)) continue; //제초제가 있는지 확인
    			count++;
    		}
    		
    		map[r][c] += count; //나무 성장
    	}
    	
    	static boolean growTree_growThisPlace_isJecho(int r, int c) {
    		int size = jechoQueue.size();
    		while(size > 0) {
    			Jecho now = jechoQueue.poll();
    			if(r == now.r && c == now.c) {
    				jechoQueue.add(now);
    				return true;
    			}
    			jechoQueue.add(now);
    			size--;
    		}
    		return false;
    	}
    
    	static boolean cantGo(int r, int c) {
    		if(r < 0 || c < 0 || r >= N || c >= N) return true;
    		return false;
    	}
    	
    	static void log() {
    		for(int i = 0; i < N; i++) {
    			for(int j = 0; j < N; j++) {
    				System.out.print(map[i][j] + "\t");
    			}
    			System.out.println("");
    		}
    		System.out.println("-----------");
    	}
    }
    
    
    
    class Tree {
    	int r, c, grow;
    	public Tree(int r, int c, int grow) {
    		this.r = r;
    		this.c = c;
    		this.grow = grow;
    	}
    }
    
    
    class Jecho {
    	int r, c, year;
    	public Jecho(int r, int c, int year) {
    		this.r = r;
    		this.c = c;
    		this.year = year;
    	}
    }

    '알고리즘 > 코드트리' 카테고리의 다른 글

    팩맨[G1]  (1) 2023.11.02
    예술성[G3]  (0) 2023.11.02
    코드트리빵[G2]  (0) 2023.11.02
    싸움땅[G2]  (0) 2023.11.02
    메이즈 러너[G3] [실패]  (0) 2023.11.02