3197번: 백조의 호수

입력의 첫째 줄에는 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1500. 다음 R개의 줄에는 각각 길이 C의 문자열이 하나씩 주어진다. '.'은 물 공간, 'X'는 빙판 공간, 'L'은 백조가 있는 공간으로 나타낸다.

www.acmicpc.net

IDEA

만약 문제가 "얼음이 전부 녹는 데 걸리는 시간은?" 이었다면 정말 간단히 풀 수 있는 문제다.

이 문제의 함정은 얼음이 녹을 때마다 백조가 만날 수 있는지 없는지 여부를 확인한다는 것에 있다.

여기서 '매일 녹여보고 확인해보면 되지 않을까?' 생각했지만 호수의 범위가 가로 1~1500칸, 세로 1~1500칸이다.

만약 이 방법을 사용한다면 최대 225만칸을 확인해야하는데 무조건 시간초과가 나올 것이다.

다른 분들의 풀이를 보면서 어떻게 접근해야하는지 알았다.

이 문제는 큐를 여러개 사용해야한다.

 

기존의 아이디어를 알고리즘화 하면 이렇게 된다.

1) 얼음을 녹이기 위한 BFS 큐를 하나 사용한다.

2) 한 수준의 탐색이 끝나면 백조가 만날 수 있는지 BFS 큐를 하나 더 사용한다.

3) 백조가 만나지 못했다면 1) 로 돌아간다.

 

여기서 마지막 탐색이 이루어진 위치, 그러니까 얼음을 녹이는 경우 물에서 얼음을 만났을 때,

백조의 경우 얼음을 만났을 때, 각각의 좌표를 기억해두고 다음 BFS를 그 시점에서 시작한다.

그렇다면 매번 번거롭게 처음부터 탐색을 하는 헛수고를 하지 않아도 된다.

탐색 범위가 좁다면 별 의미가 없겠지만 탐색 범위가 넓을수록, 탐색이 길어질수록 절약할 수 있는 시간은 훨씬 커진다.

따라서 매 탐색이 끝날 때마다 끝난 곳의 위치를 새로운 큐에 저장한다.

얼음을 녹이는 BFS, 백조를 찾는 BFS 각각 하나씩의 큐가 더 필요하다.

이렇게 총 4개의 큐를 생성하고 탐색을 실행한다.

매 탐색마다 시간을 1씩 증가시키고 백조를 만났다면 시간을 출력하고 코드를 종료한다.

 

SourceCode

