알고리즘/백준

[백준-16985] Maaaaaaaaaze

또도닝 2021. 4. 21. 11:54

이 문제는 백준의 골드 3레벨 문제이다.

 

이 곳을 클릭하면 문제 화면으로 이동합니다.

 

이 문제는 구현해야 할 게 많아서 혹시 어디선가 틀리면 디버깅하기가 너무 막막했는데,

다행히 한번에 맞았습니다가 나왔다ㅠㅠㅠㅠ 감격...

 

하여튼, 이 문제는 이런 방식으로 풀어냈다.

1. 층 정하기(Floor, 순열)

2. 각 층의 방향 정해주기(direction, 중복순열)

3. 층이랑 방향대로 map에 넣어주기.(change)

4. 시작점인(0,0,0)이 1이라면 들어가기. (bfs)

 

이 과정을 계속해서 반복하면 된다. 

층은 총 5개이고, 각 층마다 4개의 방향을 가질 수 있으니 5!*4^5 개의 조합이 생긴다.

이 것들을 다 돌려주면 된다.

 

일단 Floor 함수에서는 boolean 배열과 int 배열을 이용해 5개의 판을 가지고 순열을 만들어준다.

만약 cnt가 5개가 되면, 하나의 세트가 완료되었다는 것이므로 각 층의 방향을 정해주기 위해 direction함수를 불러준다.

static void Floor(int cnt) {
		if(cnt == 5) { 
			direction(0);//각 층의 방향 정해주기
			return;
		}
		for(int i=0; i<5; i++) { //층 순서대로 나오게 해주는 함수
			if(!check[i]) {
				floor[cnt] = i;
				check[i] = true;
				Floor(cnt+1);
				check[i] = false;
			}			
		}
	}

dirction 함수에서는 int 배열을 이용해 중복 순열을 만들어주었다. 한 개의 판은 한 층에만 갈 수 있지만, 방향은 같아도 상관이 없기 때문이다. 방향의 세트가 완성되면, change 함수에 넣어서 map에 값을 넣어준다. 매 조합마다 초기화를 해주어야하는데, 초기화 함수를 새로 부르기 보다는 새로운 조합이 생길 때 마다 map 배열을 새로 할당 해 주었다.

그 후, 만들어진 map의 (0,0,0)이 들어갈 수 있는 곳이라면 bfs를 시작한다.

static void direction(int cnt){ //중복순열. 방향 골라주기
		if(cnt == 5) {
			map = new int[5][5][5]; //새로운 조합이 생길 때 마다 할당.
			change();//층이랑 방향 설정.
			if(map[0][0][0] == 1) //들어갈 수 있는 길이면 들어가기
				bfs(); //bfs돌리기
			return;
		}
		for(int i=0; i<4; i++) {
			dir[cnt] = i;
			direction(cnt+1);
		}
	}

change 함수에서는 3중 포문을 돌며, map의 0층부터 쌓아나간다. 

주석에 달아 놓았지만, dir이 0이면 그대로, 1이면 반시계 방향 90도, 2면 180도, 3이면 270도 회전한 모양이다.

층은 미리 계산해 두었던 floor[k] 배열을 이용해서 지정해주었다. 

 

마지막 bfs!

사실 보통의 bfs와 크게 다르지 않기는 한데, 이번에는 3차원 배열을 돌 것이기 때문에 전후좌우상하 로 나누어서 계산해주었다. map[k][i][j] 에서 상하로 갈 때는 k값이 바뀌고, 전후좌우로 갈 때는 i,j 값이 바뀌기 때문에 델타 배열을 6개로 만들어서 계산해주었다. 

static void bfs(){
		boolean[][][] visited = new boolean[5][5][5];
		Queue<Point> q = new LinkedList<>();
		visited[0][0][0] = true;
		q.add(new Point(0,0,0,0));
		
		while(!q.isEmpty()) {
			Point p = q.poll();
			
//			System.out.println(p.k+" "+p.r+" "+p.c+" "+p.cnt);
			if(p.k == 4 && p.r == 4 && p.c == 4) { //도착
				if(p.cnt < ans)
					ans = p.cnt;
				return;
			}
			
			for(int i=0; i<6; i++){ //전,후,좌,우,상,하
				if(i<4) { //전후좌우. 같은 층 안에서만 움직일 것
					int nr = p.r + dr[i];
					int nc = p.c + dc[i];
					
					if(nr<0|| nc<0|| nr>=5 || nc>= 5)
						continue;
					if(visited[p.k][nr][nc])
						continue;
					if(map[p.k][nr][nc] == 1) {
						visited[p.k][nr][nc] = true;
						q.add(new Point(p.k, nr,nc,p.cnt+1));
					}
				}
				else { //상하. 층 이동 함
					int nk = p.k + dr[i];
					
					if(nk<0 || nk>=5)
						continue;
					if(visited[nk][p.r][p.c])
						continue;
					
					if(map[nk][p.r][p.c] == 1) {
						visited[nk][p.r][p.c] = true;
						q.add(new Point(nk,p.r,p.c,p.cnt+1));
					}
				}
			}
		}
	}

 

