📅  最后修改于: 2023-12-03 15:28:48.983000             🧑  作者: Mango
这是一道 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];
首先,我们需要建立一个虚拟的点表示魔法门。
然后,定义初始状态,将所有人的位置和钥匙集合初始化为 0,距离初始化为 0。
接着,使用广度优先搜索,将当前状态入队,并标记当前状态已访问。
下一步,我们需要遍历所有人,以及他们拥有的钥匙,来检查是否有门可以打开。
对于每个人,遍历其拥有的钥匙,检查能否打开门。如果可以,我们将钥匙加入他人的手中,用新状态去更新队列(队列中会存储当前状态到新状态的操作,这里就是交钥匙这个操作)。
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});
}
直到找到最终状态或者队列为空,结束搜索。
输出最少步数即可。
广度优先搜索的时间复杂度为 $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)$。