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

 

5547번: 일루미네이션

첫째 줄에 두 개의 정수 W와 H가 주어진다. (1 ≤ W, H ≤ 100) 다음 H줄에는 상근이네 집의 건물 배치가 주어진다. i+1줄에는 W개의 정수가 공백으로 구분되어 있다. j번째 (1 ≤ j ≤ w) 정수의 좌표는

www.acmicpc.net

 

문제풀이

BFS 문제인데, 어느정도 아이디어를 떠올려야 풀 수 있는 문제였습니다.

이 문제의 가장 큰 중요한 아이디어는,

1. 벽이 존재하지 않는 곳에서 BFS를 돌면서 정육면체 6방향 (좌, 좌상, 우상, 우, 우하, 좌하)을 확인하며 해당 방향에 벽이 존재하는지 확인하고, 벽의 개수를 result[][] 에 저장한뒤 마지막에 result에 저장된 모든 수를 더하면 변의 개수를 알 수 있습니다.

2. 정육각형이지만, 입력값은 직사각형 형태로 주어지기에 X와 Y값을 계산하는것이 포인트입니다.

//좌, 좌상, 우상, 우, 우하, 좌하  ( 시계방향 )
    	int OddDir[][] = { {0, -1}, { -1,  0}, {-1, 1}, {0, 1}, {1,  1}, {1, 0}};//홀수 행
        int EvenDir[][] = { {0, -1}, { -1, -1}, {-1, 0}, {0, 1}, {1, 0}, {1, -1}};//짝수 행

위 코드처럼 짝수와 홀수의 x와 y의 이동위치가 다르므로, 위처럼 이동방향을 정합니다.

추가로, 행과 열이 바뀌었으므로 일반적으로 행과 열을 구하듯이 하면안되고, 구한뒤에 행과 열을 대칭이동(y=x대칭) 시켜줘야합니다.

(문제를 풀다가 도중에 행과 열이 바뀐 값인것을 알아차려서 코드에는 단순히 H와 W를 반대로 입력하게 하여, 처리를 했습니다. 원래는 W와 H 순서로 받은뒤 new int[H+2][W+2] 로 하면됩니다.)

 

이 문제에서 어떻게 변의 개수를 구할까 고민했는데 이떄, 마지막 정육각형 벽들은 벽의 개수를 구하기 힘드므로,

map[H+2][W+2] 이렇게 추가로 벽을 만들어주어 BFS를 시작하면 무조건 map의 테두리에는 지나갈 수 있는 공간이 있기에 해당 아이디어를 떠올리는것이 중요했습니다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
	
	public static int W, H;
	public static int[][] map;
	public static int[][] result;
	static boolean[][] visited;

    public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer st = new StringTokenizer(br.readLine());
    	
    	//W : 행, H : 열
    	H = Integer.parseInt(st.nextToken());
    	W = Integer.parseInt(st.nextToken());
    	
    	
    	//외벽과 닿는 모든 면을 정육각형으로 둘러주기 위해 +2 를 합니다.
    	// 0 (흰색부분) 으로 둘러쌓이게 처리하는 것 입니다.
    	map = new int[W+2][H+2];
    	visited = new boolean[W+2][H+2];
    	result = new int[W+2][H+2];
    	for(int i=1;i<=W;i++) {
    		st = new StringTokenizer(br.readLine());
    		for(int j=1;j<=H;j++) {
    			map[i][j] = Integer.parseInt(st.nextToken());
    			if(map[i][j] == 1) {
    				visited[i][j] = true;
    			}
    		}
    	}
    	
    	BFS(0,0);
    	
    	int answer = 0;
    	for(int i=0; i<W+2; i++) {
    		for(int j=0;j<H+2;j++) {
    			answer += result[i][j];
    		}
    	}
    	
    	System.out.println(answer);
    } 
    
    public static void BFS(int startx, int starty) {
    	
        //좌, 좌상, 우상, 우, 우하, 좌하  ( 시계방향 )
    	int OddDir[][] = { {0, -1}, { -1,  0}, {-1, 1}, {0, 1}, {1,  1}, {1, 0}};//홀수 행
        int EvenDir[][] = { {0, -1}, { -1, -1}, {-1, 0}, {0, 1}, {1, 0}, {1, -1}};//짝수 행
    	Queue<Node> q = new LinkedList<>();
    	q.add(new Node(startx, starty));
    	visited[startx][starty] = true;
    	
    	while(!q.isEmpty()) {
    		Node temp = q.poll();
    		int x = temp.x;
    		int y = temp.y;
    		
    		for(int dir=0;dir<6;dir++) {
    			int nx = 0;
    			int ny = 0;
    			if(x %2 == 1) {
    				nx = x + OddDir[dir][0];
    				ny = y + OddDir[dir][1];
    			}else {
    				nx = x + EvenDir[dir][0];
    				ny = y + EvenDir[dir][1];
    			}
    			
    			if( nx < 0 || nx >= W + 2 || ny < 0 || ny >= H + 2) continue;
    			if(map[nx][ny] == 1) {
    				result[x][y] += 1;
    				continue;
    			}
    			//이미 간곳은 visited[nx][ny] =true로 수정하여 중복안되게 합니다.
    			if(visited[nx][ny] == true) continue;
    			visited[nx][ny] = true;
    			q.add(new Node(nx, ny));
    		}
    	}
    }
    
}

