728x90
 

1431번: 시리얼 번호

첫째 줄에 기타의 개수 N이 주어진다. N은 50보다 작거나 같다. 둘째 줄부터 N개의 줄에 시리얼 번호가 하나씩 주어진다. 시리얼 번호의 길이는 최대 50이고, 알파벳 대문자 또는 숫자로만 이루어

www.acmicpc.net

 

위의 문제의 경우에는 세 가지의 정렬 기준으로 주어진 입력을 정렬해야 하는데

반복문을 돌면서 총 3번의 정렬을 수행할 수도 있겠지만

Comparator와 Comparable을 이용해 커스텀 정렬 기준으로 정렬을 할 수도 있다.

 

자바에서 제공하는 두 인터페이스들은 객체를 비교할 수 있는 상태로 만들기 위해 사용하는데

숫자나 문자 같은 경우는 기본적으로 손쉽게 비교가 가능했지만

객체는 서로 다른 특성을 가지고 있기 때문에 어떤 것으로 비교해야 할지

정해진 기준이 없기 때문에 두 인터페이스를 구현하여 객체를 어떻게 비교할지 정해주는 것이다.

 

Comparable

public class People implements Comparable<People> {

    int age;
    String name;

    public People(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public int compareTo(People o) {
        return this.age - o.age;
    }
}

 

우선 Comparable을 먼저 살펴보자면 구현은 위와 같이 할 수 있다.

 

객체를 어떤 기준으로 비교할지 compareTo 메서드를 구현하여 정의할 수 있는데

메서드의 파라미터로는 비교할 객체를 받아온다.

 

PeopleA와 비교하고자 하는 PeopleB를 파라미터로 받아오면 되는데

즉, 자기 자신과 비교하고자 하는 같은 타입의 상대 객체를 받아오면 된다.

 

compareTo 메서드의 리턴값에 따라 객체의 비교 결과가 달라지는데

양수인 경우에는 자기 자신이 더 큰 경우이고 0인 경우에는 같은 경우

음수인 경우에는 상대 객체가 더 큰 경우이다.

 

Comparator

public class CustomStringComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        return 0;
    }
}

public class CustomIntegerComparator implements Comparator<Integer> {

    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
}

 

위의 코드를 보면 알 수 있듯이 Comparator는 compare 메서드를 구현하는데

Comparable과 다른 점은 매개 변수로 비교할 두 값을 받아온다는 것이다.

 

즉, Comparator와 Comparable의 차이는

받아온 두 값을 비교하거나 자기 자신과 다른 객체를 비교하는 것이다.

Arrays.sort(arr, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return 0;
    }
});

 

별도의 클래스를 만들지 않고 익명 객체로 사용하거나

위와 같이 정렬 메서드의 정렬 기준으로 사용할 수도 있다.

 

'Java > Algorithms' 카테고리의 다른 글

[백준] 11652번 : 카드  (1) 2023.11.16
[백준] 1431번 : 시리얼 번호  (1) 2023.11.16
[백준] 1600번 : 말이 되고픈 원숭이  (1) 2023.11.15
[백준] 13549번 : 숨바꼭질 3  (1) 2023.11.14
[백준] 2146번 : 다리 만들기  (0) 2023.11.13
728x90

문제

 

 

접근 방식

숨바꼭질 + 나이트의 이동 문제가 합쳐진 느낌의 문제인데

기존에는 가로세로, 가로세로상하 이렇게 이동했다면

이번에는 가로세로 + 나이트의 이동 방식 8가지가 합쳐진 BFS를 해야 한다.

 

총 12가지 방향으로 매번 탐색을 수행해 주면서 0,0에서 배열의 끝까지

갈 수 있는 최단 경로를 찾아주면 되는데

주의해야 할 점은 나이트의 이동 방식은 정해진 횟수만큼만 사용 가능하다.

 

예제 테스트케이스가 통과되는 코드를 작성하는 것은 쉬웠지만

막상 제출을 하니 계속해서 실패하였는데

