P2895 [USACO08FEB]Meteor Shower S
使用标记来剪枝,多几行代码少算一年(虚指).
没想到bfs也有今天.(你也得剪枝了)
此外,还有一个预处理的方法.
最初想法就是很常规的bfs,不过有很多要判断的条件,写起来比较麻烦,但是起码能写出来.细节见注释.
需要开一个存储结构体的队列,每次push的参数包含一个坐标和移动到该坐标的时刻.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <queue> using namespace std; const int INF = 100000000; int n, s[310][310], ans = -1; // burn time int dx[4] = {-1, 0, 0, 1}, dy[4] = {0, 1, -1, 0}; struct S { int x, y, t; // current state }tmp; queue<struct S> que; int main() { cin >> n; for(int i = 0; i < 310; i++) for(int j = 0; j < 310; j++) s[i][j] = INF; int a, b, c; for(int i = 0; i < n; i++) { cin >> a >> b >> c; s[a][b] = c; } que.push({0, 0, 0}); while(que.size()) { tmp = que.front(); que.pop(); if(s[tmp.x][tmp.y] == INF) { bool bad = false; for(int i = 0; i < 4; i++) { int nx = tmp.x + dx[i], ny = tmp.y + dy[i]; if(nx >= 0 && nx <= 300 && ny >= 0 && ny <= 300 && s[nx][ny] != INF ) // 当前位置安全当且仅当当前位置及其上下左右相邻格子流星坠落时间均为INF bad = true; } if(!bad) { ans = tmp.t; break; } } for(int i = 0; i < 4; i++) { int nx = tmp.x + dx[i], ny = tmp.y + dy[i]; if(nx >= 0 && nx <= 300 && ny >= 0 && ny <= 300 && tmp.t + 1 < s[nx][ny]) //此处有错误,可以移动到300x300场地以外的地方(300x300只是限制流星出现的范围) { //本文后面的代码已经修正了这个错误 bool bad = false; for(int j = 0; j < 4; j++) //检查移动到的目标格子上下左右是否已经有流星,若有则该格子已经烧焦了 { int nnx = nx + dx[j], nny = ny + dy[j]; if(nnx >= 0 && nnx <= 300 && nny >= 0 && s[nnx][nny] != INF && nny <= 300 && tmp.t + 1 >= s[nnx][nny]) { bad = true; break; } } if(!bad) que.push({nx, ny, tmp.t + 1}); } } } printf("%d ", ans); return 0; }
TLE6个点.可以看出来写出这段代码已经够痛苦了.(写代码痛苦程度与使用bad数量成正比)
有一个简单的剪枝念头就是只向下或者向右移动(y轴与数学的坐标系反向),但是很容易举出来反例.稍微思考一下后发现只需要将已经访问过的坐标标记并且以后不再访问即可,因为随着时间的增长所有格子的情况对于最终结果的影响只可能变坏或者不变,后者仅出现在该格子为终点的情况,不需要考虑.
除此之外,有一个简化代码的方案免去了所有的判断对于上下左右的格子的条件限制.见最终代码.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <queue> using namespace std; const int INF = 100000000; int n, s[310][310], ans = -1; // burn time bool vis[310][310]; // don't go back, there's no good int dx[4] = {-1, 0, 0, 1}, dy[4] = {0, 1, -1, 0}; struct S { int x, y, t; // current state }tmp; queue<struct S> que; int main() { cin >> n; for(int i = 0; i < 310; i++) for(int j = 0; j < 310; j++) s[i][j] = INF;
/*
这个方案就是对输入数据进行一些预处理,使得在输入结束后所有坐标上的值直接地表示在何时刻及之后该坐标不可用.
所以在之后的搜索中只需要对当前的格子情况做出判断就可以了,不再需要顾虑上下左右的格子.
*/ int a, b, c; for(int i = 0; i < n; i++) { cin >> a >> b >> c; s[a][b] = min(s[a][b], c); if(a > 0) s[a - 1][b] = min(s[a - 1][b], c); if(b > 0) s[a][b - 1] = min(s[a][b - 1], c); s[a + 1][b] = min(s[a + 1][b], c); s[a][b + 1] = min(s[a][b + 1], c); } que.push({0, 0, 0}); while(que.size()) { tmp = que.front(); que.pop(); if(s[tmp.x][tmp.y] == INF) { // bool bad = false; // for(int i = 0; i < 4; i++) // { // int nx = tmp.x + dx[i], ny = tmp.y + dy[i]; // if(nx >= 0 && ny >= 0 && s[nx][ny] != INF ) // bad = true; // } // if(!bad) // { ans = tmp.t; break; // } } for(int i = 0; i < 4; i++) { int nx = tmp.x + dx[i], ny = tmp.y + dy[i]; if(nx >= 0 && ny >= 0 && tmp.t + 1 < s[nx][ny]) { // bool bad = false; // for(int j = 0; j < 4; j++) // { // int nnx = nx + dx[j], nny = ny + dy[j]; // if(nnx >= 0 && nny >= 0 && s[nnx][nny] != INF && tmp.t + 1 >= s[nnx][nny]) // { // bad = true; // break; // } // } // if(!bad) if(!vis[nx][ny]) que.push({nx, ny, tmp.t + 1}); vis[nx][ny] = true; } } } printf("%d ", ans); return 0; }