https://www.acmicpc.net/problem/21611

 

21611번: 마법사 상어와 블리자드

마법사 상어는 파이어볼, 토네이도, 파이어스톰, 물복사버그, 비바라기 마법을 할 수 있다. 오늘 새로 배운 마법은 블리자드이고, 크기가 N×N인 격자에서 연습하려고 한다. N은 항상 홀수이고, (

www.acmicpc.net

코드설명

구현(Implementation) + 시뮬레이션(Simulation) 를 활용하는 문제입니다.

 

문제를 한 문장으로 설명해보면, 주어진 판에서 상어가 블리자드 마법을 사용하여 시뮬레이션하고, 마법에 의해 일어나는 구슬의 변화를 처리하는 프로그램입니다.

 

코드의 큰 로직입니다. 자세한 사항은 모두 주석에 달아두었습니다. 또한 각 과정마다 모두 디버깅을 위해 출력과정을 담아두었습니다.

  1. 블리자드 마법 적용 (blizardMagic 함수):
    • 주어진 방향과 거리에 따라 블리자드 마법을 시뮬레이션하여 Map에서 해당 위치의 구슬을 제거합니다.
  2. 빈 구슬 칸 채우기 (makeNewMap 함수):
    • 구슬이 제거된 후 빈 공간을 제외한 나머지 구슬들을 상어와 가까운 칸에서 순서대로 떨어뜨려 채웁니다.
  3. 4개 이상 연속 구슬 폭파 (bombBallWhichIsLongerThan4 함수):
    • Map을 순회하면서 4개 이상의 연속된 구슬이 있는 경우 폭파시킵니다.
    • 각 그룹은 구슬의 개수와 번호로 큐에 저장되고, 나중에 처리됩니다.
  4. 구슬 변화 (mutateToNewBall 함수):
    • 구슬 폭파 후 남은 구슬을 그룹화하여 구슬의 개수와 번호로 새로운 얼음판을 만듭니다.
    • 얼음판을 중앙에서 시작하여 반시계방향으로 탐색하며 변화를 적용합니다.
  5. 결과 출력:
    • 마법을 여러 번 시뮬레이션한 후 각 색의 구슬이 폭발한 횟수를 계산하여 출력합니다.

 

유의해야할점입니다.

첫번째는, 구슬의 개수를 셀때, 마지막에도 세주어야합니다. 처음에 94%에서 틀려서 원인을 찾아보니 마지막에 칸에서 벗어날때도 개수를 세주어야합니다.

while(level <= N) {

    //각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    for(int turn = 0; turn < 2; turn ++) {
        dir = changeDir(dir);
        //level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
        for(int move = 0 ; move < level; move ++) {
            r = r + dx[dir]; //이동
            c = c + dy[dir]; 
            moveCnt += 1;

            if(r < 0 || r > N || c < 0 || c > N) continue;
            if(map[r][c] != EMPTY_SPACE) {
                //만약 연속적인 볼과 다르다면, 
                if(continuousBallNumber != map[r][c]) {

                    //만약 다른데 갯수가 4개 이상이라면, 해당 큐는 새로운 큐에 들어가지 못한다. 즉 폭파한다는 뜻이다.
                    if(continuousCnt >= 4) {
                        answer[continuousBallNumber] += continuousCnt;
                        findFlag = true;
                        continuousCheckQ.clear();

                    }
                    //만약 4개 이하라면, 이전 큐에 넣어줍니다.
                    else if(continuousCnt < 4) {
                        while(!continuousCheckQ.isEmpty()) {
                            q.offer(continuousCheckQ.poll());
                        }
                    }
                    continuousCnt = 1;
                    continuousBallNumber = map[r][c];
                }

                //연속적인 볼이라면 갯수를 증가시킨다.
                else if(continuousBallNumber == map[r][c]) { //만약 이전 값과 같다면, 연속적인 볼이라면 하나씩 세어줍니다.
                    continuousCnt += 1;
                }

                continuousCheckQ.offer(map[r][c]); //먼저 넣는다.
            }


        }
    }
    level += 1;
}

//만약 다른데 갯수가 4개 이상이라면, 해당 큐는 새로운 큐에 들어가지 못한다.
if(continuousCnt >= 4) {
    answer[continuousBallNumber] += continuousCnt;
    findFlag = true;
    continuousCheckQ.clear();
}

 