처음에는 가로세로 이동과 나이트의 이동 모두

방문을 기록하는 배열을 공유해 봤고

두 번째에는 각각의 배열을 나눠서 사용해 봤는데

둘 다 공통적으로 발생하는 문제점이 있었다.

 

같은 배열을 쓰던 안 쓰던 나이트의 이동으로 앞서간 후에

가로세로를 탐색하여 방문 처리를 해버리면

이후에 오는 이동들이 모두 막혀버린다.

x 0 0 0
1 0 0 0
0 0 1 1
0 1 0 0

x m 0 0
1 0 h 0
0 h 1 1
0 1 0 0

x m h 0
1 h h h
h h 1 1
0 1 0 0

 

 

x가 시작지점 m이 원숭이가 가로세로로 이동하는 경로 h가 말처럼 이동하는 경로일 때

위와 같이 BFS로 탐색을 진행하는 과정을 살펴보면

말의 이동경로가 앞서 가서 모두 방문처리를 해버리기 때문에

원숭이가 가야 할 길이 모두 사라져 버린다.

 

그래서 이를 해결하기 위해 기존 방문 여부를 기록하던 2차원 배열을

3차원 배열로 바꾸어 말로 이동한 횟수로 분리하여 방문 여부를 기록했다.

 

즉, 말로 이동한 횟수가 같을 때만 방문 여부를 공유하고

다른 경우에는 방문 여부를 공유하지 않는다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;

		int[] hx = {2, 2, 1, 1, -1, -1, -2, -2};
		int[] hy = {1, -1, 2, -2, 2, -2, 1, -1};

		int[] mx = {-1, 1, 0, 0};
		int[] my = {0, 0, -1, 1};

		int k = Integer.parseInt(br.readLine());

		st = new StringTokenizer(br.readLine());
		int w = Integer.parseInt(st.nextToken());
		int h = Integer.parseInt(st.nextToken());

		int[][] zoo = new int[h][w];
		boolean[][][] isVisited = new boolean[h][w][k + 1];

		int targetX = h - 1;
		int targetY = w - 1;

		for (int i = 0; i < h; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 0; j < w; j++) {
				zoo[i][j] = Integer.parseInt(st.nextToken());
			}
		}

		Queue<Monkey> monkeys = new LinkedList<>();
		monkeys.offer(new Monkey(0, 0, 0, 0));
		isVisited[0][0][0] = true;

		int min = -1;

		while (!monkeys.isEmpty()) {
			Monkey cur = monkeys.poll();

			int cx = cur.x;
			int cy = cur.y;
			int ch = cur.horse;
			int cm = cur.move;

			if (cx == targetX && cy == targetY) {
				min = cm;
				break;
			}

			if (ch < k) {
				for (int dir = 0; dir < 8; dir++) {
					int x = cx + hx[dir];
					int y = cy + hy[dir];

					if (x < 0 || x >= h || y < 0 || y >= w) continue;
					if (isVisited[x][y][ch + 1] || zoo[x][y] == 1) continue;

					isVisited[x][y][ch + 1] = true;
					monkeys.offer(new Monkey(x, y, ch + 1, cm + 1));
				}
			}

			for (int dir = 0; dir < 4; dir++) {
				int x = cx + mx[dir];
				int y = cy + my[dir];

				if (x < 0 || x >= h || y < 0 || y >= w) continue;
				if (isVisited[x][y][ch] || zoo[x][y] == 1) continue;

				isVisited[x][y][ch] = true;
				monkeys.offer(new Monkey(x, y, ch, cm + 1));
			}
		}

		System.out.println(min);
	}

	private static class Monkey {
		int x;
		int y;
		int horse;
		int move;

		public Monkey(int x, int y, int horse, int move) {
			this.x = x;
			this.y = y;
			this.horse = horse;
			this.move = move;
		}
	}
}

 

728x90

문제

 

 

접근 방식

뒤로 이동은 x - 1, 앞으로 이동은 x + 1, 순간이동은 x * 2를 해주면서

