Solution -「HNOI」EVACUATE

Sol.

可以发现人的移动除了不能穿墙以外没有别的限制。也就是说人的移动多半不是解题的突破口。

接下来会发现出口的限制很强,即出口每个时刻只能允许一个人出去。

每个时刻?

不难想到对于每一个时刻每一个门,我们单独考虑。也就是说每一个门具有三个属性,横坐标、纵坐标、时间坐标。

于是我们就有了很多很多的门,就可以将限制转换为每一个门都只能让该时刻到达的那一个人出去。

每一扇门仅允许一个人?

这就是一个匹配问题嘛。并且如果考虑人作为左部,门作为右部,这就是一个简单的二分图最大匹配。

显然可以通过 bfs 求得每一个人到任意一扇门所需的时间,设坐标 \((U_x, U_y)\) 这个人到坐标 \((V_x, V_y)\) 这扇门需要 \(t\) 的时间。

也就是说当前这个点对于这扇门可以走所有空间坐标 \((V_x, V_y)\),时间坐标晚于 \(t\) 的所有门。

但这只能判断可行性,难以算出具体答案啊?

容易想到直接二分答案。若当前二分到答案 \(T\),我们就可以将门的空间坐标限制到 \([t, T]\) 内,然后每个人相对于可以走的门连边跑匈牙利即可。

可以简单估算一下二分上界。假设有 \(nm - 1\) 个人,\(1\) 扇门,最远的人到达这扇门的距离是 \(n + m\),而不能发现每个时候都会有人到达,所以上界应是 \(n m\)。

另外一种情况,一个人为了绕开所有墙,最多也只会耗费 \(n m\) 的时间,且对于门来说,\(n m\) 的时间一定可以把人全部送出去。


Code.

#include <queue>
#include <cstdio>
using namespace std;

int Abs(int x) { return x < 0 ? -x : x; }
int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
    write(x);
    putchar(s);
}

const int MAXM = 2e6 + 5;
const int MAXN = 2e4 + 5;

struct Bipartite_Graph {
    struct edge {
        int v, nxt;
        edge() {}
        edge(int V, int Nxt) {
            v = V, nxt = Nxt;
        }
    } e[MAXM << 1];
    int head[MAXN], n, cnt;
    void Add_Edge(int u, int v) { 
        e[cnt] = edge(v, head[u]);
        head[u] = cnt++;
    }
    int Mat[MAXN], Tim[MAXN], p[MAXN], len, tot;

    void init(int N) {
        n = N;
        for(int i = 1; i <= n; i++) 
            head[i] = -1, Tim[i] = 0, Mat[i] = 0;
        cnt = 0, tot = 0, len = 0;
    }

    bool dfs(int u) {
        if (Tim[u] == tot)
            return false;        
        Tim[u] = tot;
        for (int i = head[u], v; ~i; i = e[i].nxt) {
            v = e[i].v;
            if (!Mat[v] || dfs(Mat[v])) {
                Mat[v] = u;
                return true;
            }
        }
        return false;
    }

    int calc() {
        int ans = 0;
        for (int i = 1; i <= len; i++) {
            tot++;
            ans += dfs(p[i]);
        }
        return ans;
    }    
} Graph; // 二分图最大匹配模板。

char s[MAXN][MAXN];
int pos[MAXN][MAXN], dist[MAXN][MAXN], n, m, num, cnt;

struct node {
    int x, y;
    node() {}
    node(int X, int Y) {
        x = X, y = Y;
    }
};
int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
void bfs(node st) { // 求人到门所需时间。
    queue<node> q;
    q.push(st);
    while(!q.empty()) {
        node u = q.front(); q.pop();
        for(int i = 0, cx, cy; i < 4; i++) {
            cx = u.x + dir[i][0], cy = u.y + dir[i][1];
            if(cx < 1 || cx > n || cy < 1 || cy > m)
                continue;
            if(s[cx][cy] == '.' && !dist[pos[st.x][st.y]][pos[cx][cy]]) {
                dist[pos[st.x][st.y]][pos[cx][cy]] = dist[pos[st.x][st.y]][pos[u.x][u.y]] + 1;
                q.push(node(cx, cy));
            }
        }
    }
}

bool check(int mid) { // 二分可行性判断。
    Graph.init(num + (cnt - num) * mid);   
    for(int i = 1; i <= num; i++) {
        Graph.p[++Graph.len] = i;
        for(int j = num + 1; j <= cnt; j++)
            if(dist[j][i])
                for(int k = dist[j][i]; k <= mid; k++)
                    Graph.Add_Edge(i, j + (k - 1) * (cnt - num)); // 时间范围内 拆点 建边
    }
    int res = Graph.calc(); 
    return res >= num;
}

int main() {
    n = read(), m = read();
    num = 0;
    for(int i = 1; i <= n; i++) {
        scanf ("%s", s[i] + 1);        
        for(int j = 1; j <= m; j++)
            if(s[i][j] == '.')
                pos[i][j] = ++num; 
    }
    cnt = num;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) 
            if(s[i][j] == 'D') {
                cnt++;
                pos[i][j] = cnt;
                bfs(node(i, j));
            }
    // 这部分处理打得有点奇怪了,转整数标号还是按照自己的喜好来。
    int l = 0, r = n * m * 2, mid, res = -1;
    while(l <= r) {
        mid = (l + r) >> 1;
        if(check(mid)) {
            r = mid - 1;
            res = mid;
        }
        else    
            l = mid + 1;
    }
    if(res == -1)
        printf("impossible\n");
    else
        print(res, '\n');
    return 0;
}
上一篇:怎样给访问量过大的mysql数据库减压


下一篇:系统性肥大细胞增多症的治疗行业调研报告 - 市场现状分析与发展前景预测