• [题解] [NOI Online 2021 入门组 T3] 重力球


    题目大意

    在一个 (n imes n) 的矩形中,题目会给出 (m) 个障碍物。有两个小球,你可以选定四个方向(上下左右)的其中一个,小球会朝着这四个方向一直滚动,直到遇到障碍物或是矩形的边缘停止。有 (q) 条形如 (a) (b) (c) (d) 的询问,代表两个小球的坐标 ((a,b))((c,d)) ,求多少步,小球会重叠。

    题目链接

    思路

    55pts

    首先考虑暴力,先预处理出所有点滚动会滚动到哪里,写四个 (dfs) 完事。

    int L(int i, int j) {//向左滚
    	if(stn[i][j - 1]) {
    		l[i][j] = L(i, j - 1);
    		return l[i][j];
    	}
    	else
    		l[i][j] = j;
    	return j;
    }
    int U(int i, int j) {//向上滚
    	if(stn[i - 1][j]) {
    		u[i][j] = U(i - 1, j);
    		return u[i][j];
    	}
    	else
    		u[i][j] = i;
    	return i;
    }
    int R(int i, int j) {//向右滚
    	if(stn[i][j + 1]) {
    		r[i][j] = R(i, j + 1);
    		return r[i][j];
    	}
    	else
    		r[i][j] = j;
    	return j;
    }
    int D(int i, int j) {//向下滚
    	if(stn[i + 1][j]) {
    		w[i][j] = D(i + 1, j);
    		return w[i][j];
    	}
    	else
    		w[i][j] = i;
    	return i;
    }
    

    主函数中:

    for(int i = 1; i <= n; i++) {
    	for(int j = 1; j <= n; j++) {
    		if(!stn[i][j])//小球不会到障碍物上
    			continue;
    		if(!r[i][j])
    			R(i, j);
    		if(!w[i][j])
    			D(i, j);
    	}
    }
    for(int i = n; i >= 1; i--) {
    	for(int j = n; j >= 1; j--) {
    		if(!stn[i][j])//小球不会到障碍物上
    			continue;
    		if(!l[i][j])
    			L(i, j);
    		if(!u[i][j])
    			U(i, j);
    	}
    }
    

    然后无脑 (BFS)(TLE) 。( (55pts) 亲测)

    主体部分

    void BFS() {
    	q.push(Node(a, b, c, d, 0));
    	if(a == c && b == d) {
    		printf("0
    ");
    		return;
    	}
    	while(!q.empty()) {
    		Node now = q.front(); q.pop();
    		Node next = now;
    		next.step++;
    		next.Y_1 = l[next.X_1][next.Y_1];
    		next.Y_2 = l[next.X_2][next.Y_2];
    		if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
    			printf("%d
    ", next.step);
    			return;
    		}
    		int tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
    		if(!f[tmp]) {
    			f[tmp] = 1;
    			q.push(next);
    		}
    		
    		
    		next = now;
    		next.step++;
    		next.Y_1 = r[next.X_1][next.Y_1];
    		next.Y_2 = r[next.X_2][next.Y_2];
    		if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
    			printf("%d
    ", next.step);
    			return;
    		}
    		tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
    		if(!f[tmp]) {
    			f[tmp] = 1;
    			q.push(next);
    		}
    		
    		
    		next = now;
    		next.step++;
    		next.X_1 = u[next.X_1][next.Y_1];
    		next.X_2 = u[next.X_2][next.Y_2];
    		if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
    			printf("%d
    ", next.step);
    			return;
    		}
    		tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
    		if(!f[tmp]) {
    			f[tmp] = 1;
    			q.push(next);
    		}
    		
    		next = now;
    		next.step++;
    		next.X_1 = w[next.X_1][next.Y_1];
    		next.X_2 = w[next.X_2][next.Y_2];
    		if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
    			printf("%d
    ", next.step);
    			return;
    		}
    		tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
    		if(!f[tmp]) {
    			f[tmp] = 1;
    			q.push(next);
    		}
    	}
    	printf("-1
    ");
    }
    

    100pts

    考虑逆推求出所有状态的最小满足条件步数。

    不难发现,经过一次的滚动后,小球会落在障碍物的旁边或是矩阵的边缘。一共有 ((4m+4n)) 中状态,那么两个求就一共有 ((4m+4n)^2) 种状态,可以往四边滚,那么可以将会与其他的四种状态有联系。

    在考虑将这些点进行 (hash) 相连。那么就成为了一个多源最短路问题。将一个超级源点 (s) 连向每个两小球重叠的哈希值连边。同时当前状态与下一个状态建立反边,跑最短路即可。

    由于边长都为 (1) ,则可以使用 (BFS) 来求最短路,时间复杂度为 (O(4(4m+4n)^2))

    最后是查询的问题,也比较简单, 分四个方向先滚动一次,那么步数就是 (dis[tmp]+1)(dis) 记录最短路, (tmp) 为当前状态的哈希值)。但值得注意的是,若滚动前和滚动后两个小球的位置没有改变,则不需要加一。

    细节代码上有注释。

    Code

    #include <queue>
    #include <cstdio>
    using namespace std;
    #define INF 0x3f3f3f3f
    const int MAXN = 2e3 + 5;
    const int MAXM = 5e7 + 5;
    struct Node {//小球的位置
    	int X_1, Y_1, X_2, Y_2;
    	Node() {}
    	Node(int A, int B, int C, int D) {
    		X_1 = A;
    		Y_1 = B;
    		X_2 = C;
    		Y_2 = D;
    	}
    	friend bool operator == (Node x, Node y) {
    		return (x.X_1 == y.X_1) && (x.X_2 == y.X_2) && (x.Y_1 == y.Y_1) && (x.Y_2 == y.Y_2);
    	}
    };
    struct Edge {//链式前向星存边,别用vector
    	int To, Next;
    };
    Edge edge[MAXM];
    int head[MAXM];
    int u[MAXN][MAXN], w[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN];
    bool stn[MAXN][MAXN], can[MAXN][MAXN], vis[MAXM];
    int dis[MAXM], Hash[MAXN][MAXN];
    pair<int, int> id[MAXN];
    queue<int> q;
    int x[MAXN], y[MAXN];
    int n, m, s, Q;
    int a, b, c, d;
    int tot, cnt;
    void Addedge(int u, int v) {//加入边
    	edge[++tot].Next = head[u];
    	edge[tot].To = v;
    	head[u] = tot;
    }
    int Get_Hash(int A, int B, int C, int D) {//哈希值
    	if(A > C || (A == C && B > D)) {//注意先排序,再hash
    		swap(A, C);
    		swap(B, D);
    	}
    	int x = Hash[A][B];
    	int y = Hash[C][D];
    	return x * 2001 + y;//共有(4n+4m)种状态,大概是2000,这样不会发生冲突
    }
    int L(int i, int j) {//向左走
    	if(stn[i][j - 1]) {
    		l[i][j] = L(i, j - 1);
    		return l[i][j];
    	}
    	else
    		l[i][j] = j;
    	return j;
    }
    int U(int i, int j) {//向上走
    	if(stn[i - 1][j]) {
    		u[i][j] = U(i - 1, j);
    		return u[i][j];
    	}
    	else
    		u[i][j] = i;
    	return i;
    }
    int R(int i, int j) {//向右走
    	if(stn[i][j + 1]) {
    		r[i][j] = R(i, j + 1);
    		return r[i][j];
    	}
    	else
    		r[i][j] = j;
    	return j;
    }
    int D(int i, int j) {//向下走
    	if(stn[i + 1][j]) {
    		w[i][j] = D(i + 1, j);
    		return w[i][j];
    	}
    	else
    		w[i][j] = i;
    	return i;
    }
    void Build() {//建图
    	int tmp;
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; j <= n; j++)
    			if(can[i][j] && stn[i][j]) {//是障碍物边缘且不失障碍物
    				id[++cnt].first = i;
    				id[cnt].second = j;
    				Hash[i][j] = cnt;
    				tmp = Get_Hash(i, j, i, j);
    				Addedge(s, tmp);//超级源点连结果
    			}
    	for(int i = 1; i <= cnt; i++) {
    		for(int j = i + 1; j <= cnt; j++) {//下一状态连边,注意是反向边
    			Node now = Node(id[i].first, id[i].second, id[j].first, id[j].second);
    			int to = Get_Hash(id[i].first, id[i].second, id[j].first, id[j].second);
    			Node next = now;
    			next.Y_1 = l[next.X_1][next.Y_1];
    			next.Y_2 = l[next.X_2][next.Y_2];
    			int tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    			Addedge(tmp, to);
    			next = now;
    			next.Y_1 = r[next.X_1][next.Y_1];
    			next.Y_2 = r[next.X_2][next.Y_2];
    			tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    			Addedge(tmp, to);
    			next = now;
    			next.X_1 = u[next.X_1][next.Y_1];
    			next.X_2 = u[next.X_2][next.Y_2];
    			tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    			Addedge(tmp, to);
    			next = now;
    			next.X_1 = w[next.X_1][next.Y_1];
    			next.X_2 = w[next.X_2][next.Y_2];
    			tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    			Addedge(tmp, to);
    		}
    	}
    }
    void Shortestpast() {//多源最短路
    	q.push(s);
    	vis[s] = 1;
    	dis[s] = -1;
    	while(!q.empty()) {//边长为1用BFS
    		int now = q.front(); q.pop();
    		for(int i = head[now]; i; i = edge[i].Next) {
    			int next = edge[i].To;
    			if(!vis[next]) {
    				vis[next] = 1;
    				dis[next] = dis[now] + 1;
    				q.push(next);
    			}
    		}
    	}
    }
    int Query() {
    	if(a == c && b == d)//已经重叠不用滚
    		return 0;
    	Node now = Node(a, b, c, d);
    	int tmp, res = INF;
    	Node next = now;
    	next.Y_1 = l[next.X_1][next.Y_1];
    	next.Y_2 = l[next.X_2][next.Y_2];
    	tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    	if(vis[tmp]) {//如果最短路中被标记过才更新
    		if(next == now)
    			res = min(res, dis[tmp]);//还是在原位置,不加1
    		else
    			res = min(res, dis[tmp] + 1);
    	}
    	next = now;
    	next.Y_1 = r[next.X_1][next.Y_1];
    	next.Y_2 = r[next.X_2][next.Y_2];
    	tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    	if(vis[tmp]) {
    		if(next == now)
    			res = min(res, dis[tmp]);
    		else
    			res = min(res, dis[tmp] + 1);
    	}
    	next = now;
    	next.X_1 = u[next.X_1][next.Y_1];
    	next.X_2 = u[next.X_2][next.Y_2];
    	tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    	if(vis[tmp]) {
    		if(next == now)
    			res = min(res, dis[tmp]);
    		else
    			res = min(res, dis[tmp] + 1);
    	}
    	next = now;
    	next.X_1 = w[next.X_1][next.Y_1];
    	next.X_2 = w[next.X_2][next.Y_2];
    	tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
    	if(vis[tmp]) {
    		if(next == now)
    			res = min(res, dis[tmp]);
    		else
    			res = min(res, dis[tmp] + 1);
    	}
    	if(res != INF)
    		return res;//找到答案
    	return -1;//没有答案
    }
    int main() {
    	scanf("%d %d %d", &n, &m, &Q);
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; j <= n; j++)
    			stn[i][j] = 1;
    	for(int i = 1; i <= m; i++) {
    		scanf("%d %d", &x[i], &y[i]);
    		stn[x[i]][y[i]] = 0;//障碍物标记
    		can[x[i]][y[i] + 1] = 1;//障碍物四周标记
    		can[x[i] + 1][y[i]] = 1;
    		can[x[i]][y[i] - 1] = 1;
    		can[x[i] - 1][y[i]] = 1;
    	}
    	for(int i = 1; i <= n; i++)
    		can[1][i] = can[n][i] = can[i][1] = can[i][n] = 1;//矩阵四周标记
    	for(int i = 1; i <= n; i++) {
    		for(int j = 1; j <= n; j++) {
    			if(!stn[i][j])
    				continue;
    			if(!r[i][j])
    				R(i, j);
    			if(!w[i][j])
    				D(i, j);
    		}
    	}
    	for(int i = n; i >= 1; i--) {
    		for(int j = n; j >= 1; j--) {
    			if(!stn[i][j])
    				continue;
    			if(!l[i][j])
    				L(i, j);
    			if(!u[i][j])
    				U(i, j);
    		}
    	}
    	Build();
    	Shortestpast();
    	for(int i = 1; i <= Q; i++) {
    		scanf("%d %d %d %d", &a, &b, &c, &d);
    		printf("%d
    ", Query());
    	}
    	return 0;//完结撒花
    }
    
  • 相关阅读:
    [JSOI2012][bzoj4332] 分零食 [FFT]
    [MUTC2013][bzoj3513] idiots [FFT]
    [bzoj4259][bzoj4503] 残缺的字符串 [FFT]
    [bzoj3160] 万径人踪灭 [FFT+manacher]
    [AHOI2017/HNOI2017][bzoj4827] 礼物 [FFT]
    [ZJOI2014][bzoj3527]力 [FFT]
    [CQOI2012][bzoj2668] 交换棋子 [费用流]
    [CQOI2014][bzoj3504] 危桥 [最大流]
    [ZJOI2011][bzoj2229] 最小割 [最小割树]
    移动游戏ui设计(一)
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14596516.html
Copyright © 2020-2023  润新知