Sangwon Coding

벽 부수고 이동하기 본문

알고리즘/DFS, BFS

벽 부수고 이동하기

SW1 2019. 11. 18. 20:53

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다. (1, 1)과 (N, M)은 항상 0이라고 가정하자.

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.

 

 

import java.util.*;

class Main {
	static int n;
	static int m;
	static int[][] board;
	static boolean[][] is_wall;
	static Queue<Dot> queue = new LinkedList<Dot>();
	static int[] dx = { 1, 0, -1, 0 };
	static int[] dy = { 0, 1, 0, -1 };
	static int answer = Integer.MAX_VALUE;
	static boolean find = false;

	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		m = scan.nextInt();

		board = new int[n][m];
		is_wall = new boolean[n][m];

		for (int i = 0; i < n; i++) {
			String str = scan.next();

			for (int j = 0; j < m; j++) {
				board[i][j] = str.charAt(j) - '0';
			}
		}

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (board[i][j] == 1) {
					queue.add(new Dot(i, j));
					is_wall[i][j] = true;
				}
			}
		}

		if (queue.isEmpty()) {
			bfs();
		} else {
			while (!queue.isEmpty()) {
				bfs();
			}
		}
		
		if (find == false) {
			System.out.println(-1);
		} else {
			System.out.println(answer);
		}
	}

	static void bfs() {
		int[][] temp = new int[n][m];
		boolean[][] temp_is_wall = new boolean[n][m];

		temp = board;
		temp_is_wall = is_wall;

		if (!queue.isEmpty()) {
			Dot wall = queue.poll();

			temp[wall.x][wall.y] = 0;
			temp_is_wall[wall.x][wall.y] = false;
		}

		Queue<Dot> q = new LinkedList<Dot>();
		q.add(new Dot(0, 0));

		while (!q.isEmpty()) {
			Dot d = q.poll();

			for (int i = 0; i < 4; i++) {
				int next_x = d.x + dx[i];
				int next_y = d.y + dy[i];

				if (next_x < 0 || next_y < 0 || next_x >= n || next_y >= m || temp[next_x][next_y] != 0)
					continue;

				q.add(new Dot(next_x, next_y));
				temp[next_x][next_y] = temp[d.x][d.y] + 1;
			}
		}

		if (temp[n - 1][m - 1] > 0 && temp[n - 1][m - 1] < answer) {
			answer = temp[n - 1][m - 1] + 1;
			find = true;
		}
	}
}

class Dot {
	int x;
	int y;

	Dot(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

 

처음에 제가 짠 코드인데 모든 벽들을 하나씩 부수는 경우마다 BFS를 써서 제일 적은 경우의 수를 찾아내는 방식인데 시간복잡도가 상당히 높아 시간초과가 떠서 접근 방식의 수정이 필요하다고 생각되었습니다.

 

그래서 생각해낸것이 큐에 단순한 현재 상태 좌표가 아닌 좌표와 부숴진 벽인지 아닌지 여부를 계속 넣어주고 visited[x][y][벽을 부수고 왔는지 여부]라는 변수 설정을 통해서 해결하였습니다.

 

import java.util.*;

class Main {
	static int n;
	static int m;
	static int[][] board;
	static int[][][] visited;
	static int answer = 0;
	static int[] dx = { 1, 0, -1, 0 };
	static int[] dy = { 0, 1, 0, -1 };

	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		m = scan.nextInt();

		board = new int[n][m];
		visited = new int[n][m][2];

		for (int i = 0; i < n; i++) {
			String str = scan.next();

			for (int j = 0; j < m; j++) {
				board[i][j] = str.charAt(j) - '0';
				visited[i][j][0] = 0;
				visited[i][j][1] = 0;
			}
		}

		bfs();

		if (answer == 0) {
			System.out.print(-1);
		} else {
			System.out.print(answer);
		}
	}

	static void bfs() {
		Queue<Dot> q = new LinkedList<Dot>();
		q.add(new Dot(false, 0, 0, 1));
		visited[0][0][0] = 1;
		visited[0][0][1] = 1;
		int count = 1;

		while (!q.isEmpty()) {
			Dot d = q.poll();

			for (int i = 0; i < 4; i++) {
				int next_x = d.x + dx[i];
				int next_y = d.y + dy[i];
				boolean broken = d.broken;
				count = d.count;

				if (next_x < 0 || next_y < 0 || next_x >= n || next_y >= m) {
					continue;
				}

				if (board[next_x][next_y] == 1 && broken == false && visited[next_x][next_y][1] == 0) { // 벽을 안뿌셨고 벽일때
					q.add(new Dot(true, next_x, next_y, count + 1)); //
					visited[next_x][next_y][1] = count + 1;
				}

				if (board[next_x][next_y] == 1 && broken == true) { // 벽을 뿌셨고 벽일때
					continue;
				}

				if (board[next_x][next_y] == 0 && broken == true && visited[next_x][next_y][1] == 0) { // 벽을 뿌셨고 길일때
					q.add(new Dot(true, next_x, next_y, count + 1)); 
					visited[next_x][next_y][1] = count + 1;
				}

				if (board[next_x][next_y] == 0 && broken == false && visited[next_x][next_y][0] == 0) { // 벽을 안뿌셨고 길일때
					q.add(new Dot(false, next_x, next_y, count + 1));
					visited[next_x][next_y][0] = count + 1;
				}
			}

			if (d.x == n - 1 && d.y == m - 1) {
				answer = count;
				break;
			}
		}
	}
}

class Dot {
	boolean broken;
	int x;
	int y;
	int count;

	Dot(boolean broken, int x, int y, int count) {
		this.broken = broken;
		this.x = x;
		this.y = y;
		this.count = count;
	}
}

 

문제출처

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

'알고리즘 > DFS, BFS' 카테고리의 다른 글

탈출  (0) 2019.11.19
안전 영역  (0) 2019.11.19
다리 만들기  (0) 2019.11.17
영역 구하기  (0) 2019.11.16
단지번호 붙이기  (0) 2019.11.15
Comments