동생의 좌표까지 가주면 되는 간단한 문제인데 함정이 몇 개 숨어있다.

 

뒤와 앞으로 이동하는 경우에는 이동시간이 1씩 증가하지만

순간이동의 경우에는 이동하는데 시간이 걸리지 않는다.

 

그래서 순간이동은 아무리 써도 시간이 걸리지 않기 때문에

순간이동을 우선적으로 사용해 주고 앞뒤로 이동해주어야 한다.

 

이동 순서가 바뀌게 되면 결과가 달라지는 문제라

이 부분을 신경 써줘야 한다.

 

BFS를 사용해서 풀 때 한 가지 더 조심해야 할 부분이 있는데

큐는 들어온 순서대로 실행되기 때문에 최단경로가

텔레포트를 3 연속으로 사용하는 경우라도

텔레포트 > 텔레포트 > 텔레포트 순서대로 실행되는 것이 아니라

(텔레포트 > 앞으로 이동 > 뒤로 이동) * 3 순서대로 실행된다.

 

그래서 텔레포트를 3번 하는 과정에서 앞이나 뒤로 이동하다

k에 도달하는 경우가 있을 수 있기 때문에

k에 도달했다고 종료하는 것이 아니라

큐가 빌 때까지 탐색을 계속해줘야 한다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		int n = Integer.parseInt(st.nextToken());
		int k = Integer.parseInt(st.nextToken());

		boolean[] isVisited = new boolean[100001];

		Queue<Subin> q = new LinkedList<>();
		q.offer(new Subin(n, 0));

		int min = Integer.MAX_VALUE;

		while (!q.isEmpty()) {
			Subin cur = q.poll();

			int x = cur.x;
			int move = cur.move;
			isVisited[x] = true;

			if (x == k) {
				min = Math.min(min, move);
				continue;
			}

			int back = x - 1;
			int front = x + 1;
			int tp = x + x;

			if (tp < 100001 && !isVisited[tp]) q.offer(new Subin(tp, move));
			if (front <= k && !isVisited[front]) q.offer(new Subin(front, move + 1));
			if (back >= 0 && !isVisited[back]) q.offer(new Subin(back, move + 1));
		}

		System.out.println(min);
	}

	private static class Subin {
		int x;
		int move;

		public Subin(int x, int move) {
			this.x = x;
			this.move = move;
		}
	}
}

 

728x90

문제

 

 

접근 방식

1이 끊어지지 않고 최대로 이어진 곳들을 하나의 대륙이라고 볼 수 있고

각 대륙들을 서로 잇는 다리의 길이들을 구해야 한다.

 

일단 각 대륙들을 구분하면서 각 대륙의 외곽 지역을 큐들에 집어넣었는데

대륙의 외곽 지역은 바다와 맞닿아 있는 땅이라고 볼 수 있다.

 

외곽 지역을 모두 찾은 후에는 외곽을 순회하면서

바다만 찾아서 탐색을 하면서 다른 대륙을 마주친 경우

현재까지 이동한 거리를 이전의 다리 길이와 비교하여

더 작은 경우에는 값을 바꿔주었다.

 