두번쨰는, 반시계방향으로 순회하는것에 대한 로직입니다.

이 사항은 유의해야할점이라기보다는  이 문제 구현에 있어서 가장 중요한 코드이기에 읽어보면 좋습니다.

//반시계방향으로 순회.
public static void traverseByAntiColor() {
    int level = 1;
    int dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    int r = (N/2); int c = (N/2);
    int nr = r; int nc = c;
    int moveCnt = 1;
    while(level <= N) {

        //각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
        for(int turn = 0; turn < 2; turn ++) {
            dir = changeDir(dir);
            //level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
            for(int move = 0 ; move < level; move ++) {
                r = r + dx[dir]; //이동
                c = c + dy[dir]; 
                moveCnt += 1;

                if(r < 0 || r > N || c < 0 || c > N) continue; 
                System.out.print(" "+map[r][c]);
            }
            System.out.println(" ");
        }
        level += 1;
    }

}

 

세번째, 각 코드를 안에서부터 채워나갈떄 하나의 Map안에서 땡긴다기보다는 Queue를 활용해서 새로운 newMap을 반환시켜 한다면 편하게 구현할 수 있는 것 같습니다. 여러 풀이가 있지만, 저는 그렇습니다.

처음에는 DFS로 구현하는것도 생각해봤었는데 이렇게 For문으로 구현하는것이 훨씬 편한것 같습니다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	public static int N, M;
	public static int[][] map;
	public static int[] answer = new int[4];
	public static int[] dx = {-1, 1, 0, 0};
	public static int[] dy = {0, 0, -1, 1};
	public static int EMPTY_SPACE = 0;
	public static boolean bombBallWhichIsLongerThan4Flag = false;
    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());
    	
    	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());
//    			System.out.print(" "+map[i][j]);
    		}
//    		System.out.println();
    	}
//    	System.out.println();
//    	
    	for(int i=0; i<M;i++) {
    		st = new StringTokenizer(br.readLine());
    		int d = Integer.parseInt(st.nextToken());
    		int s = Integer.parseInt(st.nextToken());
    		
    		
    		//마법이 주어지고, 일련의 과정이 진행된다.
    		//이거 굳이 떙기지않고, 새로 만들어서 하나씩 채워가는것이 훨씬 편할것이다.
    		
    		//1. 마법에 의해 얼음파편이 떨어져 구슬이 파괴된다.
    		blizardMagic(d - 1, s);
//    		System.out.println("blizard Magic ==================================");
//    		for(int k=0;k<N;k++) {
//    			for(int h=0;h<N;h++) {
//    				System.out.print(" "+map[k][h]);
//    			}
//    			System.out.println();
//    		}
//    		System.out.println();
    		
    		
    		//2. 빈 구슬칸을 채워준다.
//    		System.out.println("makeNew Map ==================================");
    		map = makeNewMap();
//    		for(int k=0;k<N;k++) {
//    			for(int h=0;h<N;h++) {
//    				System.out.print(" "+map[k][h]);
//    			}
//    			System.out.println();
//    		}
//    		System.out.println();
    		
    		
    		bombBallWhichIsLongerThan4Flag = false;
    		//3. 4개이상 연속하는 구슬이있을경우 구슬이 4개 이상의 구슬이 모두 폭파된다.
    		while(bombBallWhichIsLongerThan4Flag == false) {
    			map = bombBallWhichIsLongerThan4();
//    			System.out.println("bombBallWhichIsLongerThan4 Map ==================================");
//        		for(int k=0;k<N;k++) {
//        			for(int h=0;h<N;h++) {
//        				System.out.print(" "+map[k][h]);
//        			}
//        			System.out.println();
//        		}
//        		System.out.println();
    		}
    		
    		
    		//4. 더이상 폭발할 구슬이 없다면, 구슬이 변화하는 단계가 된다. 연속하는 구슬은 하나의 그룹이라고한다.
    		// 하나의 그룹은 두개의 구슬 A와 B로 변화한다. 구슬 A의 번호는 그룹에 들어있는 구슬의 개수이다. B는 그룹을 이루고 있는 구슬의 번호이다.
    		// 구슬은 다시 구슬의 순서대로 1번칸부터 차례대로 A, B의 순서로 칸에 들어간다.
    		map = mutateToNewBall();
//    		System.out.println("mutateToNewBall Map ==================================");
//    		for(int k=0;k<N;k++) {
//    			for(int h=0;h<N;h++) {
//    				System.out.print(" "+map[k][h]);
//    			}
//    			System.out.println();
//    		}
//    		System.out.println();
    		
    		
    	}