class Node{
	int x; 
	int y;
	public Node(int x, int y) {
		this.x=x;
		this.y=y;
	}
}

 

시간이 오래 걸리는 코드입니다.

위의 코드와 반대로, 벽 안의 0 을 1로 채워준뒤, 벽 기준으로 0 의 개수를 세어나갑니다.

위의 코드와 비교하여 시간이 더 소요됩니다. (각 점을 기준으로 모두 BFS를 실행하기 떄문입니다.) 위의 코드는 1번의 BFS로 모두 Counting 할 수 있습니다.

package Main;

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 {
	private static int N, W, H;
    private static int[][] arr;
    private static int answer = 0;
    private static int[][] dr = {
    		{-1, -1, 0, 0, 1, 1},
    		{-1, -1, 0, 0, 1, 1}
    };
    private static int[][] dc = {
    		{0, 1, -1, 1, 0, 1},
    		{-1, 0, -1, 1, -1, 0}
    };
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        W = Integer.parseInt(st.nextToken());
        H = Integer.parseInt(st.nextToken());
        arr = new int[H][W];
        for(int i=0;i<H; i++) {
        	st = new StringTokenizer(br.readLine());
        	for(int j=0;j<W; j++) {
        		arr[i][j] = Integer.parseInt(st.nextToken()); 
        	}
        }
        
        for(int i=0;i<H; i++) {
        	for(int j=0;j<W; j++) {
        		if(arr[i][j] == 0) {
        			//만약 나갈 수 있다면, 1로 감싸져있지 않으므로 0 으로 여전히 유지합니다.
        			//나갈 수 없다면 1로 감싸져있으므로 벽으로 인식합니다.
        			arr[i][j] = isBlockOut(i, j) == true ? 0 : 1;
        		}
        	}
        }
        
        boolean[][] visited;
        visited = new boolean[H][W];
        for(int i=0;i<H; i++) {
        	for(int j=0;j<W; j++) {
        		if(arr[i][j] == 1) {
        			answer += func(i, j, visited);
        		}
        	}
        }
        
        System.out.println(answer);
    }
    private static boolean isBlockOut(int sr, int sc) {
    	Queue<Node> q = new LinkedList<>();
    	q.offer(new Node(sr, sc));
    	boolean[][] visited = new boolean[H][W];
    	visited[sr][sc] = true;
    	while(!q.isEmpty()) {
    		Node temp = q.poll();
    		int or = temp.r;
    		int oc = temp.c;
			for(int dir =0 ; dir < 6; dir++) {
				int nr = or + dr[or%2][dir];
				int nc = oc + dc[or%2][dir];

				if(nr < 0 || nr >= H || nc < 0 || nc >= W) return true;
				if(visited[nr][nc] == true) continue;
				if(arr[nr][nc] == 1) continue;
				visited[nr][nc] = true;
				q.offer(new Node(nr, nc));
			}
    	}
    	return false;
    }
    
    private static int func(int sr, int sc, boolean[][] visited) {
    	int ret = 0;
    	//만약 짝수라면
		for(int dir = 0; dir < 6; dir++) {
			int nr = sr + dr[sr%2][dir];
			int nc = sc + dc[sr%2][dir];
			
			if(nr < 0 || nc < 0 || nr >= H || nc >= W) {
				ret += 1;
				continue;
			}
			else if( arr[nr][nc] == 0 ) {
				ret += 1;
			}
		}

		return ret;
    }
    
}
class Node{
	int r;
	int c;
	public Node(int r, int c) {
		this.r=r;
		this.c=c;
	}
}