풀이 자체는 쉽게 떠올렸는데 코드로 구현하고 보니 엄청 길어진...

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;

		int[] dx = {-1, 1, 0, 0};
		int[] dy = {0, 0, -1, 1};

		int n = Integer.parseInt(br.readLine());

		int[][] maps = new int[n][n];
		boolean[][] isVisited = new boolean[n][n];
		boolean[][] isWall = new boolean[n][n];

		int count = 0;

		for (int i = 0; i < n; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 0; j < n; j++) {
				int num = Integer.parseInt(st.nextToken());
				maps[i][j] = num;
				count = Math.max(count, num);
			}
		}

		int area = 1;
		Queue<Continent> continents;
		Queue<Continent> walls = new LinkedList<>();

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (!isVisited[i][j] && maps[i][j] == 1) {
					continents = new LinkedList<>();
					continents.offer(new Continent(i, j));
					isVisited[i][j] = true;
					maps[i][j] = area;

					while (!continents.isEmpty()) {
						Continent cur = continents.poll();

						for (int dir = 0; dir < 4; dir++) {
							int x = cur.x + dx[dir];
							int y = cur.y + dy[dir];

							if (x < 0 || x >= n || y < 0 || y >= n) continue;
							if (isVisited[x][y]) continue;
							if (maps[x][y] == 0) {
								if (!isWall[cur.x][cur.y]) {
									isWall[cur.x][cur.y] = true;
									walls.offer(cur);
								}
								continue;
							}

							isVisited[x][y] = true;
							maps[x][y] = area;
							continents.offer(new Continent(x, y));
						}
					}

					area++;
				}
			}
		}

		int distance = Integer.MAX_VALUE;
		Queue<Continent> queue;

		while (!walls.isEmpty()) {
			Continent wall = walls.poll();
			int continent = maps[wall.x][wall.y];
			queue = new LinkedList<>();
			isVisited = new boolean[n][n];
			wall.dist = 0;
			queue.offer(wall);
			isVisited[wall.x][wall.y] = true;

			while (!queue.isEmpty()) {
				Continent cur = queue.poll();

				for (int dir = 0; dir < 4; dir++) {
					int x = cur.x + dx[dir];
					int y = cur.y + dy[dir];

					if (x < 0 || x >= n || y < 0 || y >= n) continue;

					int next = maps[x][y];
					if (next == continent || isVisited[x][y]) continue;
					if (next != 0) {
						distance = Math.min(distance, cur.dist);
						continue;
					}
					if (cur.dist >= distance) {
						isVisited[x][y] = true;
						continue;
					}

					isVisited[x][y] = true;
					queue.offer(new Continent(x, y, cur.dist + 1));
				}
			}

		}
		System.out.println(distance);
	}

	private static class Continent {
		int x;
		int y;
		int dist;

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

		public Continent(int x, int y, int dist) {
			this.x = x;
			this.y = y;
			this.dist = dist;
		}
	}
}

 

728x90

문제

 

 

접근 방식

빙산을 매년 녹이면서 현재 빙산의 연결들이 끊어지지 않았는지 확인을 해주면 되는 간단한 문제다.

 

문제의 풀이 자체는 간단하게 떠올릴 수 있는데

PS는 항상 코드 작성이 문제다...

 

일단 입력을 받으면서 바다(0)가 아닌 1 ~ 10의 빙산들을 모두 큐에 넣어주었는데

이 때 시간을 같이 기록해줘야한다.

 

시간을 기록하는 이유는 만약 10개의 빙산 중 첫 번째 빙산이 녹았을 때

그 다음 빙산은 녹아버린 첫 번째 빙산을 바다로 취급하지 않아야 되는데

코드 상으로는 빙산이 하나씩 순서대로 녹아내리지만

실제로는 동시간대에 존재하는 모든 빙산이 한 번에 녹아야하기 때문이다.

 

즉, 같은 시간대의 빙산끼리는 서로에게 영향을 줄 수 없어야 하고

이를 위해서 큐에 넣을 객체는 빙산의 좌표와 시간대를 포함하게 했다.

 

이제 큐를 순회하면서 빙산의 가로세로 한 칸 범위에 바다가 존재할 때마다

빙산의 높이를 1씩 감소시켜준다.

 

녹아서 바다가 되지 않은 빙산들은 다시 큐에 넣고

다음 시간대에서 다시 녹게 해준다.

 

이제 마지막으로 해줄 것은 빙산이 끊어지지 않았는지 확인해주는 것인데

빙산을 모두 녹인 후에 시간대를 확인하는 것보단