using System;
using System.Collections.Generic;
public class Location
{
    public int x;
    public int y;
    public Location(int a, int b)
    {
        x = a;
        y = b;
    }
}
class Program
{
    static int R, C, day, r, c;
    static int[,] lake = new int[1500, 1500];
    static bool[,] wcheck = new bool[1500, 1500];
    static bool[,] scheck = new bool[1500, 1500];
    static Queue<Location> water = new Queue<Location>();
    static Queue<Location> Nwater = new Queue<Location>();
    static Queue<Location> swan = new Queue<Location>();
    static Queue<Location> Nswan = new Queue<Location>();
    static void Main()
    {
        var rc = Console.ReadLine().Split();
        R = int.Parse(rc[0]); C = int.Parse(rc[1]);
        for (int i = 0; i < R; i++)
        {
            var row = Console.ReadLine();
            for (int j = 0; j < C; j++)
            {
                if (row[j] == '.') lake[i, j] = 0;      // 0 : water
                else if (row[j] == 'X') lake[i, j] = 1; // 1 : glacier
                else lake[i, j] = -1;                   // -1: swan
            }
        }
        for (int i = 0; i < R; i++)
        {
            for (int j = 0; j < C; j++)
            {
                if (lake[i, j] < 1)
                {
                    water.Enqueue(new Location(i, j));
                    wcheck[i, j] = true;
                }
                if (lake[i, j] == -1)
                {
                    swan.Enqueue(new Location(i, j));
                    scheck[i, j] = true;
                }
            }
        }
        scheck[swan.Peek().x, swan.Peek().y] = false;
        swan.Dequeue();
        while (true)
        {
            while (Nwater.Count > 0)
            {
                r = Nwater.Peek().x; c = Nwater.Peek().y;
                if (lake[r, c] == 1) lake[r, c] = 0;
                water.Enqueue(new Location(r, c));
                Nwater.Dequeue();
            }                               // Nwater 빙하 녹이고
                                            // water  으로 큐 옮기기
            while (water.Count > 0)
            {
                r = water.Peek().x; c = water.Peek().y;
                if (r > 0 && !wcheck[r - 1, c])
                {
                    if (lake[r - 1, c] == 1) Nwater.Enqueue(new Location(r - 1, c));
                    else water.Enqueue(new Location(r - 1, c));
                    wcheck[r - 1, c] = true;
                }
                if (c > 0 && !wcheck[r, c - 1])
                {
                    if (lake[r, c - 1] == 1) Nwater.Enqueue(new Location(r, c - 1));
                    else water.Enqueue(new Location(r, c - 1));
                    wcheck[r, c - 1] = true;
                }
                if (r < R - 1 && !wcheck[r + 1, c])
                {
                    if (lake[r + 1, c] == 1) Nwater.Enqueue(new Location(r + 1, c));
                    else water.Enqueue(new Location(r + 1, c));
                    wcheck[r + 1, c] = true;
                }
                if (c < C - 1 && !wcheck[r, c + 1])
                {
                    if (lake[r, c + 1] == 1) Nwater.Enqueue(new Location(r, c + 1));
                    else water.Enqueue(new Location(r, c + 1));
                    wcheck[r, c + 1] = true;
                }                               // water  좌표 상하좌우 확인하고
                                                // 빙하 있으면 Nwater에 Enqueue,
                                                // 빙하 없으면  water에 Enqueue
                water.Dequeue();
            }
            while (Nswan.Count > 0)
            {
                r = Nswan.Peek().x; c = Nswan.Peek().y;
                swan.Enqueue(new Location(r, c));
                Nswan.Dequeue();
            }                               // Nswan에서 swan으로 큐 옮기기
            while (swan.Count > 0)
            {
                r = swan.Peek().x; c = swan.Peek().y;
                if (r > 0 && !scheck[r - 1, c])
                {
                    if(lake[r - 1, c] == -1)
                    {
                        Console.WriteLine(day);
                        return;
                    }
                    else if (lake[r - 1, c] == 1) Nswan.Enqueue(new Location(r - 1, c));
                    else swan.Enqueue(new Location(r - 1, c));
                    scheck[r - 1, c] = true;
                }
                if (c > 0 && !scheck[r, c - 1])
                {
                    if (lake[r, c - 1] == -1)
                    {
                        Console.WriteLine(day);
                        return;
                    }
                    else if (lake[r, c - 1] == 1) Nswan.Enqueue(new Location(r, c - 1));
                    else swan.Enqueue(new Location(r, c - 1));
                    scheck[r, c - 1] = true;
                }
                if (r < R - 1 && !scheck[r + 1, c])
                {
                    if (lake[r + 1, c] == -1)
                    {
                        Console.WriteLine(day);
                        return;
                    }
                    else if (lake[r + 1, c] == 1) Nswan.Enqueue(new Location(r + 1, c));
                    else swan.Enqueue(new Location(r + 1, c));
                    scheck[r + 1, c] = true;
                }
                if (c < C - 1 && !scheck[r, c + 1])
                {
                    if (lake[r, c + 1] == -1)
                    {
                        Console.WriteLine(day);
                        return;
                    }
                    else if (lake[r, c + 1] == 1) Nswan.Enqueue(new Location(r, c + 1));
                    else swan.Enqueue(new Location(r, c + 1));
                    scheck[r, c + 1] = true;
                }
                swan.Dequeue();
            }
            day++;
        }
    }
}

큐에 2개 이상의 값을 저장하는 방법을 새로 적용해봤다.

Queue<(int, int)> 와 같은 해괴한 표현보다는 훨씬 실용적이고 깔끔한 것 같다.

 

BFS에서 if문을 두 번씩 겹쳐썼는데 가독성이 영 별로인 것 같고 메모리, 시간 면에서 효율이 많이 떨어지는 것 같다.

더 깔끔한 방법을 찾아봐야겠다.

전라남도교육지원청