POJ 3057 「Evacuation」은 벽으로 구분된 방의 사람들이 문마다 1초에 한 명씩 빠져나가는 문제입니다. X는 벽, .은 사람이 있는 빈칸, D는 문입니다. 매초 상하좌우로 한 칸 이동할 수 있고, 모두 나가는 최소 시간을 구합니다.
원문에서는 내부 한 칸에 여러 사람이 있을 수 있습니다. 제한은 문 통과에 적용됩니다. 문에 닿으면 이미 탈출했으므로 다른 구역으로 가는 통로로 쓰지 않습니다. 아래 재생은 사람의 이동 애니메이션이 아니라 문을 통과하는 시간표입니다.
현재 상태
풀이의 핵심
- BFS로 거리 구하기: 각 문에서 빈칸으로 탐색해 사람 p가 문 d에 도착하는 최소 시간 dist[d][p]를 구합니다.
- 시간 T로 질문 바꾸기: T초 안에 모두 탈출할 수 있는지 확인합니다. 오른쪽 정점은 (문, 1초), (문, 2초)…(문, T초)입니다.
- 이분 그래프 만들기: 사람 p는 dist[d][p] ≤ t인 (문 d, 시각 t) 슬롯에 연결됩니다. 도달 불가능한 문에는 연결하지 않습니다.
- DFS로 배정 조정하기: 슬롯이 이미 사용 중이면 기존 사람을 다른 슬롯으로 옮길 수 있는지 재귀적으로 찾습니다. 모든 사람이 서로 다른 슬롯을 얻으면 T초에 가능합니다.
- 최소 T 찾기: 가능한 T보다 큰 시간도 가능하므로 이분 탐색합니다.
여기서 DFS는 방 안의 이동 경로가 아니라 배정 그래프를 탐색합니다. 예를 들어 P1은 1초에 D1·D2 모두 가능하고 P2는 D1만 가능하다면, P1을 D1에 먼저 넣었어도 DFS로 P1→D2, P2→D1로 재배정할 수 있습니다.
문 1개 예제는 사람이 8명입니다. 가장 먼 사람의 최단거리는 5초지만, 문이 초당 한 명만 처리하므로 정답은 8초입니다. 「최소 시간 찾기」를 누른 뒤 T를 1 줄여 배정 인원이 어떻게 바뀌는지 확인하세요.
매칭 하나는 각 사람에게 도달 가능한 시각을 주고 문·시각의 중복을 막습니다. 내부에서 기다릴 수 있으므로 이 배정대로 탈출할 수 있습니다. 반대로 실제 탈출 일정도 이런 매칭이 되므로, 전원 매칭 여부와 T초 내 탈출 가능 여부가 일치합니다.
복잡도와 주의점
사람 수 P, 문 수 D, 지도 R × C, 가장 먼 사람의 가까운 문까지 거리를 L이라 합시다. 아무 문에도 도달하지 못하는 사람이 있으면 impossible입니다. 그 외에는 max(L, ceil(P/D))부터 U = L + P - 1까지 탐색합니다. 모두 L초 안에 가까운 문 앞에 도달한 뒤 최대 P명이 순서대로 나가면 U초면 충분합니다.
거리 계산은 O(DRC)입니다. T초 그래프의 간선 수를 ET라 하면 DFS 매칭 한 번은 O(P ET)이고 ET ≤ PDT입니다. 따라서 전체 시간 상한은 O(DRC + P EU log(U+1)), 추가 공간은 O(RC + DP + EU + DU)입니다.
가장 가까운 문으로만 보내거나 최장 최단거리만 답으로 쓰면 병목을 놓칩니다. 내부 칸에도 한 명 제한이 있는 다른 문제라면 이 모델은 적용되지 않습니다. 아래는 이 조건에 맞춰 작성한 C++ 풀이이며, 브라우저 실험은 작은 예제에 맞췄습니다.
직접 작성한 풀이 코드
C++ (C++98)
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
#include <utility>
#include <vector>
using namespace std;
struct Evacuation {
vector<pair<int, int> > people, doors;
vector<vector<int> > distance, edges;
vector<int> owner, seen;
int stamp;
bool augment(int person) {
for (int i = 0; i < (int)edges[person].size(); ++i) {
int slot = edges[person][i];
if (seen[slot] == stamp) continue;
seen[slot] = stamp;
if (owner[slot] == -1 || augment(owner[slot])) {
owner[slot] = person;
return true;
}
}
return false;
}
bool feasible(int limit) {
int p = people.size(), d = doors.size();
edges.assign(p, vector<int>());
for (int person = 0; person < p; ++person)
for (int door = 0; door < d; ++door)
if (distance[door][person] != -1)
for (int t = distance[door][person]; t <= limit; ++t)
edges[person].push_back((t - 1) * d + door);
owner.assign(d * limit, -1);
seen.assign(d * limit, 0);
stamp = 0;
for (int person = 0; person < p; ++person) {
++stamp;
if (!augment(person)) return false;
}
return true;
}
int solve(const vector<string>& grid) {
int h = grid.size(), w = grid[0].size();
for (int r = 0; r < h; ++r) for (int c = 0; c < w; ++c) {
if (grid[r][c] == '.') people.push_back(make_pair(r, c));
if (grid[r][c] == 'D') doors.push_back(make_pair(r, c));
}
int p = people.size(), d = doors.size();
if (p == 0) return 0;
if (d == 0) return -1;
distance.assign(d, vector<int>(p, -1));
int dr[] = {-1, 0, 1, 0}, dc[] = {0, 1, 0, -1};
for (int door = 0; door < d; ++door) {
vector<vector<int> > dist(h, vector<int>(w, -1));
queue<pair<int, int> > q;
q.push(doors[door]);
dist[doors[door].first][doors[door].second] = 0;
while (!q.empty()) {
int r = q.front().first, c = q.front().second;
q.pop();
for (int k = 0; k < 4; ++k) {
int nr = r + dr[k], nc = c + dc[k];
if (nr < 0 || nr >= h || nc < 0 || nc >= w) continue;
if (grid[nr][nc] != '.' || dist[nr][nc] != -1) continue;
dist[nr][nc] = dist[r][c] + 1;
q.push(make_pair(nr, nc));
}
}
for (int person = 0; person < p; ++person)
distance[door][person] = dist[people[person].first][people[person].second];
}
int farthest = 0;
for (int person = 0; person < p; ++person) {
int nearest = h * w + 1;
for (int door = 0; door < d; ++door)
if (distance[door][person] != -1)
nearest = min(nearest, distance[door][person]);
if (nearest == h * w + 1) return -1;
farthest = max(farthest, nearest);
}
int low = max(farthest, (p + d - 1) / d), high = farthest + p - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (feasible(mid)) high = mid;
else low = mid + 1;
}
return low;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
int cases;
cin >> cases;
while (cases--) {
int h, w;
cin >> h >> w;
vector<string> grid(h);
for (int r = 0; r < h; ++r) cin >> grid[r];
Evacuation solver;
int answer = solver.solve(grid);
if (answer < 0) cout << "impossible\n";
else cout << answer << '\n';
}
}