해가 지날 때마다 녹지 않은 빙산을 순회하며 끊어지지 않았는지 확인해줬다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		int[] dx = {1, -1, 0, 0};
		int[] dy = {0, 0, -1, 1};

		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());

		Queue<Iceberg> icebergs = new LinkedList<>();

		int[][] north = new int[n][m];
		int[][] times = new int[n][m];

		for (int i = 0; i < n; i++) {
			st = new StringTokenizer(br.readLine());

			for (int j = 0; j < m; j++) {
				int sea = Integer.parseInt(st.nextToken());
				times[i][j] = -1;
				north[i][j] = sea;

				// 빙산인 경우 큐에 넣어줌
				if (sea != 0) {
					icebergs.offer(new Iceberg(i, j, 0));
				}
			}
		}

		// 해가 바뀐지 확인하기 위한 변수
		int start = 0;

		while (!icebergs.isEmpty()) {
			Iceberg cur = icebergs.poll();
			int cx = cur.x;
			int cy = cur.y;
			int time = cur.time;
			times[cx][cy] = time;

			// 해가 바뀐 경우
			if (start != time) {
				start = time;

				int count = icebergs.size();

				Queue<Iceberg> queue = new LinkedList<>();
				boolean[][] isVisited = new boolean[n][m];

				queue.offer(cur);
				isVisited[cx][cy] = true;

				// 빙산이 끊기지 않았는지 확인
				while (!queue.isEmpty()) {
					Iceberg iceberg = queue.poll();
					int ix = iceberg.x;
					int iy = iceberg.y;

					for (int dir = 0; dir < 4; dir++) {
						int x = ix + dx[dir];
						int y = iy + dy[dir];

						if (x < 0 || x >= n || y < 0 || y >= m) continue;
						if (north[x][y] < 1 || isVisited[x][y]) continue;

						isVisited[x][y] = true;
						queue.offer(new Iceberg(x, y, 0));
						count--;
					}
				}

				// 끊겼으면 몇 해가 지났는지 출력하면서 종료
				if (count != 0) {
					System.out.println(time);
					return;
				}
			}

			// 상하좌우 확인하며 바다인 경우 빙산을 녹임
			for (int dir = 0; dir < 4; dir++) {
				int x = cx + dx[dir];
				int y = cy + dy[dir];

				if (x < 0 || x >= n || y < 0 || y >= m) continue;
				if (times[x][y] <= time && north[x][y] == 0 && north[cx][cy] > 0 && north[cx][cy] != 0)
					north[cx][cy]--;

			}

			cur.time++;

			// 빙산이 녹지 않은 경우에만 큐에 넣음
			if (north[cx][cy] != 0) {
				icebergs.offer(cur);
			}
			times[cx][cy] = cur.time;
		}

		System.out.println(0);
	}

	private static class Iceberg {
		int x;
		int y;
		int time;

		public Iceberg(int x, int y, int time) {
			this.x = x;
			this.y = y;
			this.time = time;
		}
	}
	}
}

 

'Java > Algorithms' 카테고리의 다른 글

[백준] 13549번 : 숨바꼭질 3  (1) 2023.11.14
[백준] 2146번 : 다리 만들기  (0) 2023.11.13
[백준] 9466번 : 텀 프로젝트  (1) 2023.11.10
[백준] 2206번 : 벽 부수고 이동하기  (0) 2023.11.08
[백준] 5427번 : 불  (0) 2023.11.07
728x90

문제

 

 

접근 방식

정말 x 9999 어렵게 풀었다...

시간초과가 테스트케이스의 1%, 32% 83%에서 발생해서 해결하느라 애먹었다

 

문제 분류를 살펴보면 DFS지만 BFS가 아직은 더 편해서 BFS를 사용해서 풀었는데

우선 문제를 이해부터 하고 시작하겠다.

 

사이클을 찾아서 사이클이 존재하지 않는 경우가 몇 개인지 찾는 문제로

1 2 3 4 5 6 7
3 1 3 7 3 4 6

 

위 예제의 경우에는 3번은 자기 자신으로 바로 돌아와서 혼자 팀을 하는 사이클이 존재하고

4, 6, 7번은 4 > 7 > 6 > 4라는 사이클이 존재한다.

 

이외의 경우는 자기 자신으로 돌아오는 사이클이 존재하지 않는데

