题解 P3191 【[HNOI2007]紧急疏散EVACUATE】

· · 题解

BFS + 二分答案 + 最大流

(算补充楼下的把)

【解题思路】

[1] 二分答案 mid (所有人安全撤离所需时间)。

[2] 通过最大流来判断逃生时间为 mid 时,所有人能否安全撤离,能则缩小边界:

  1. 将源点 S 向每个空地连一条容量为 1 的边,表示每个空地初始时有一个人;

  2. 因为每一秒钟只能有一个人移动到门的位置,我们要将每个门拆成 mid 个点,分别表示时刻为第 1 ~ mid 秒的门,并向汇点 T 连一条容量为 1 的边;

  3. 为了简化问题,在二分答案前我们先 BFS 求出每块空地到每个门所需时间(当然用SPFA也是可以的,其实这时两者效率相同),然后将每块空地与对应所需时间的门连边,容量为1(因为初始时每块空地上只有一个人);

  4. (这一点楼下没讲,但代码里有写)那么如果有一些人(人数大于等于1)进入任意一个时刻的门,那么这个时刻的这扇门对应的点必定有 1 的流量流入汇点,但可能会有多个人到达同一个门所需的时间相同,所以我们要让剩下的人等到下一秒再让其中一个人移动到门的位置,所以对于时刻为第 1 ~ mid - 1 秒的门,我们都要向时刻为下一秒的门连一条容量为无穷大的边(因为同一时刻到达门的人可能有任意多,且 mid >= 正确答案时,所有人所代表的流量都将流入汇点 T,所以这里就不多考虑人数了);

  5. 判断如此建图的最大流是否等于总人数(总的空地数),如果一直无法相等则说明所有人无法全部安全撤出,输出“impossible”。

【注意】

  1. 因为要拆点,空间问题比较棘手(下面的代码空间开的可能会比较大,也是从MLE中慢慢调出来的);

  2. 因为要跑多次最大流,源点汇点以及建边都要注意重置。

【AC代码】

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int Maxn = 0x3f3f3f3f;
const int L = 21, M = 1e6 + 5, N = 5e4 + 5;
const int dx[] = {-1, 1, 0, 0},
          dy[] = {0, 0, -1, 1};
int dis[N][L][L], h[N][2], G[L][L]; 
int lst[N], nxt[M], to[M], flw[M], cur[N], lev[N];
int n, m, T, E, R, l = 1, r = 1000, Ans = -1, src, des; 
bool vis[L][L]; char c[L][L];
template <class T> inline T Min(const T a, const T b) {return a < b? a : b;}
template <class T> inline void CkMax(T &a, const T b) {if (a < b) a = b;}
template <class T> inline void CkMin(T &a, const T b) {if (a > b) a = b;}
inline int get()
{
    char ch; bool f = false; int res = 0;
    while (((ch = getchar()) < '0' || ch > '9') && ch != '-');
    if (ch == '-') f = true;
     else res = ch - '0';
    while ((ch = getchar()) >='0' && ch <= '9')
        res = (res << 3) + (res << 1) + ch - '0';
    return f? ~res + 1 : res;
}
inline void put(int x)
{
    if (x < 0)
      x = ~x + 1, putchar('-');
    if (x > 9) put(x / 10);
    putchar(x % 10 + 48);
}
inline void add(const int x, const int y, const int z)
{
    nxt[++T] = lst[x]; lst[x] = T; to[T] = y; flw[T] = z;
    nxt[++T] = lst[y]; lst[y] = T; to[T] = x; flw[T] = 0;
}
inline bool Bfs()
{
    static int Qn, Q[N]; int x, y;
    for (int i = src; i <= des; ++i) cur[i] = lst[i], lev[i] = -1;
    Q[Qn = 1] = src; lev[src] = 0;
    for (int j = 1; j <= Qn; ++j)
    {
        x = Q[j];
        for (int i = lst[x]; i; i = nxt[i])
         if (flw[i] > 0 && lev[y = to[i]] == -1)
         {
             lev[y] = lev[x] + 1; Q[++Qn] = y;
             if (y == des) return true;
         }
    }
    return false;
}
inline int Dinic(const int x, const int fl)
{
    if (x == des) return fl;
    int Del, res = 0, y;
    for (int &i = cur[x]; i; i = nxt[i])
    if (flw[i] > 0 && lev[x] < lev[y = to[i]])
    {
        Del = Dinic(y, Min(flw[i], fl - res));
        if (Del)
        {
            flw[i] -= Del; flw[i ^ 1] += Del;
            res += Del; if (res == fl) break;
        }
    }    
    if (res != fl) lev[x] = -1;
    return res;
}
inline int MaxFlow()
{
    int res = 0;
    while (Bfs()) res += Dinic(src, Maxn);
    return res;
}
inline void Ready(const int s, const int px, const int py)
{
    memset(vis, false, sizeof(vis)); int x, y, t = 0, w = 1; 
    vis[px][py] = true; dis[s][px][py] = 0; h[w][0] = px; h[w][1] = py;
    while ((t++) < w)
     for (int i = 0; i < 4; ++i)
     {
        int tx = h[t][0] + dx[i], ty = h[t][1] + dy[i];
        if (tx < 1 || tx > n || ty < 1 || ty > m || vis[tx][ty] || c[tx - 1][ty - 1] != '.') continue;
        dis[s][tx][ty] = dis[s][h[t][0]][h[t][1]] + 1;
        h[++w][0] = tx; h[w][1] = ty; vis[tx][ty] = true;
     }
}
inline bool Judge(const int mi)
{
    src = 0; des = R + E * mi + 1; T = 1;
    memset(lst, 0, sizeof(lst));
    for (int i = 1; i <= n; ++i)
     for (int j = 1; j <= m; ++j)
      if (c[i - 1][j - 1] == '.') add(src, G[i][j], 1);
    for (int k = 1; k <= E; ++k)
     for (int i = 1; i <= n; ++i)
      for (int j = 1; j <= m; ++j)
       if (c[i - 1][j - 1] == '.' && dis[k][i][j] <= mi) 
           add(G[i][j], (k - 1) * mi + R + dis[k][i][j], 1);
    for (int i = 1; i <= E; ++i)
     for (int j = 1; j <= mi; ++j) 
     {    
        int tmp = (i - 1) * mi + R; add(tmp + j, des, 1);
        if (j != mi) add(tmp + j, tmp + j + 1, Maxn);
     }
    return MaxFlow() == R;
}
int main()
{
    memset(dis, Maxn, sizeof(dis));
    n = get(); m = get(); 
    for (int i = 0; i < n; ++i) scanf("%s", c[i]);
    for (int i = 1; i <= n; ++i)
     for (int j = 1; j <= m; ++j)
     if (c[i - 1][j - 1] == 'D') Ready(++E, i, j);
      else if (c[i - 1][j - 1] == '.') G[i][j] = ++R; 
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (Judge(mid)) Ans = mid, r = mid - 1;
         else l = mid + 1;
    } 
    if (Ans == -1) puts("impossible");
     else put(Ans);
    return 0;
}