[題解] [NOI Online 2021 入門組 T3] 重力球


題目大意

在一個 \(n\times 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\n");
		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\n", 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\n", 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\n", 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\n", 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\n");
}

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\n", Query());
	}
	return 0;//完結撒花
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM