SWEA 5656 - 벽돌 깨기

Updated:

Java

5656 번 - 벽돌 깨기

문제

N 개의 벽돌을 떨어트려 최대한 많은 벽돌을 제거하려고 한다.
N, W, H, 그리고 벽돌들의 정보가 주어질 때,
▶ 남은 벽돌의 개수를 구하라!

접근 방법

board의 가로 길이가 최대 12이고 구슬의 개수는 최대 4개이다.
따라서 0부터 W-1까지 구슬을 하나씩 다 떨어뜨려 보면서, 각 경우마다 남은 벽돌의 개수 중 제일 적은 값을 도출해낸다.
따라서, 떨어뜨릴 구슬의 위치를 정하는 총 경우의 수는 12^4가 된다.
이는 DFS을 통하여 중복순열을 사용한 것이다.

위치를 정한 뒤, 구슬을 떨어뜨려 벽돌을 깬다.
Queue를 사용하여 벽돌 하나를 깻을 때 연쇄적으로 깨지는 벽돌도 저장한다.
이후 Queue에서 다음 연쇄 깨질 벽돌을 꺼내여 다시 범위대로 퍼뜨려 본다.
즉 BFS와 비슷한 방식으로 이용한다.

한번 벽돌을 떨어뜨리고 연쇄 반응으로 벽돌을 제거하였다면, 공중에 벽돌에 떠있다.
따라서 공중에 떠있는 벽돌을 아래로 내려야한다.
각 열마다 탐색하여, deque에 벽돌을 저장하고 아래서부터 하나씩 꺼내어 아래로 몰아 넣는다.

코드

import java.io.*;
import java.util.*;

class Solution {
	static int result = 0, bCnt, n, m;
	static int[][] saved, map;
	static int[] sel;
	public static void main(String []args) throws Exception {  
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer stk;
    	int tc = stoi(br.readLine());
    	
    	for(int tidx = 1; tidx <= tc; tidx++) {
    		result = Integer.MAX_VALUE;
    		stk = new StringTokenizer(br.readLine()); 
    		
    		bCnt = stoi(stk.nextToken());
    		m = stoi(stk.nextToken());
    		n = stoi(stk.nextToken());
    		
    		map = new int[n][m];
    		saved = new int[n][m];
    		sel = new int[bCnt];
    		
    		for(int i = 0; i < n; i++) {
    			stk = new StringTokenizer(br.readLine());
    			for(int j = 0; j < m; j++) {
    				map[i][j] = stoi(stk.nextToken());
    				saved[i][j] = map[i][j];
    			}
    		}
    		// ------- 초기화 끝 ----------
    		
    		DFS(0);
    		
    		System.out.println("#" + tidx + " " + result);
    	}
    	
	}
	// n개의 떨어뜨릴 구슬의 위치를 정하는 DFS
	static void DFS(int lv) {
		if(lv == bCnt) {
			init();		// 초기 상태로 되돌린다.
			result = Math.min(result, cal());
			return;
		}
		for(int i = 0; i < m; i++) {
			sel[lv] = i;
			DFS(lv + 1);
		}
	}
	
	static int dy[] = {-1,1,0,0};
	static int dx[] = {0,0,-1,1};
	
	private static int cal() {
		
		int curX, curY = 0, y, x, ny, nx, range;
		int[] temp;
		Queue<int[]> queue = new LinkedList<int[]>();
		for(int idx = 0; idx < bCnt; idx++) {
			curX = sel[idx];
			curY = 0;
			// 처음 터질 벽돌의 위치를 찾는다.
			while(true) {
				if(!isIn(curY,curX) || map[curY][curX] != 0)
					break;
				curY++;
			}
			if(!isIn(curY,curX)) continue;
			
			queue.add(new int[] {curY, curX});
			
			// 벽돌 하나를 깨트리고, 그에 따른 연쇄 벽돌도 다 깨뜨린다.
			while(!queue.isEmpty()) {
				temp = queue.poll();
				y = temp[0];
				x = temp[1];
				range = map[y][x] - 1;
				map[y][x] = 0;
				
				// 4방향으로 범위 만큼 연쇄하여 터뜨린다
				for(int i = 0; i < 4; i++) {
					ny = y;
					nx = x;
					for(int j = 0; j < range; j++) {
						ny += dy[i];
						nx += dx[i];
						if(isIn(ny,nx) && map[ny][nx] != 0) {
							queue.add(new int[] {ny,nx});
						}
					}
				}
			}
			
			// 공중에 떠있는 벽돌을 다 내린다
			Deque<Integer> above = new ArrayDeque<>();
			for(int j = 0; j < m; j++) {
				for(int i = 0; i < n; i++) {
					if(map[i][j] != 0) {
						above.add(map[i][j]);
					}
					map[i][j] = 0;
				}
				int index = n - 1;
				while(!above.isEmpty()) {
					map[index--][j] = above.pollLast();
				}
			}
		}
		
		// 남아있는 벽돌의 개수를 센다
		int cnt = 0;
		for(int i = 0; i < n; i++) {
			for(int j = 0; j < m; j++) {
				if(map[i][j] != 0)
					cnt++;
			}
		}
		return cnt;
	}

	public static void init() {
		for(int i = 0; i < n; i++) {
			map[i] = saved[i].clone();
		}
	}
	
	// 주어진 범위 안에 있는가
    public static boolean isIn(int y, int x){
        if(y < 0 || y >= n || x < 0 || x >= m)
            return false;
        return true;
    }
	
	static int stoi(String str) {
    	return Integer.parseInt(str);
    }
}

총평

후기

굳굳

개선할 점