P7473 [NOI Online 2021 入门组] 重力球
P7473 NOI Online 2021 入门组 重力球。
球在运动过程中,除了初始状态,都只会运动到与边界或障碍物相邻的点,不妨称之为转移点。不难发现转移点最多只有
我们考虑将转移点从
发现两个球分别处于两个转移点的总状态数不超过
我们用
球初始时可能不在转移点。可以枚举第一次的四种重力方向,让球运动到转移点上,最后求第一次重力取哪种方向能让答案最小即可。
为了做到快速求出球往一个方向运动到的转移点编号,我们要预处理出每个位置往每个方向移动到的转移点,这个可以
于是回答询问时,可以
现在我们只用考虑转移点了。
考虑建图。设转移点
如果上一次两个球处于
因此,
如果我们建图按照
两种操作方式:
- 给每条边加一条属性:方向。从
这个状态开始,同时枚举从 开始的一条出边 和从 开始的一条出边 。只有当 和 方向属性相同时,再按照这两个边的方向,转移到下一个合法状态,比如 或 。 - 建四张图,每张图只放一种方向的边。这样以来,我们先枚举四张图中的一张,再让
按照这张图上面的边转移即可。比如,一张只有“向左”边的图中,能让 转移到 ;而一张只有“向下”边的图中,能让 转移到 ,不会混乱。
我选择了第二种方式。因为这样以来,从每个状态开始枚举出边时,只会枚举到有用的同向出边对,枚举次数会更少。
然后再观察这个题,是一个多终点最短路问题。我们应该转化成多源点最短路问题。
反图上多源点到一个点
为此我们要将上面这个图反过来,也就是这么建:
然后,我们再将所有表示两个球处于同一个转移点的状态,也即
然后从这些源点开始,bfs 求得其余所有
在询问之前,bfs 预处理出距离,回答时就能做到
本题中,由于是两个点一起转移,bfs 时间复杂度数量级是图上点数的平方加边数的平方,而点数(即转移点数)为
回答一次询问
一些细节:
- 这个图是有自环的。比如样例中的点
就是一个转移点,它向左或向上移动后,都会转移到自己。但没什么影响。 - 算完两个球第一步运动到某个方向的下一个状态对应的答案最小值后,不要忘记
。然后需要把两个球一开始就重合的情况特判掉。
如果这篇题解帮助到您了,记得给个赞!
/*
* @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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】