여기서 자기 자신으로 돌아오는 사이클이 없는 경우가 바로 팀에 속하지 않는 경우다.

 

 

발그림으로라도 설명을 해보자면 완전한 사이클을 이루는 경우를 제외한

다른 모든 경우가 사이클을 만나면 해당 경우를 사이클이 존재하지 않는다고 판단할 수 있는데

3은 혼자 자기 자신으로 돌아오는 하나의 완전한 사이클을 가지고 있고

1은 이러한 사이클에 끼어들 수 없으니 사이클을 만들 수 없게 되고

2도 당연히 사이클을 만들 수 없는 1을 마주치니 사이클을 만들 수 없다.

 

즉, 1 > 3 > 2 > 1이 되면 사이클이 될 수 있지만, 3에서 사이클이 존재하기 때문에

1과 2는 사이클을 만들 수 없게 되는 것이다.

 

정리하자면 이미 존재하는 사이클을 만나거나 사이클을 만들 수 없는 학생을 마주치면

해당 학생은 사이클이 될 수 없어서 팀을 꾸릴 수 없는 학생이고

자기 자신으로 돌아오는 경우에는 중간에 거친 모든 학생과 팀을 이룰 수 있다.

 

코드로 구현해야 하는 것은 크게 두 가지인데

 

사이클을 만나거나 사이클이 될 수 없는 학생과 마주친 경우

이전에 지나친 모든 학생들을 사이클이 될 수 없는 학생으로 기록하는 것 = 팀을 이룰 수 없는 학생

 

자기 자신으로 돌아오는 사이클이 존재하여

이전에 지나친 모든 학생들을 사이클이 될 수 있는 학생으로 기록하는 것 = 팀을 이룰 수 있는 학생

 

위 두개의 로직을 코드로 구현해주면 된다.

 

테스트 케이스가 빡새 시간이 생각보다 타이트한 문제로 탐색의 종료 시점과

건너뛸 부분을 잘 처리해줘야 한다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		StringTokenizer st;

		int t = Integer.parseInt(br.readLine());

		for (int i = 0; i < t; i++) {
			int n = Integer.parseInt(br.readLine());
			int count = n;
			st = new StringTokenizer(br.readLine());

			int[]students = new int[n];
			int[] arr = new int[n];

			boolean[] isCycle = new boolean[n];
			boolean[] isNotCycle = new boolean[n];

			for (int j = 0; j < n; j++) {
				int select = Integer.parseInt(st.nextToken()) - 1;

				students[j] = select;

				if (j == select) {
					isCycle[j] = true;
					count--;
				}
			}

			for (int j = 0; j < n; j++) {
				if (isCycle[j] || isNotCycle[j]) continue;

				int select = students[j];

				arr[0] = j;

				int size = 1;

				while (true) {

					if (isCycle[select] || isNotCycle[select]) {
						for (int k = 0; k < size; k++) {
							isNotCycle[arr[k]] = true;
						}
						break;
					}

					if (j == select) {
						for (int k = 0; k < size; k++) {
							isCycle[arr[k]] = true;
						}
						count -= size;
						break;
					}

					if (size >= count) {
						isNotCycle[j] = true;
						break;
					}

					arr[size++] = select;
					select = students[select];
				}
			}

			sb.append(count).append("\n");
		}

		System.out.println(sb);
	}
}

 

'Java > Algorithms' 카테고리의 다른 글

[백준] 2146번 : 다리 만들기  (0) 2023.11.13
[백준] 2573번 : 빙산  (1) 2023.11.12
[백준] 2206번 : 벽 부수고 이동하기  (0) 2023.11.08
[백준] 5427번 : 불  (0) 2023.11.07
[백준] 7562번 : 나이트의 이동  (0) 2023.11.07
728x90

문제

 

 

접근 방식

여태까지 풀어본 BFS 문제 중 가장 어려웠던 문제인 거 같다...

 

일단 문제에서 중요한 포인트는 출발지에서 도착지까지 가는 동안

