P7473 [NOI Online 2021 入门组] 重力球
P7473 NOI Online 2021 入门组 重力球。
球在运动过程中,除了初始状态,都只会运动到与边界或障碍物相邻的点,不妨称之为转移点。不难发现转移点最多只有 \(4(n+m)\) 个。
我们考虑将转移点从 \(1\) 开始编号。
发现两个球分别处于两个转移点的总状态数不超过 \([4(n+m)]^2 \le 4\ \times 10^6\) 个,可以接受。
我们用 \((x, y)\) 表示第一个球在转移点 \(x\),第二个球在转移点 \(y\) 的状态。
球初始时可能不在转移点。可以枚举第一次的四种重力方向,让球运动到转移点上,最后求第一次重力取哪种方向能让答案最小即可。
为了做到快速求出球往一个方向运动到的转移点编号,我们要预处理出每个位置往每个方向移动到的转移点,这个可以 \(\Theta(nm)\) 做到。
于是回答询问时,可以 \(\Theta(1)\) 求得球向某个方向移动到的转移点编号。
现在我们只用考虑转移点了。
考虑建图。设转移点 \(x\) 向左可以运动到转移点 \(p\),向下可以运动到转移点 \(q\);点 \(y\) 向左可以运动到转移点 \(s\),向下可以运动到转移点 \(t\)。
如果上一次两个球处于 \((x, y)\) 状态,那么向左一次运动可以得到 \((p, s)\),向下运动一次可以得到 \((q, t)\)。
因此,\((x, y)\) 可以转移到 \((p, s)\) 或 \((q, t)\),而不能一次转移到 \((q, s)\) 或 \((p, t)\)。这给我们什么启发?
如果我们建图按照 \(x \to p\),\(x \to q\),\(y \to s\),\(y \to t\) 这四条边全都放一起大锅乱炖,我们将无法从 \((x, y)\) 开始正确转移到下一步。
两种操作方式:
- 给每条边加一条属性:方向。从 \((x, y)\) 这个状态开始,同时枚举从 \(x\) 开始的一条出边 \(e_x\) 和从 \(y\) 开始的一条出边 \(e_y\)。只有当 \(e_x\) 和 \(e_y\) 方向属性相同时,再按照这两个边的方向,转移到下一个合法状态,比如 \((p, s)\) 或 \((q, t)\)。
- 建四张图,每张图只放一种方向的边。这样以来,我们先枚举四张图中的一张,再让 \((x, y)\) 按照这张图上面的边转移即可。比如,一张只有“向左”边的图中,能让 \((x, y)\) 转移到 \((p, s)\);而一张只有“向下”边的图中,能让 \((x, y)\) 转移到 \((q, t)\),不会混乱。
我选择了第二种方式。因为这样以来,从每个状态开始枚举出边时,只会枚举到有用的同向出边对,枚举次数会更少。
然后再观察这个题,是一个多终点最短路问题。我们应该转化成多源点最短路问题。
反图上多源点到一个点 \(u\) 的距离,也就是原图上这个点 \(u\) 到多终点的距离。
为此我们要将上面这个图反过来,也就是这么建:\(p \to x\),\(q \to x\),\(s \to y\),\(t \to y\)。
然后,我们再将所有表示两个球处于同一个转移点的状态,也即 \((x, x)\) 这种状态作为源点。这些源点对应的 \(\mathrm{dis} = 0\)。
然后从这些源点开始,bfs 求得其余所有 \([4(n + m)]^2\) 个状态对应的距离。
在询问之前,bfs 预处理出距离,回答时就能做到 \(\Theta(1)\) 了。
本题中,由于是两个点一起转移,bfs 时间复杂度数量级是图上点数的平方加边数的平方,而点数(即转移点数)为 \(4(n+m)\),边数为 \(4 \times 4(n+m)\)(每个点最多连接四条边),所以加起来是一个大概 \(20\) 常数的 \((n+m)^2 = 10^6\),也就是大概 \(2 \times 10^8\),而且跑不满。
回答一次询问 \(\Theta(1)\),所以上述算法可过。
一些细节:
- 这个图是有自环的。比如样例中的点 \((1, 1)\) 就是一个转移点,它向左或向上移动后,都会转移到自己。但没什么影响。
- 算完两个球第一步运动到某个方向的下一个状态对应的答案最小值后,不要忘记 \(+1\)。然后需要把两个球一开始就重合的情况特判掉。
如果这篇题解帮助到您了,记得给个赞!
/*
* @Author: crab-in-the-northeast
* @Date: 2023-03-22 08:00:29
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-03-22 11:01:43
*/
#include <bits/stdc++.h>
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
const int maxn = 255, maxm = 255;
int id[maxn][maxn];
std :: vector <int> G[(maxn + maxm) << 2][4];
int fall[maxn][maxn][4];
int dis[(maxn + maxm) << 2][(maxn + maxm) << 2];
int main() {
int n = read(), m = read(), Q = read();
for (int i = 1; i <= m; ++i) {
int x = read(), y = read();
id[x][y] = -1;
}
for (int i = 1; i <= n; ++i)
id[i][0] = id[0][i] = id[i][n + 1] = id[n + 1][i] = -1;
int cnt = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (id[i][j] < 0)
continue;
if (id[i - 1][j] < 0 || id[i + 1][j] < 0 || id[i][j - 1] < 0 || id[i][j + 1] < 0)
id[i][j] = ++cnt;
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
int x = id[i][j];
if (x < 0)
continue;
if (id[i][j - 1] < 0)
fall[i][j][0] = x;
else
fall[i][j][0] = fall[i][j - 1][0];
if (id[i - 1][j] < 0)
fall[i][j][1] = x;
else
fall[i][j][1] = fall[i - 1][j][1];
if (x > 0) {
G[fall[i][j][0]][0].push_back(x);
G[fall[i][j][1]][1].push_back(x);
}
}
}
for (int i = n; i; --i) {
for (int j = n; j; --j) {
int x = id[i][j];
if (x < 0)
continue;
if (id[i][j + 1] < 0)
fall[i][j][2] = x;
else
fall[i][j][2] = fall[i][j + 1][2];
if (id[i + 1][j] < 0)
fall[i][j][3] = x;
else
fall[i][j][3] = fall[i + 1][j][3];
if (x > 0) {
G[fall[i][j][2]][2].push_back(x);
G[fall[i][j][3]][3].push_back(x);
}
}
}
std :: queue <std :: pair <std :: pair <int, int>, int> > q;
std :: memset(dis, 0x3f, sizeof(dis));
const int inf = dis[0][0];
for (int i = 1; i <= cnt; ++i) {
q.push({{i, i}, 0});
dis[i][i] = 0;
}
while (!q.empty()) {
int a = q.front().first.first, b = q.front().first.second, d = q.front().second;
q.pop();
for (int dir = 0; dir < 4; ++dir) {
for (int nxta : G[a][dir])
for (int nxtb : G[b][dir])
if (dis[nxta][nxtb] == inf) {
dis[nxta][nxtb] = d + 1;
q.push({{nxta, nxtb}, d + 1});
}
}
}
while (Q--) {
int a = read(), b = read(), c = read(), d = read();
if (a == c && b == d) {
puts("0");
continue;
}
int ans = inf;
for (int dir = 0; dir < 4; ++dir) {
ans = std :: min(ans,
dis[fall[a][b][dir]][fall[c][d][dir]] + 1
);
}
printf("%d\n", (ans == inf) ? -1 : ans);
}
return 0;
}