https://www.acmicpc.net/problem/9328
9328번: 열쇠
상근이는 1층 빌딩에 침입해 매우 중요한 문서를 훔쳐오려고 한다. 상근이가 가지고 있는 평면도에는 문서의 위치가 모두 나타나 있다. 빌딩의 문은 모두 잠겨있기 때문에, 문을 열려면 열쇠가
www.acmicpc.net
코드설명
시뮬레이션 + 구현 + BFS(너비우선탐색) + 비트마스크(BitMask) 문제입니다.
이번 문제에서 유의해야할점은, 최단거리를 구하는것이 아닌 훔칠 수 있는 문서의 최대 개수를 구하는 것입니다.
처음에는 최단거리를 구하는 줄알고, BFS에서 여러 경우를 고려하며 어떻게 열쇠를 가진상태에서 최단거리를 구하지라는 고민을 했었기에 각 Class마다 Key값을 두고 가지고 있는 키의 종류와 비교하며 진행하려했지만, 그렇게 할경우 Visited배열에 각 키값을 가지고있는경우의수를 모두 저장하며 진행해야하고, 이 문제가 원하는 값을 빠르게 도출하지 못합니다.
단순히 훔칠 수 있는 문서의 최대개수를 구하는경우, 문제가 훨씬 간단해집니다.
또한 처음에는 평면도에서 빌딩의 가장자리를 모두 돌면서 따로 BFS를 수행하거나 각 가장자리의 값들을 모두 Queue에 넣어서 실행하기보다는 빌딩의 크기를 늘려주어서 처리하면 됩니다.
이때 처음에는 CharToArrays를 사용해서 w+2 만큼 값을 넣어주지않고 w만큼 값을 넣어서 오류가 IndexOutof Error를 발견햇었습니다. 반복문으로 입력을 바꾸어서 처리하여 해결했습니다.
이번 문제를 풀며 문제의 접근을 최대한 간단하게 생각하는 방법을 생각하여야 코드의 복잡도가 훨씬 줄어들 수 있다는 것을 다시 느꼈습니다.
또한 구현중에 Visited[nr][nc]에 Key값을 저장해두는데 이를 통해 같은 이전에 같은 키값을 들고서 들어왔던경우는 다시 들어오지 않도록 합니다. 만약 새로운 Key값이라면 다시 방문하여 방문하지 못했던 곳을 방문할 수 있습니다.
이떄 Key값 같은경우 key[ 26 ] 으로 처리해도 되지만, 비트마스크도 활용해보고 싶어 사용했습니다.
만약 Visited[nr][nc][Keys] 로 하여 처리했다면, Key값이 비트마스킹을 거치며 2^26 값의 경우의 수가 나오기에 Overflow가 나올것입니다.
문제가 원하는 것이 무엇인지에 대하여 생각하여 간단하게 해결, 복잡도를 낮추는 방법에 대해서 고민해보면 좋을 문제입니다.
코드
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; public class Main { public static int N, M, K, T; public static int answer = 0; public static int[] arr; public static char[][] map; public static int[] dx = {-1,1,0,0}; public static int[] dy = {0,0,-1,1}; public static int h = 0, w = 0; public static int[][] visited; public static int key; public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); T = Integer.parseInt(st.nextToken()); while(T-- > 0) { answer = 0; st = new StringTokenizer(br.readLine()); h = Integer.parseInt(st.nextToken()); w = Integer.parseInt(st.nextToken()); map = new char[h+2][w+2]; for(int i=1;i<=h;i++) { st = new StringTokenizer(br.readLine()); String input = st.nextToken(); for(int j=1; j<=w;j++) { map[i][j] = input.charAt(j-1); } } key = 0; st = new StringTokenizer(br.readLine()); String inputKey = st.nextToken(); for(int i=0;i<inputKey.length();i++) { key |= (1 << inputKey.charAt(i) - 'a'); //비트마스킹. } BFS(); System.out.println(answer); } } public static void BFS() { Queue<Node> q = new LinkedList<>(); //Map의 Size가 map[h+2][w+2] 이므로 아무데서나 시작해도 괜찮다. visited = new int[h+2][w+2]; visited[0][0] = key; q.offer(new Node(0, 0)); while(!q.isEmpty()) { Node temp = q.poll(); for(int dir = 0; dir < 4; dir++) { int nr = temp.r + dx[dir]; int nc = temp.c + dy[dir]; if(nr < 0 || nr > h + 1 || nc < 0 || nc > w + 1 ) continue; if(map[nr][nc] == '*') continue; if(visited[nr][nc] == key) continue; if(map[nr][nc] >= 'A' && map[nr][nc] <= 'Z') { boolean flag = (key & (1 << map[nr][nc] - 'A')) > 0; //만약 0 이라면 key가 없는것입니다. if(flag == false) { //만약 key가 없는경우라면 continue continue; } } else if(map[nr][nc] >= 'a' && map[nr][nc] <='z') { //만약 소문자라면 key값입니다. key |= (1 << map[nr][nc] - 'a'); } else if(map[nr][nc] =='$') answer += 1; map[nr][nc] = '.'; //방문한곳은 . 로 바꿔놓습니다. visited[nr][nc] = key; q.offer(new Node(nr, nc)); } } } } class Node{ int r; int c; public Node(int r, int c) { this.r = r; this.c = c; } }