重力球
题目链接:luogu P7473
题目大意
有一个图中有一些障碍物,边界也是障碍物。
然后又每个询问给出两个小球的位置,你可以改变重力变成左右前后,问你最少要改变多少次重力才能使得两个小球滚到一起。
如果不能滚到一起输出 -1,多组询问。
思路
因为是根据重力,那在一次滚动之后,这个点停留在那里一定是因为有障碍物挡住了它,那也就说,有效的点就是旁边四个方向有障碍物的点。
那可以根据数据算出点最多不超过 (2000) 个。
那我们可以先看看一个询问怎么弄,那因为我们把点压缩到这么小,所以我们可以直接暴搜。
(不过要先自己模拟一步走到关键点)
当然,我们可以先通过一个简单的 DP 算出一个点往四个方向走可以到哪里。
就如果那个方向走一步是障碍物,那就只能走到自己,否则就在走到的地方往那个方向继续走。
然后用合适的顺序可以直接不用继续走,因为当时已经算出了答案。
但是它是多组询问,而且询问还挺多,(10^5) 个。
基本上明摆着要直接预处理出所有答案。
你考虑反过来想,你枚举最终会和的地方,然后看可以从那两个位置走到。
那你把每个会和的地方放进队列里面进行 bfs,每次就选一个方向,然后从可以走过来的地方各选一个转移。
那就是反向建边。
然后你就可以这样搜出两个关键点会和所要的最少步数。
那你就根据上面的一样,先走一步使两个都到关键点,然后就可以把四个方向得出的距离取最小值。
那你会想,如果它一步都不走呢?
那就是一开始就在同一个位置,特判一下就好。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct node {
int to, nxt;
}e[100001];
struct state {
int x, y;
};
queue <state> q;
int le[2001][4], KK, tmp;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
int n, m, Q, x, y, tot, px[2001], py[2001];
int pl[251][251], go[251][251][4][2];
int dis[2001][2001], ans;
int x1, y1, x2, y2;
bool ck(int x, int y) {
if (pl[x][y] == -1) return 0;
if (x < 1 || x > n) return 0;
if (y < 1 || y > n) return 0;
return 1;
}
void add(int x, int y, int way) {//因为你要每次走的方向一样,所以 head 要开四个分开记录四个方向的
swap(x, y);//记得是反向建边
e[++KK] = (node){y, le[x][way]}; le[x][way] = KK;
}
void bfs() {//bfs 暴搜
for (int i = 1; i <= tot; i++)
q.push((state){i, i}), dis[i][i] = 1;//一开始要走一步才能走到关键点
while (!q.empty()) {
state now = q.front();
q.pop();
x = now.x;
y = now.y;
for (int k = 0; k < 4; k++)//每次走的方向要一样
for (int i = le[x][k]; i; i = e[i].nxt)
for (int j = le[y][k]; j; j = e[j].nxt)
if (dis[e[i].to][e[j].to] == tmp)
q.push((state){e[i].to, e[j].to}), dis[e[i].to][e[j].to] = dis[x][y] + 1;
}
}
int main() {
scanf("%d %d %d", &n, &m, &Q);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
pl[x][y] = -1;
}
for (int i = 1; i <= n; i++)
pl[0][i] = pl[n + 1][i] = pl[i][0] = pl[i][n + 1] = -1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (pl[i][j] != -1) {
for (int k = 0; k < 4; k++)
if (pl[i + dx[k]][j + dy[k]] == -1) {
pl[i][j] = ++tot;//给有需要的位置编号
break;
}
}
for (int i = 1; i <= n; i++)//DP 出四个方向可以走到哪里
for (int j = 1; j <= n; j++)
if (ck(i, j)) {
if (ck(i + dx[2], j + dy[2]))
go[i][j][2][0] = go[i + dx[2]][j + dy[2]][2][0], go[i][j][2][1] = go[i + dx[2]][j + dy[2]][2][1];
else go[i][j][2][0] = i, go[i][j][2][1] = j;
if (ck(i + dx[3], j + dy[3]))
go[i][j][3][0] = go[i + dx[3]][j + dy[3]][3][0], go[i][j][3][1] = go[i + dx[3]][j + dy[3]][3][1];
else go[i][j][3][0] = i, go[i][j][3][1] = j;
}
for (int i = n; i >= 1; i--)
for (int j = n; j >= 1; j--)
if (ck(i, j)) {
if (ck(i + dx[0], j + dy[0]))
go[i][j][0][0] = go[i + dx[0]][j + dy[0]][0][0], go[i][j][0][1] = go[i + dx[0]][j + dy[0]][0][1];
else go[i][j][0][0] = i, go[i][j][0][1] = j;
if (ck(i + dx[1], j + dy[1]))
go[i][j][1][0] = go[i + dx[1]][j + dy[1]][1][0], go[i][j][1][1] = go[i + dx[1]][j + dy[1]][1][1];
else go[i][j][1][0] = i, go[i][j][1][1] = j;
}
for (int i = 1; i <= n; i++)//建边
for (int j = 1; j <= n; j++)
if (pl[i][j] > 0)
for (int k = 0; k < 4; k++)
add(pl[i][j], pl[go[i][j][k][0]][go[i][j][k][1]], k);
memset(dis, 0x7f, sizeof(dis));
tmp = dis[0][0];
bfs();
while (Q--) {
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
if (x1 == x2 && y1 == y2) {//一开始就在用一个位置
printf("0
");
continue;
}
ans = tmp;
for (int i = 0; i < 4; i++)//一开始选一个方向走到关键点
ans = min(ans, dis[pl[go[x1][y1][i][0]][go[x1][y1][i][1]]][pl[go[x2][y2][i][0]][go[x2][y2][i][1]]]);
if (ans == tmp) {
printf("-1
");
}
else printf("%d
", ans);
}
return 0;
}