【有外界干扰的BFS】经典题P2895Meteor Shower S

文章目录

输入样例

cpp 复制代码
4
0 0 2
2 1 2
1 1 2
0 3 5

输出样例

cpp 复制代码
5

本题的核心是在存在外界干扰(流星坠落)的情况下,使用广度优先搜索(BFS)算法找出贝茜到达安全格子所需的最短时间。

算法思路
  1. 输入处理与初始化
  • 读取流星信息 :从输入中读取流星的数量 m,以及每颗流星坠落的坐标 (x, y) 和坠落时间 t
  • 初始化危险时间矩阵 :创建一个二维数组 destroy,大小为 N x NN 为地图的最大可能边长),将数组中每个元素初始化为 INT_MAX,表示每个格子初始时都被认为是永远安全的。
  • 初始化距离矩阵 :创建一个二维数组 dist,同样大小为 N x N,将数组中每个元素初始化为 -1,表示每个格子初始时都未被访问过。
  1. 预处理危险时间矩阵
  • 遍历流星信息 :对于每颗流星,根据其坠落的坐标 (x, y) 和坠落时间 t,更新该格子以及其周围 4 个相邻格子(上、下、左、右)的 destroy
  • 取最小值更新 :在更新 destroy 时,使用 min 函数确保记录的是该格子首次变得危险的时间。例如,若某个格子有多颗流星先后坠落,只记录最早使其变得危险的时间。
  1. 初始化 BFS
  • 创建队列 :使用一个队列来进行 BFS 搜索,队列中存储的元素为坐标对 (x, y),表示贝茜当前所在的位置。
  • 起点入队 :将贝茜的初始位置 (0, 0) 加入队列,并将该位置的 dist 设为 0,表示从起点到起点的距离为 0。
  1. BFS 搜索过程
  • 取出队首元素 :从队列中取出队首元素 (x, y),同时记录到达该位置的当前时间 currentTime,即 dist[x][y]
  • 检查是否到达安全格子 :检查当前位置的 destroy 是否为 INT_MAX,若是,则说明该位置永远安全,输出当前时间 currentTime 并结束搜索。
  • 尝试向四个方向移动:
    • 计算新位置和新时间 :对于当前位置 (x, y),尝试向四个方向(上、下、左、右)移动,计算新位置的坐标 (newX, newY) 和到达新位置所需的新时间 newTimenewTime = currentTime + 1)。
    • 边界检查 :检查新位置是否越界(即 newX < 0newY < 0),若越界则跳过该位置。
    • 危险检查 :检查新时间 newTime 是否大于等于新位置的 destroy,若大于等于则说明新位置在贝茜到达时已经变得危险,跳过该位置。
    • 未访问检查 :检查新位置的 dist 是否为 -1,若为 -1 则说明该位置未被访问过,更新 dist[newX][newY]newTime,并将新位置 (newX, newY) 加入队列。
  1. 结果判断
  • 队列为空:若队列遍历完仍未找到安全的格子,说明贝茜无法到达安全地点,输出 -1。
代码示例
cpp 复制代码
#include<iostream>
#include<vector>
#include<queue>
#include<climits>
using namespace std;

typedef pair<int,int> PII;
const int N=310;
int destroy[N][N];//记录每个格子被摧毁的最早时间 
int dist[N][N];//记录到达每个格子的最短时间 
int dx[]={-1,0,1,0},dy[]={0,-1,0,1}; 


int bfs(){
	queue<PII> q;
	q.push({0,0});
	dist[0][0]=0; 
	
	while(!q.empty()){
		auto t=q.front();//将起点入队 
		q.pop();
		int x=t.first,y=t.second;
		 
		//如果当前位置安全且永远不会被流星摧毁,返回到达该位置的时间
		if(destroy[x][y] == INT_MAX) return dist[x][y];
		
		//尝试向四个方向移动 
		for(int i=0;i<4;i++){
			int nx=x+dx[i],ny=y+dy[i];
			int newTime=dist[x][y]+1;		
				
			//检查新位置是否越界
			if(nx<0 || ny<0) continue;
			
			//检查新位置是否已经变得危险
			if(newTime>=destroy[nx][ny]) continue;
			
			//如果新位置未被访问,更新距离并加入队列
			if(dist[nx][ny] == -1){
				dist[nx][ny] = newTime;
				q.push({nx,ny});
			} 
		}
		
	}
	return -1;
}

