【luogu P7473】重力球

重力球

题目链接:luogu P7473

题目大意

有一个图中有一些障碍物,边界也是障碍物。
然后又每个询问给出两个小球的位置,你可以改变重力变成左右前后,问你最少要改变多少次重力才能使得两个小球滚到一起。
如果不能滚到一起输出 -1,多组询问。

思路

因为是根据重力,那在一次滚动之后,这个点停留在那里一定是因为有障碍物挡住了它,那也就说,有效的点就是旁边四个方向有障碍物的点。

那可以根据数据算出点最多不超过 2000 2000 2000 个。
那我们可以先看看一个询问怎么弄,那因为我们把点压缩到这么小,所以我们可以直接暴搜。
(不过要先自己模拟一步走到关键点)

当然,我们可以先通过一个简单的 DP 算出一个点往四个方向走可以到哪里。
就如果那个方向走一步是障碍物,那就只能走到自己,否则就在走到的地方往那个方向继续走。
然后用合适的顺序可以直接不用继续走,因为当时已经算出了答案。

但是它是多组询问,而且询问还挺多, 1 0 5 10^5 105 个。
基本上明摆着要直接预处理出所有答案。

你考虑反过来想,你枚举最终会和的地方,然后看可以从那两个位置走到。
那你把每个会和的地方放进队列里面进行 bfs,每次就选一个方向,然后从可以走过来的地方各选一个转移。
那就是反向建边。
然后你就可以这样搜出两个关键点会和所要的最少步数。

那你就根据上面的一样,先走一步使两个都到关键点,然后就可以把四个方向得出的距离取最小值。
那你会想,如果它一步都不走呢?
那就是一开始就在同一个位置,特判一下就好。

代码

#include
#include
#include
#include
#include

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\n");
			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\n");
		}
		else printf("%d\n", ans);
	}
	
	return 0;
}

你可能感兴趣的:(#,动态规划,#,dfs或bfs,bfs,dp)