📜  门|门CS 2011 |第 65 题(1)

📅  最后修改于: 2023-12-03 15:28:48.983000             🧑  作者: Mango

门|门CS 2011 第 65 题

这是一道 ACM 题目,需要编写程序求解。

题目描述

有一个 $n\times m$ 的矩阵,矩阵中的元素为 $0$ 或 $1$,每个元素表示一个房间的门。一个人可以从任意一个房间中的门进入这个房间,出现在任意一个房间中的门。

现在有 $k$ 个人,每个人从不同的房间中的门进入,他们的目的是要到达矩阵中的一个特定房间中的门。假设这些房间都是连通的,那么他们可以通过相互协作,互相之间传递钥匙,以到达目的地,换而言之,如果一个人进入了一扇门,他可以将钥匙通过某个方式传给另一个人,让另一个人继续前进。

注意:同一个人不能在同一个时刻进入两个房间,也不能从一个房间的一扇门进入另一个房间的同一扇门。

假设初始时所有人都在各自的起点,问最少需要几步才能使得所有人到达目标房间中的门。

输入格式

输入包含多组数据。

对于每组数据,第一行包含三个整数 $n,m,k$,表示矩阵的行数、列数,以及人的数量。

接下来 $k$ 行,每行包含四个整数 $x_i,y_i,p_i,q_i$,表示第 $i$ 个人的起点 $(x_i,y_i)$ 和目标点 $(p_i,q_i)$。

接下来 $n$ 行,每行包含 $m$ 个整数,表示矩阵中的元素。

为了简化,直接用数字 $0$ 和 $1$ 分别表示门和非门,没有非门的房间我们不需要考虑。

输入以三个 $0$ 为结尾,表示输入结束。

输出格式

对于每组数据,输出一个整数,表示所有人到达目的地所需的最少步数。

如果无法到达,则输出 $-1$。

输入样例
3 3 2
1 1 3 3
3 1 1 1
1 0 0
0 1 0
0 0 1
0 0 0
0 0 0
0 0 1
2 2 2
2 1 1 2
1 1 1 1
0 1
1 1
1 2
2 2
0 0 0
输出样例
3
1
算法

此题需要用到图论的知识,需要建立一个虚拟的点表示魔法门,然后利用广度优先搜索,计算出所有人到达目的地所需的最少步数。

数据结构

我们定义一个类 State 储存当前状态,包含所有人所在的位置、步数,以及每个人手中拥有的钥匙集合。

struct State {
    int x[M], y[M];
    int st[M];
    int dist;
};

x[i]y[i] 表示第 $i$ 个人所在的行列位置,st[i] 表示第 $i$ 个人拥有的钥匙集合,dist 表示到达当前状态的步数。

为了防止重复访问,我们维护一个 $n\times m\times 2^k$ 的状态数组,其中 $2^k$ 表示所有人的钥匙集合可表示的状态数。

bool vis[N][M][1 << M];
解题思路
  1. 首先,我们需要建立一个虚拟的点表示魔法门。

  2. 然后,定义初始状态,将所有人的位置和钥匙集合初始化为 0,距离初始化为 0。

  3. 接着,使用广度优先搜索,将当前状态入队,并标记当前状态已访问。

  4. 下一步,我们需要遍历所有人,以及他们拥有的钥匙,来检查是否有门可以打开。

  5. 对于每个人,遍历其拥有的钥匙,检查能否打开门。如果可以,我们将钥匙加入他人的手中,用新状态去更新队列(队列中会存储当前状态到新状态的操作,这里就是交钥匙这个操作)。