이제 전체코드이다.

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Maaaaaaaaaze_16985 { //백준 골드3
	static int[][][] map;
	static int[][][] copy;
	static boolean[] check;
	static int[] floor;
	static int[] dir;

	static int[] dr = {-1,1,0,0,-1,1};
	static int[] dc = {0,0,-1,1,0,0};
	static int ans = 987654321;
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		copy = new int[5][5][5];
		
		for(int k=0; k<5; k++) { //입력
			for(int i=0; i<5; i++) {
				for(int j=0; j<5; j++) {
					copy[k][i][j] = sc.nextInt();
				}
			}
		}
		
		check = new boolean[5];
		floor = new int[5];
		dir = new int[5];
		Floor(0);
		
		ans = (ans == 987654321? -1:ans);
		System.out.println(ans);
	}
	
	static void Floor(int cnt) {
		if(cnt == 5) { 
			direction(0);//각 층의 방향 정해주기
			return;
		}
		
		for(int i=0; i<5; i++) { //층 순서대로 나오게 해주는 함수
			if(!check[i]) {
				floor[cnt] = i;
				check[i] = true;
				Floor(cnt+1);
				check[i] = false;
			}			
		}
	}
	
	static void direction(int cnt){ //중복순열. 방향 골라주기
		if(cnt == 5) {
			map = new int[5][5][5]; //새로운 조합이 생길 때 마다 할당.
			change();//층이랑 방향 설정.
			if(map[0][0][0] == 1) //들어갈 수 있는 길이면 들어가기
				bfs(); //bfs돌리기
			return;
		}
		for(int i=0; i<4; i++) {
			dir[cnt] = i;
			direction(cnt+1);
		}
	}
	
	static void change() {
		for(int k=0; k<5; k++) { //층별, 방향별 값 설정
			if(dir[k] == 0) { //원상태 그대로
				for(int i=0; i<5; i++) {
					for(int j=0; j<5; j++) {
						map[k][i][j] = copy[floor[k]][i][j];
					}
				}				
			}
			else if(dir[k] == 1) { //반시계방향 90도
				for(int i=0; i<5; i++) {
					for(int j=0; j<5; j++) {
						map[k][i][j] = copy[floor[k]][j][4-i];
					}
				}
			}
			else if(dir[k] == 2) { //반시계방향 180도
				for(int i=0; i<5; i++) {
					for(int j=0; j<5; j++) {
						map[k][i][j] = copy[floor[k]][4-i][4-j];
					}
				}
			}
			else if(dir[k] == 3){ //반시계방향270도
				for(int i=0; i<5; i++) {
					for(int j=0; j<5; j++) {
						map[k][i][j] = copy[floor[k]][4-j][i];						
					}
				}
			}
		}
	}
	
	static void bfs(){
		boolean[][][] visited = new boolean[5][5][5];
		Queue<Point> q = new LinkedList<>();
		visited[0][0][0] = true;
		q.add(new Point(0,0,0,0));
		
		while(!q.isEmpty()) {
			Point p = q.poll();
			
//			System.out.println(p.k+" "+p.r+" "+p.c+" "+p.cnt);
			if(p.k == 4 && p.r == 4 && p.c == 4) { //도착
				if(p.cnt < ans)
					ans = p.cnt;
				return;
			}
			
			for(int i=0; i<6; i++){ //전,후,좌,우,상,하
				if(i<4) { //전후좌우. 같은 층 안에서만 움직일 것
					int nr = p.r + dr[i];
					int nc = p.c + dc[i];
					
					if(nr<0|| nc<0|| nr>=5 || nc>= 5)
						continue;
					if(visited[p.k][nr][nc])
						continue;
					if(map[p.k][nr][nc] == 1) {
						visited[p.k][nr][nc] = true;
						q.add(new Point(p.k, nr,nc,p.cnt+1));
					}
				}
				else { //상하. 층 이동 함
					int nk = p.k + dr[i];
					
					if(nk<0 || nk>=5)
						continue;
					if(visited[nk][p.r][p.c])
						continue;
					
					if(map[nk][p.r][p.c] == 1) {
						visited[nk][p.r][p.c] = true;
						q.add(new Point(nk,p.r,p.c,p.cnt+1));
					}
				}
			}
		}
	}
	
	static class Point{
		int k,r,c,cnt;
		Point(int k, int r, int c, int cnt){
			this.k = k;
			this.r = r;
			this.c = c;
			this.cnt = cnt;
		}
	}

}

 

생각보다 빨리 잘 풀어내서 기분이 좋다:)

다른것도 이렇게 잘 풀리면 좋을텐데ㅠㅠ