문제 난이도 | 유형 |
🏅 플래티넘 5티어 | 큐, BFS, 시뮬레이션 |
문제
시간 제한 | 메모리 제한 | 제출 | 정답 | 맞힌 사람 | 정답 비율 |
1 초 | 256 MB | 21220 | 4462 | 2703 | 19.271% |
두 마리의 백조가 호수에서 살고 있었다. 그렇지만 두 마리는 호수를 덮고 있는 빙판으로 만나지 못한다.
호수는 행이 R개, 열이 C개인 직사각형 모양이다. 어떤 칸은 얼음으로 덮여있다.
호수는 차례로 녹는데, 매일 물 공간과 접촉한 모든 빙판 공간은 녹는다. 두 개의 공간이 접촉하려면 가로나 세로로 닿아 있는 것만 (대각선은 고려하지 않는다) 생각한다.
아래에는 세 가지 예가 있다.
...XXXXXX..XX.XXX ....XXXX.......XX .....XX..........
....XXXXXXXXX.XXX .....XXXX..X..... ......X..........
...XXXXXXXXXXXX.. ....XXX..XXXX.... .....X.....X.....
..XXXXX..XXXXXX.. ...XXX....XXXX... ....X......XX....
.XXXXXX..XXXXXX.. ..XXXX....XXXX... ...XX......XX....
XXXXXXX...XXXX... ..XXXX.....XX.... ....X............
..XXXXX...XXX.... ....XX.....X..... .................
....XXXXX.XXX.... .....XX....X..... .................
처음 첫째 날 둘째 날
백조는 오직 물 공간에서 세로나 가로로만(대각선은 제외한다) 움직일 수 있다.
며칠이 지나야 백조들이 만날 수 있는지 계산하는 프로그램을 작성하시오.
입력
입력의 첫째 줄에는 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1500.
다음 R개의 줄에는 각각 길이 C의 문자열이 하나씩 주어진다. '.'은 물 공간, 'X'는 빙판 공간, 'L'은 백조가 있는 공간으로 나타낸다.
출력
첫째 줄에 문제에서 주어진 걸리는 날을 출력한다.
예제 입력
8 17
...XXXXXX..XX.XXX
....XXXXXXXXX.XXX
...XXXXXXXXXXXX..
..XXXXX.LXXXXXX..
.XXXXXX..XXXXXX..
XXXXXXX...XXXX...
..XXXXX...XXX....
....XXXXX.XXXL...
예제 출력
2
풀이
두 가지 구현부를 나눌 수 있다. 얼음을 녹이는 부분과 백조가 서로 만날 수 있는지 확인하는 부분으로 나누어서 문제를 해결해 나가면 된다. 최악의 경우 1500 x 1500의 맵을 여러 번 BFS 해야 하기 때문에, 최적화를 하지 않는다면 시간 초과나 메모리 초과를 만날 수 있다.
현재 맵에서 두 마리 백조가 서로 만날 수 있는지 확인하고, 만날 수 없다면 물과 인접해있는 모든 얼음을 녹인 다음에 day를 증가하면서 진행하면 된다.
얼음을 녹이는 부분
function melting(waterQueue, liver, R, C) {
const next = new Queue();
while (!waterQueue.isEmpty()) {
const [x, y] = waterQueue.dequeue();
for (let i = 0; i < 4; i++) {
const nx = x + dx[i];
const ny = y + dy[i];
if (nx < 0 || ny < 0 || nx >= R || ny >= C) continue;
if (liver[nx][ny] === "X") {
liver[nx][ny] = ".";
next.enqueue([nx, ny]);
}
}
}
return next;
}
얼음을 녹이기 위해서는 처음에 모든 물의 위치가 저장된 큐가 있어야 한다. waterQueue가 비어있을 때까지 순회하면서 사방에 있는 얼음을 녹이면 된다. 중요한 것은 next 큐인데, 과거에 확인한 물 주위는 다시 확인할 필요가 없기 때문에 얼음이었다가 이번에 새로 물이 된 곳들을 다음날에 처리할 물의 위치로 담아서 리턴한다. 즉, next는 다음 melting 함수의 waterQueue가 된다.
백조가 서로 만날 수 있는지 확인하는 부분
//if duck found then return false, else then return object(true)
function notFoundDuck(duckQueue, liver, visited, R, C) {
const next = new Queue();
while (!duckQueue.isEmpty()) {
const [x, y] = duckQueue.dequeue();
for (let i = 0; i < 4; i++) {
const nx = x + dx[i];
const ny = y + dy[i];
if (nx < 0 || ny < 0 || nx >= R || ny >= C) continue;
if (visited[nx][ny]) continue;
if (liver[nx][ny] === ".") {
visited[nx][ny] = true;
duckQueue.enqueue([nx, ny]);
} else if (liver[nx][ny] === "X") next.enqueue([x, y]);
else if (liver[nx][ny] === "L") return false;
}
}
return next;
}
이 부분은 많이 어려웠다. 백조를 이동을 최적화를 하는 데 많은 자료도 찾아보고, 실제 집 앞의 하천을 계속 뚫어져라 찾아봤다. 크레이지 아케이드를 통해 원리를 알아보자.
결론부터 말하자면 백조가 BFS를 하면서 이전에 갔던 길은 다시 가지 않고, 얼음을 만나기 이전 위치를 next 큐에 담아서 다음날부터는 백조가 그 위치에서부터 BFS를 수행한다. 왼쪽 빨간 다오의 영역을 점점 늘려가면서 BFS를 수행하는 것이다.
즉, 위와 같이 얼음을 만나는 동그라미 위치에서부터 다음날 BFS를 시작하면 경로가 최적화된다. 이렇게 하기 위해서 백조의 위치를 담을 수 있는 큐가 하나 있어야 하고, 백조가 방문한 길을 체크하기 위해 2차원 배열도 만들어야 한다. 얼음을 녹이는 부분이 next 큐를 반환해서 다음 waterQueue로 들어온 것처럼, next 큐는 다음 duckQueue로 들어온다.
나는 한 마리의 백조만 움직이게 구현했다. 만약 백조가 다른 백조를 찾으면 false를 리턴한다. 왜냐하면 찾지 못하면 queue를 리턴하는데, 자바스크립트에서 object는 항상 true이기 때문에 백조를 찾은 경우에는 false를 리턴하도록 했다.
코드
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const dx = [-1, 1, 0, 0];
const dy = [0, 0, -1, 1];
let input = [];
rl.on("line", function (line) {
input.push(line);
}).on("close", function () {
const [R, C] = input.shift().split(" ").map(Number);
solution(
R,
C,
input.map((v) => [...v])
);
process.exit();
});
class Queue {
constructor() {
this.queue = [];
this.front = 0;
this.rear = 0;
}
enqueue(value) {
this.queue[this.rear++] = value;
}
dequeue() {
const value = this.queue[this.front];
delete this.queue[this.front++];
return value;
}
isEmpty() {
return this.front == this.rear;
}
}
function melting(waterQueue, liver, R, C) {
const next = new Queue();
while (!waterQueue.isEmpty()) {
const [x, y] = waterQueue.dequeue();
for (let i = 0; i < 4; i++) {
const nx = x + dx[i];
const ny = y + dy[i];
if (nx < 0 || ny < 0 || nx >= R || ny >= C) continue;
if (liver[nx][ny] === "X") {
liver[nx][ny] = ".";
next.enqueue([nx, ny]);
}
}
}
return next;
}
//if duck found then return false, else then return object(true)
function notFoundDuck(duckQueue, liver, visited, R, C) {
const next = new Queue();
while (!duckQueue.isEmpty()) {
const [x, y] = duckQueue.dequeue();
for (let i = 0; i < 4; i++) {
const nx = x + dx[i];
const ny = y + dy[i];
if (nx < 0 || ny < 0 || nx >= R || ny >= C) continue;
if (visited[nx][ny]) continue;
if (liver[nx][ny] === ".") {
visited[nx][ny] = true;
duckQueue.enqueue([nx, ny]);
} else if (liver[nx][ny] === "X") next.enqueue([x, y]);
else if (liver[nx][ny] === "L") return false;
}
}
return next;
}
function solution(R, C, liver) {
let duckQueue = new Queue();
let waterQueue = new Queue();
let isFounded = false;
let result = 0;
const visited = Array.from(Array(R), () => Array(C).fill(false));
//setting
for (let i = 0; i < R; i++) {
for (let j = 0; j < C; j++) {
if (liver[i][j] === ".") waterQueue.enqueue([i, j]);
else if (!isFounded && liver[i][j] === "L") {
liver[i][j] = ".";
visited[i][j] = true;
isFounded = true;
waterQueue.enqueue([i, j]);
duckQueue.enqueue([i, j]);
} else if (isFounded && liver[i][j] === "L") {
waterQueue.enqueue([i, j]);
}
}
}
//play logic
while (true) {
duckQueue = notFoundDuck(duckQueue, liver, visited, R, C);
if (!duckQueue) {
console.log(result);
break;
}
waterQueue = melting(waterQueue, liver, R, C);
result++;
}
}
'CS > 알고리즘' 카테고리의 다른 글
[백준 #1806] 부분합, 자바스크립트 풀이 (0) | 2022.10.11 |
---|---|
[백준 #1916] 최소비용 구하기, 자바스크립트 풀이 (0) | 2022.10.08 |
[백준 #7576] 토마토, 자바스크립트 풀이 (1) | 2022.10.08 |
[백준 #17144] 미세먼지 안녕!, 자바스크립트 풀이 (0) | 2022.10.07 |
[백준 #1197] 최소 스패닝 트리, 자바스크립트 풀이 (0) | 2022.09.30 |