벽은 한 개까지만 부술 수 있다는 것이다.

 

길을 이동할 때마다 벽을 부순 적이 있는지 여부를 어딘가에 기록해야 됐고

이를 사용해 벽을 부순 적이 없다면 벽을 부수고 갈 수 있고

벽을 부순 적이 있다면 벽을 만났을 때는 더 이상 못 가게 해야 했다.

 

그래서 기존에 좌표를 기록하던 클래스에 추가로

벽을 부순 적이 있는지 여부를 저장하는 변수를 추가했다.

 

이 정도까지만 구현하고 코드를 돌려봤을 때 테스트 케이스는 성공했지만

실제 제출 한 결과는 실패로 떴다.

 

방문 여부 기록을 벽을 부수지 않은 상태와 부순 상태가 공유하기 때문에

발생한 문제였는데 문제점을 인지하고 해결하는 것에 생각보다 애를 먹었다.

 

우선 부순 상태의 방문 여부와 부수지 않은 방문 여부를 기록할 배열을 만들고

각각의 처리를 따로 해주었다.

 

부순 상태의 방문에는 부순 상태에 방문 여부만 기록하고

부수지 않은 방문 여부는 기본 방문 여부를 기록한 후에

추가로 방문한 위치가 벽이라면 부순 상태의 방문 여부에도 기록했다.

 

이동 중에 다음 칸을 방문할 수 없는 경우는 아래와 같다.

  • 이미 방문한 적이 있는 경우
  • 부순 상태로 왔을 때 이미 부순 상태로 방문한 적이 있는 경우
  • 부순 상태로 왔을 때 벽인 경우

위 조건들에 해당하지 않는 경우만 방문할 수 있다.

 

이제 계속 탐색을 하면서 이동한 지점이 도착지점인지 확인해 주면 된다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int[] dx = {1, -1, 0, 0};
		int[] dy = {0, 0, -1, 1};

		String[] nm = br.readLine().split(" ");
		int n = Integer.parseInt(nm[0]);
		int m = Integer.parseInt(nm[1]);


		int[][] arr = new int[n][m];
		boolean[][] isVisited = new boolean[n][m];
		boolean[][] isBrokenVisited = new boolean[n][m];

		for (int i = 0; i < n; i++) {
			char[] input = br.readLine().toCharArray();
			for (int j = 0; j < m; j++) {
				arr[i][j] = input[j] - '0';
			}
		}

		if (n == 1 && m == 1) {
			System.out.println(1);
			return;
		}

		Queue<XY> queue = new LinkedList<>();

		queue.offer(new XY(0, 0, 1, false));
		isVisited[0][0] = true;
		isBrokenVisited[0][0] = true;

		while (!queue.isEmpty()) {
			XY cur = queue.poll();

			for (int dir = 0; dir < 4; dir++) {
				int x = dx[dir] + cur.x;
				int y = dy[dir] + cur.y;

				if (x < 0 || x >= n || y < 0 || y >= m) continue;
				if (isVisited[x][y] || (cur.isBroken && (arr[x][y] == 1 || isBrokenVisited[x][y]))) continue;

				if (x + 1 == n && y + 1 == m) {
					System.out.println(cur.moves + 1);
					return;
				}

				XY newXY = new XY(x, y, cur.moves + 1, cur.isBroken);

				if (cur.isBroken) {
					isBrokenVisited[x][y] = true;
				} else {
					isVisited[x][y] = true;
					if (arr[x][y] == 1)  {
						isBrokenVisited[x][y] = true;
						newXY.isBroken = true;
					}
				}

				queue.offer(newXY);
			}
		}

		System.out.println(-1);
	}

	private static class XY {
		int x;
		int y;
		int moves;
		boolean isBroken;

		public XY(int x, int y, int moves, boolean isBroken) {
			this.x = x;
			this.y = y;
			this.moves = moves;
			this.isBroken = isBroken;
		}
	}
}

 

'Java > Algorithms' 카테고리의 다른 글

