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个。如果我们只记录了陨石的落点,那肯定是不够的,因为周边的四个信息丢失了,所以我们需要一个二维数组记录每个点的信息,行和列无疑是代表坐标的xy了,那么值代表什么含义呢?就是最早砸中时间。同时,由于一个点可能是被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
", bfs());
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15064252.html