for (int i = 0; i < k; i++)
    for (int j = 1; j < 1 << k; j++)
        if (st[i] & j) // 当前人有钥匙 j
            for (int l = 0; l < door[j].size(); l++) {  // 遍历可开门的位置
                int xx = door[j][l].first;
                int yy = door[j][l].second;
                if (st[i] >> (xx * m + yy) & 1) continue;   // 当前人没有这把钥匙,不能打开门

                int t = st[i] ^ j; // 另一个人拿到了钥匙 j
                if (vis[x][y][t] || vis[xx][yy][st[i]]) continue;
                vis[x][y][t] = vis[xx][yy][st[i]] = true;
                q.push({x, y, xx, yy, t, dist + 1});
            }
  1. 直到找到最终状态或者队列为空,结束搜索。

  2. 输出最少步数即可。

时间复杂度

广度优先搜索的时间复杂度为 $O(nmk2^k)$。

空间复杂度

空间复杂度为 $O(nmk2^k)$。

参考文献
代码实现
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

const int N = 10, M = 1005;

int n, m, k;
int g[N][N];
bool vis[N][N][1 << N];

struct State {
    int x[M], y[M];
    int st[M];
    int dist;
};

vector<pair<int, int>> door[1 << N];

int bfs(State start) {
    queue<State> q;
    memset(vis, false, sizeof vis);
    q.push(start);

    while (!q.empty()) {
        auto t = q.front(); q.pop();
        int x[M], y[M], st[M], dist;
        memcpy(x, t.x, sizeof x);
        memcpy(y, t.y, sizeof y);
        memcpy(st, t.st, sizeof st);
        dist = t.dist;
        if (vis[x[0]][y[0]][st[0]]) continue;

        vis[x[0]][y[0]][st[0]] = true;
        bool flag = true;
        for (int i = 1; i < k; i++)
            if (g[x[i]][y[i]] != g[t.x[0]][t.y[0]]) {
                flag = false;
                break;
            }
        if (flag) return dist;

        for (int i = 0; i < k; i++) {
            for (auto d : door[st[i]]) {
                int xx = d.first;
                int yy = d.second;
                if (st[i] >> (xx * m + yy) & 1) continue;   // 当前人没有这把钥匙,不能打开门

                int t = st[i] ^ (1 << (xx * m + yy));   // 另一个人拿到了钥匙 j
                if (vis[x[i]][y[i]][t] || vis[xx][yy][st[i]]) continue;
                vis[x[i]][y[i]][t] = vis[xx][yy][st[i]] = true;
                q.push({x, y, t, dist + 1});
            }

            for (int j = 0; j < 4; j++) {
                int a = x[i] + "2101"[j] - '1';
                int b = y[i] + "1210"[j] - '1';
                if (a < 0 || a >= n || b < 0 || b >= m) continue;
                if (g[a][b] == -1) continue;

                bool ok = true;
                for (int u = 0; u < k; u++)
                    if (i != u && x[u] == a && y[u] == b) {
                        ok = false;
                        break;
                    }
                if (!ok) continue;

                int t = st[i] & ~(1 << (x[i] * m + y[i]));
                t |= (g[a][b] == 1) << (a * m + b);
                if (vis[a][b][t]) continue;
                vis[a][b][t] = true;

                x[i] = a, y[i] = b, st[i] = t;
                q.push({x, y, t, dist + 1});

                x[i] = t.x[i], y[i] = t.y[i], st[i] = t.st[i];
            }
        }
    }

    return -1;
}

int main() {
    while (cin >> n >> m >> k, n || m || k) {
        memset(g, -1, sizeof g);
        for (int i = 0; i < 1 << k; i++) door[i].clear();
        int t = 1;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++, t++) {
                cin >> g[i][j];
                if (g[i][j] == 1) door[1 << (i * m + j)].push_back({i, j});
            }

        State start;
        for (int i = 0; i < k; i++) {
            int x, y, p, q;
            cin >> x >> y >> p >> q;
            start.x[i]= x - 1, start.y[i] = y - 1, start.st[i] = 1 << (x * m + y);
            g[x - 1][y - 1] = i + 2;
            g[p - 1][q - 1] = 1;
        }
        start.dist = 0;

        cout << bfs(start) << endl;
    }

    return 0;
}

时间复杂度:$O(nmk2^k)$。

空间复杂度:$O(nmk2^k)$。