[백준] 2573번 : 빙산  (1) 2023.11.12
[백준] 9466번 : 텀 프로젝트  (1) 2023.11.10
[백준] 5427번 : 불  (0) 2023.11.07
[백준] 7562번 : 나이트의 이동  (0) 2023.11.07
[백준] 7569번 : 토마토  (0) 2023.11.07
728x90

문제

 

 

접근 방식

 

4179번: 불!

입력의 첫째 줄에는 공백으로 구분된 두 정수 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1000 이다. R은 미로 행의 개수, C는 열의 개수이다. 다음 입력으로 R줄동안 각각의 미로 행이 주어진다. 각각의 문자

www.acmicpc.net

위 문제에 조건이 좀 추가된 문제로

불이 붙은 칸뿐만 아니라 곧 불이 붙을 칸도 피해서 빌딩을 탈출해야 한다.

if (isVisited[x][y] || building[x][y] == '#') continue;
if (isFired[x][y] != 0 && isFired[x][y] - 1 < cur.time + 1) continue;

 

코드로 표현하면 위와 같은데 벽이거나 이미 방문한 곳은 패스하고

불이 났거나 불이 붙을 예정인 칸도 패스해야 한다.

 

탈출 여부는 이동한 칸의 좌표가 인덱스의 범위를 벗어난 경우로

이 때의 시간을 출력해주면 된다.

 

풀이

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		StringBuilder sb = new StringBuilder();

		int n = Integer.parseInt(br.readLine());

		int[] dx = {1, -1, 0, 0};
		int[] dy = {0, 0, -1, 1};

		while (n-- > 0) {
			st = new StringTokenizer(br.readLine(), " ");

			int w = Integer.parseInt(st.nextToken());
			int h = Integer.parseInt(st.nextToken());

			char[][] building = new char[h][w];

			boolean[][] isVisited = new boolean[h][w];
			int[][] isFired = new int[h][w];

			Queue<XY> sgs = new LinkedList<>();
			Queue<XY> fires = new LinkedList<>();

			XY sg;
			XY fire;

			for (int i = 0; i < h; i++) {
				char[] input = br.readLine().toCharArray();
				for (int j = 0; j < w; j++) {
					char c = input[j];
					if (c == '@') {
						sg = new XY(i, j, 1);
						sgs.offer(sg);
						isVisited[i][j] = true;
					} else if (c == '*') {
						fire = new XY(i, j, 1);
						fires.offer(fire);
						isFired[i][j] = 1;
					}
					building[i][j] = c;
				}
			}

			while (!fires.isEmpty()) {
				XY cur = fires.poll();

				for (int dir = 0; dir < 4; dir++) {
					int x = cur.x + dx[dir];
					int y = cur.y + dy[dir];

					if (x < 0 || x >= h || y < 0 || y >= w) continue;
					if (isFired[x][y] != 0 || building[x][y] == '#') continue;

					isFired[x][y] = cur.time + 1;
					fires.offer(new XY(x, y, cur.time + 1));
				}
			}

			boolean isPossible = false;

			out:
			while (!sgs.isEmpty()) {
				XY cur = sgs.poll();

				for (int dir = 0; dir < 4; dir++) {
					int x = cur.x + dx[dir];
					int y = cur.y + dy[dir];

					if (x < 0 || x >= h || y < 0 || y >= w) {
						sb.append(cur.time).append("\n");
						isPossible = true;
						break out;
					}
					if (isVisited[x][y] || building[x][y] == '#') continue;
					if (isFired[x][y] != 0 && isFired[x][y] - 1 < cur.time + 1) continue;

					isVisited[x][y] = true;
					sgs.offer(new XY(x, y, cur.time + 1));
				}
			}

			if (!isPossible) {
				sb.append("IMPOSSIBLE").append("\n");
			}
		}

		System.out.println(sb);
	}

	private static class XY {
		int x;
		int y;
		int time;

		public XY(int x, int y, int time) {
			this.x = x;
			this.y = y;
			this.time = time;
		}
	}
}

 

+ Recent posts