//private static boolean isBlockOutDFS(int sr, int sc, boolean[][] visited) {
//
//boolean ret = false;
//visited[sr][sc] = true;
//for(int dir =0; dir<6; dir++) {
//	int nr = sr + dr[sr%2][dir];
//	int nc = sc + dc[sr%2][dir];
//	
//	//만약 밖에 나갈 수 있으면 밖과 연결되있으므로 true를 반환한다.
//	if(nr < 0 || nr >= H || nc < 0 || nc >= W) return true;
//	
//	//만약 벽이라면 무시한다.
//	if(arr[nr][nc] == 1) continue;
//	//만약 이미 방문한 적 있다면, 무시한다.
//	if(visited[nr][nc] == true) continue;
//	visited[nr][nc] = true;
//	
//	//만약 한개라도 true라면, 
//	ret = ret | isBlockOutDFS(nr, nc, visited);
//	visited[nr][nc] = false;
//}
//
////만약 한개라도 못나갔다면, 시작구간을 벽으로 채우면 된다.
//if(ret == false) {
//	arr[sr][sc] = 1;
//}
//visited[sr][sc] = false;
//return false;
//}

 

처음에 DFS를 활용해서 최대한 isBlockOutDFS의 작동시간을 줄일려고 했습니다.

하지만, 이 방법의 경우 만약 해당 dfs 방향에서 실패한 경우가 생긴다면, 그 재귀함수 호출이 끝나면서 호출한 부분 모두가 벽으로 칠해지면서 잘못된 연산이 생깁니다. 4가지 방향으로 각기 다른 방식으로 작동한다는점을 기억해야 합니다.

private static boolean isBlockOutDFS(int sr, int sc, boolean[][] visited) {

boolean ret = false;
visited[sr][sc] = true;
for(int dir =0; dir<6; dir++) {
	int nr = sr + dr[sr%2][dir];
	int nc = sc + dc[sr%2][dir];
	
	//만약 밖에 나갈 수 있으면 밖과 연결되있으므로 true를 반환한다.
	if(nr < 0 || nr >= H || nc < 0 || nc >= W) return true;
	
	//만약 벽이라면 무시한다.
	if(arr[nr][nc] == 1) continue;
	//만약 이미 방문한 적 있다면, 무시한다.
	if(visited[nr][nc] == true) continue;
	visited[nr][nc] = true;
	
	//만약 한개라도 true라면, 
	ret = ret | isBlockOutDFS(nr, nc, visited);
	visited[nr][nc] = false;
}

//만약 한개라도 못나갔다면, 시작구간을 벽으로 채우면 된다.
if(ret == false) {
	arr[sr][sc] = 1;
}
visited[sr][sc] = false;
return false;
}

+ Recent posts