• 【luogu P7473】重力球


    重力球

    题目链接: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;
    }
    
  • 相关阅读:
    Expression多条件筛选
    Express对条件的手动拼接
    查看绝对路径
    检查Linux内核版本信息
    New 路程 2019
    Braintree PayPal 支付网关开发(二)
    Braintree PayPal 支付网关开发(一)
    Quartz 作业调度框架的使用
    C# 扩展方法
    ASP.Net MVC管道分析
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P7473.html
Copyright © 2020-2023  润新知