//    	System.out.println(answer[0]);
    	System.out.println(answer[1] * 1 + answer[2] * 2 + answer[3] * 3);
//    	traverseByAntiColor();
    }
    
  //4개 이상 연속 구슬이 있을시에 구슬이 폭발한다.
    public static int[][] mutateToNewBall() {
    	Queue<Integer> q = new LinkedList<>();
    	Queue<Integer> continuousCheckQ = new LinkedList<>();
    	int level = 1;
    	int dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	int r = (N/2); int c = (N/2);
    	int moveCnt = 1;
    	
    	//연속적인 볼의 개수의 변수명.
    	//먼저 기존 얼음판을 빈칸으 빼고 복사해옵니다.
    	int continuousCnt = 0;
    	int continuousBallNumber = EMPTY_SPACE; //어처피 이전에 블리자드로 구슬이 파괴된 후에 구슬이 다시 채워지니 빈칸은 걱정안해도 될듯하다.

    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(map[r][c] != EMPTY_SPACE) {
    					//만약 연속적인 볼과 다르다면, 
    					if(continuousBallNumber != map[r][c]) {
    					
    						//만약 다르다면, continuousQ에 (그룹의 개수, 구슬의 번호) 를 차례대로(순서지켜야한다) 넣는다.
    						if(continuousBallNumber != EMPTY_SPACE) {
    							continuousCheckQ.offer(continuousCnt);
        						continuousCheckQ.offer(continuousBallNumber);
        						
        						//먼저 값을 넣어야한다. (맨 처음값같은경우는 어처피 값이 안들어가므로 상관없다.)
        						while(!continuousCheckQ.isEmpty()) {
        							q.offer(continuousCheckQ.poll());
        						}	
    						}
    						
    						continuousCnt = 1;
    						continuousBallNumber = map[r][c];
    					}
    					
    					//연속적인 볼이라면 갯수를 증가시킨다.
    					else if(continuousBallNumber == map[r][c]) { //만약 이전 값과 같다면, 연속적인 볼이라면 하나씩 세어줍니다.
    						continuousCnt += 1;
    					}
    				}
    				
    			}
    		}
    		level += 1;
    	}
		continuousCheckQ.offer(continuousCnt);
		continuousCheckQ.offer(continuousBallNumber);
		
		//먼저 값을 넣어야한다. (맨 처음값같은경우는 어처피 값이 안들어가므로 상관없다.)
		while(!continuousCheckQ.isEmpty()) {
			q.offer(continuousCheckQ.poll());
		}
    	
		
		
    	int[][] newMap = new int[N][N];
    	level = 1;
    	dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	r = (N/2); c = (N/2);
    	moveCnt = 1;
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(q.isEmpty()) return newMap;
    				newMap[r][c] = q.poll();
    			}
    		}
    		level += 1;
    	}
    	return newMap;
    }
    
    
    public static void blizardMagic(int d, int s) {
    	int r = (N/2), c = (N/2);
    	for(int i=0; i<s;i++) {
    		r = r + dx[d];
    		c = c + dy[d];
    		
    		//문제어 주어진 S는 (N-1)/2 이기에 범위 걱정없다.
    		map[r][c] = EMPTY_SPACE;
    	}
    	
    }
    
    public static int[][] makeNewMap(){
    	Queue<Integer> q = new LinkedList<>();
    	int level = 1;
    	int dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	int r = (N/2); int c = (N/2);
    	int moveCnt = 1;
    	
    	//먼저 기존 얼음판을 빈칸으 빼고 복사해옵니다.
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(map[r][c] != EMPTY_SPACE) q.offer(map[r][c]);
    				
    			}
    		}
    		level += 1;
    	}
    	
    	int[][] newMap = new int[N][N];
    	level = 1;
    	dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	r = (N/2); c = (N/2);
    	moveCnt = 1;
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(q.isEmpty()) return newMap;
    				newMap[r][c] = q.poll();
    			}
    		}
    		level += 1;
    	}
    	return newMap;
    }
    
    //4개 이상 연속 구슬이 있을시에 구슬이 폭발한다.
    public static int[][] bombBallWhichIsLongerThan4() {
    	Queue<Integer> q = new LinkedList<>();
    	Queue<Integer> continuousCheckQ = new LinkedList<>();
    	int level = 1;
    	int dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	int r = (N/2); int c = (N/2);
    	int moveCnt = 1;
    	
    	//연속적인 볼의 개수의 변수명.
    	//먼저 기존 얼음판을 빈칸으 빼고 복사해옵니다.
    	int continuousCnt = 0;
    	int continuousBallNumber = EMPTY_SPACE; //어처피 이전에 블리자드로 구슬이 파괴된 후에 구슬이 다시 채워지니 빈칸은 걱정안해도 될듯하다.
    	boolean findFlag = false;
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(map[r][c] != EMPTY_SPACE) {
    					//만약 연속적인 볼과 다르다면, 
    					if(continuousBallNumber != map[r][c]) {
    						
    						//만약 다른데 갯수가 4개 이상이라면, 해당 큐는 새로운 큐에 들어가지 못한다. 즉 폭파한다는 뜻이다.
    						if(continuousCnt >= 4) {
    							answer[continuousBallNumber] += continuousCnt;
    							findFlag = true;
    							continuousCheckQ.clear();
    							
    						}
    						//만약 4개 이하라면, 이전 큐에 넣어줍니다.
    						else if(continuousCnt < 4) {
    							while(!continuousCheckQ.isEmpty()) {
    								q.offer(continuousCheckQ.poll());
    							}
    						}
    						continuousCnt = 1;
    						continuousBallNumber = map[r][c];
    					}
    					
    					//연속적인 볼이라면 갯수를 증가시킨다.
    					else if(continuousBallNumber == map[r][c]) { //만약 이전 값과 같다면, 연속적인 볼이라면 하나씩 세어줍니다.
    						continuousCnt += 1;
    					}
    					
    					continuousCheckQ.offer(map[r][c]); //먼저 넣는다.
    				}
    				
    				
    			}
    		}
    		level += 1;
    	}
    	
    	//만약 다른데 갯수가 4개 이상이라면, 해당 큐는 새로운 큐에 들어가지 못한다.
		if(continuousCnt >= 4) {
			answer[continuousBallNumber] += continuousCnt;
			findFlag = true;
			continuousCheckQ.clear();
		}
		//만약 4개 이하라면, 이전 큐에 넣어줍니다.
		else if(continuousCnt < 4) {
			while(!continuousCheckQ.isEmpty()) {
				q.offer(continuousCheckQ.poll());
			}
		}
		if(findFlag == false) {
			bombBallWhichIsLongerThan4Flag = true;
			return map;
		}
		
    	int[][] newMap = new int[N][N];
    	level = 1;
    	dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	r = (N/2); c = (N/2);
    	moveCnt = 1;
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue;
    				if(q.isEmpty()) return newMap;
    				newMap[r][c] = q.poll();
    			}
    		}
    		level += 1;
    	}
    	return newMap;
    }
   
    
    //반시계방향으로 순회.
    public static void traverseByAntiColor() {
    	int level = 1;
    	int dir = 0; //처음에는 왼쪽방향부터 시작합니다.
    	int r = (N/2); int c = (N/2);
    	int nr = r; int nc = c;
    	int moveCnt = 1;
    	while(level <= N) {
    		
    		//각 2번씩 같은숫자만큼 방향을 바꿔가며 이동한다.
    		for(int turn = 0; turn < 2; turn ++) {
    			dir = changeDir(dir);
    			//level만큼 이동한다. level, level, level + 1, level + 1 ... 의 규칙성이 존재한다.
    			for(int move = 0 ; move < level; move ++) {
    				r = r + dx[dir]; //이동
    				c = c + dy[dir]; 
    				moveCnt += 1;
    				
    				if(r < 0 || r > N || c < 0 || c > N) continue; 
    				System.out.print(" "+map[r][c]);
    			}
    			System.out.println(" ");
    		}
    		level += 1;
    	}
    	
    }
    
    public static int changeDir(int dir) {
    	if(dir == 0) {
    		return 2;
    	}else if(dir == 1) {
    		return 3;
    	}else if(dir == 2) {
    		return 1;
    	}else if(dir == 3) {
    		return 0;
    	}
    	//여기까지 올일은없음.
    	return -1;
    }
    
}

+ Recent posts