int main(){
	int m;
	cin>>m;
	
	//初始化到危险时间矩阵为无穷大
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			destroy[i][j] = INT_MAX;
		}
	}
	
	//初始化距离矩阵为-1,表示未访问过 
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			dist[i][j] = -1;
		}
	} 
	
	//读取流星信息并更新危险时间矩阵 
	for(int i=0;i<m;i++){
		int x,y,t;
		cin>>x>>y>>t;
		//更新当前格子和周围四个格子的被摧毁的最早时间 
		destroy[x][y] = min(destroy[x][y],t);
		for(int j=0;j<4;j++){
			int nx=x+dx[j],ny=y+dy[j];
			if(nx>=0 && ny>=0){//四周的坐标没有越界 
				destroy[nx][ny] = min(destroy[nx][ny],t);
			}
		} 
	} 
	
	int result=bfs();
	cout<<result<<endl;
	return 0;
}

☆对干扰因素的处理

预处理阶段

预处理阶段的核心目标是提前处理好干扰因素的相关信息,将其转化为便于在 BFS 过程中使用的数据结构,从而减少 BFS 过程中的重复计算,提高算法效率。

数据收集与存储
  • 收集干扰信息 :从题目输入中收集所有与干扰因素相关的数据。例如在流星坠落问题中,需要收集每颗流星的坠落坐标 (x, y) 和坠落时间 t
  • 存储干扰信息 :根据干扰因素的特点,选择合适的数据结构来存储这些信息。在流星问题中,使用二维数组 destroyed来记录每个格子首次变得危险(被摧毁)的时间,初始时将数组元素都设为一个极大值(如 INT_MAXINF),表示初始状态下所有格子都是安全的。
干扰信息整合
  • 更新干扰状态 :遍历收集到的干扰信息,根据干扰因素的影响范围和规则,更新相应位置的干扰状态。在流星问题中,对于每颗流星,不仅要更新其坠落格子的危险时间,还要更新其周围 4 个相邻格子的危险时间,使用 min 函数确保记录的是最早的危险时间。

每个状态的干扰情况更新

在 BFS 搜索过程中,每个状态(节点)都可能受到干扰因素的影响,因此需要在每次状态转移时更新和检查干扰情况

状态表示
  • 包含干扰信息 :在 BFS 的状态表示中,除了常规的位置信息(如坐标 (x, y)),还需要包含与干扰因素相关的信息,如到达该位置的时间 t。在流星问题中,队列中存储的元素为 (x, y, t),表示贝茜在时间 t 到达位置 (x, y)
干扰情况检查
  • 判断状态合法性 :在每次状态转移时,根据预处理得到的干扰信息,检查新状态是否合法。在流星问题中,对于新位置 (newX, newY),需要检查到达该位置的新时间 newTime 是否小于该位置的 dangerTime,如果 newTime >= dangerTime[newX][newY],则说明该位置在贝茜到达时已经变得危险,该状态不合法,应跳过。

BFS 中的扩展阶段

在 BFS 的扩展阶段,需要根据干扰因素的影响,对状态的扩展规则进行调整,确保只扩展合法的状态。

扩展规则调整
  • 考虑干扰因素 :在尝试向相邻状态扩展时,要将干扰因素纳入考虑范围。在流星问题中,从当前位置 (x, y) 向四个方向(上、下、左、右)扩展时,不仅要检查新位置是否越界,还要检查新位置在新时间下是否安全。
  • 剪枝优化:利用干扰信息进行剪枝,避免不必要的状态扩展。如果某个状态已经被证明无法到达目标状态(如到达某个位置的时间已经超过该位置的危险时间),则无需对其进行进一步扩展。
记录最优信息
  • 更新最优状态 :在扩展过程中,记录到达每个合法状态的最优信息(如最短时间)。在流星问题中,使用 dist 数组记录到达每个格子的最短时间,每次到达一个新的合法位置时,更新该位置的 dist 值。

总结

在有外界干扰的 BFS 题型中,通过预处理将干扰因素转化为可查询的数据结构,在状态转移时更新和检查干扰情况,以及在扩展阶段调整扩展规则和进行剪枝优化,可以有效地处理干扰因素,提高算法的效率和正确性。不同的干扰因素可能需要不同的处理方式,但总体思路都是围绕着提前处理、动态检查和合理扩展这几个方面展开。

相关推荐
奋进的小暄10 分钟前
贪心算法(13)(java)合并区间
算法
快来卷java22 分钟前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
阿巴~阿巴~24 分钟前
C/C++蓝桥杯算法真题打卡(Day11)
算法
tpoog28 分钟前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
刚入门的大一新生1 小时前
排序算法3-交换排序
算法·排序算法
虾球xz1 小时前
游戏引擎学习第193天
c++·学习·游戏引擎
董董灿是个攻城狮2 小时前
Transformer 通关秘籍7:词向量的通俗理解
算法
牵牛老人2 小时前
C++设计模式-迭代器模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
c++·设计模式·迭代器模式
卷卷的小趴菜学编程2 小时前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法
钱彬 (Qian Bin)2 小时前
QT Quick(C++)跨平台应用程序项目实战教程 5 — 界面设计
c++·qt·教程·音乐播放器·qml·qt quick