P2895 [USACO08FEB]Meteor Shower S 题解
感悟与理解
1、需要上下左右进行移动的,需要使用delta
变量数组,这玩意有四个方向的,也有八个方向的,还有马走日之类的,总之,一事一议吧。
//二维坐标系中每个点的上下左右变化delta坐标
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
2、有多个方向,就需要判断是否有数组范围内,是不是出界,包括左出界,右出界,上出界来下出界。
if (nx >= 0 && ny >= 0) //本题是第一象限,所以只需要检查是不是大于等于0即可,上和右可以理解为无限制。当然,数组认为足够大就是无限制,本题目数据范围310。
3、结构体
一般向bfs队列中添加的,基本上没有原始的int型数据,一般是结构体,也很好理解,因为结构体才能保存更多信息吗,比如x,y坐标,还是到这走了几步了,就是step.
//定义结构体,x,y这个坐标
struct coord {
int x, y;
};
4、访问过的标识+从原点出发需要走几步到达这个位置
int st[N][N]; //访问过的标识+从原点出发需要走几步到达这个位置
...
//标识走过了0步,到达了这个位置
st[0][0] = 0;
...
st[nx][ny] = st[u.x][u.y] + 1;
5、预处理(关键)
为什么要预处理呢?预处理些什么东西呢?
(1) 题目中说到,每个陨石,它的破坏范围是5个(也可能在边界处小于5个),就是坐标位置一个,四周四个,共5个。如果我们只记录了陨石的落点,那肯定是不够的,因为周边的四个信息丢失了,所以我们需要一个二维数组记录每个点的信息,行和列无疑是代表坐标的x
和y
了,那么值代表什么含义呢?就是最早砸中时间。同时,由于一个点可能是被N块陨石关联到,比如它自己被砸一次,过一会又被旁边点带上一次,就是多次嘛,我们记录最早的关联时间就可以了。为什么呢?因为一旦被标识为破坏状态,我们后面就走不了了,对我们就没有意义了,我才不管它后面还被砸几次呢。
//预处理的过程
//Q:预处理的是什么?
//A:每个点,都有一个最早的破坏时间,可能是直接被砸中,也可能是被牵连~,那么,这个点,
// 是什么时间被一次覆盖到呢?需要预处理出来
//求出该点和周围点的最早摧毁时间
a[x][y] = min(a[x][y], t); //如果直接砸中的时间小于原来记录的时间,需要修改一下。
for (int j = 0; j < 4; j++) {
//可以到达的新坐标位置
int nx = x + dx[j];
int ny = y + dy[j];
//一直在第一象限行动,只要nx,ny不小于0,可以随意向右,向上去走,右和上没有边界
if (nx >= 0 && ny >= 0) a[nx][ny] = min(a[nx][ny], t);
}
6、怎么样知道哪个点是永远安全的?
如果一个点,在预处理后,还是没有被关联到,就是安全的,奶牛可以一直呆在这里~
//如果这个点为INF,表示永远不会被摧毁,已经安全了
if (a[nx][ny] == INF) return st[nx][ny] = st[u.x][u.y] + 1;
7、怎么样知道某个点在某个时间是否可以走呢?
(1)这个点没有走过。
(2)到达时间小于最早摧毁时间
//如果到达这个时间点的时间早于最早摧毁时间,可以走
if (st[u.x][u.y] + 1 < a[nx][ny] && st[nx][ny] == -1) {
st[nx][ny] = st[u.x][u.y] + 1;
q.push({nx, ny});
}
8、C++代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 310;
//二维坐标系中每个点的上下左右变化delta坐标
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
int a[N][N]; //x,y坐标的最早被流星覆盖到的时间
int st[N][N]; //访问过的标识+从原点出发需要走几步到达这个位置
//定义结构体,x,y这个坐标,到达需要step步骤
struct coord {
int x, y;
};
//广度优先搜索
int bfs() {
queue<coord> q;
//出发的原点位置
q.push({0, 0});
//标识走过了0步,到达了这个位置
st[0][0] = 0;
//广度优先搜索的框架
while (!q.empty()) {
//队列首
coord u = q.front();
//出队列
q.pop();
//四个方向
for (int i = 0; i < 4; i++) {
int nx = u.x + dx[i];
int ny = u.y + dy[i];
//出了第一象限是不可以的
if (nx < 0 || ny < 0)continue;
//如果这个点为INF,表示永远不会被摧毁,已经安全了
if (a[nx][ny] == INF) return st[nx][ny] = st[u.x][u.y] + 1;
//如果到达这个时间点的时间早于最早摧毁时间,可以走
if (st[u.x][u.y] + 1 < a[nx][ny] && st[nx][ny] == -1) {
st[nx][ny] = st[u.x][u.y] + 1;
q.push({nx, ny});
}
}
}
return -1;
}
int main() {
//初始化为INF(预求最小,先设最大)
memset(a, 0x3f, sizeof a);
//初始化为-1,表示没有走过
memset(st, -1, sizeof st);
//一共m个流星
int m;
cin >> m;
for (int i = 1; i <= m; i++) {
int x, y, t;
cin >> x >> y >> t;
//预处理的过程
//Q:预处理的是什么?
//A:每个点,都有一个最早的破坏时间,可能是直接被砸中,也可能是被牵连~,那么,这个点,
// 是什么时间被一次覆盖到呢?需要预处理出来
//求出该点和周围点的最早摧毁时间
a[x][y] = min(a[x][y], t); //如果直接砸中的时间小于原来记录的时间,需要修改一下。
for (int j = 0; j < 4; j++) {
//可以到达的新坐标位置
int nx = x + dx[j];
int ny = y + dy[j];
//一直在第一象限行动,只要nx,ny不小于0,可以随意向右,向上去走,右和上没有边界
if (nx >= 0 && ny >= 0) a[nx][ny] = min(a[nx][ny], t);
}
}
//广度优先搜索
printf("%d\n", bfs());
return 0;
}