ABC246E Bishop 2(bfs)
原题链接:https://atcoder.jp/contests/abc246/tasks/abc246_e
这题在考场上硬是没有调出来,当时百思不得其解,考场代码如下:
void bfs() {
memset(vis, 0, sizeof(vis));
memset(dis, inf, sizeof(dis));
queue<pair<int, int> > que;
que.push(make_pair(a1, b1));
dis[a1][b1] = 0; vis[a1][b1] = 1;
while (!que.empty()) {
pair<int, int> cur = que.front(); que.pop();
for (int i = 0; i < 4; i++) {
for (int d = 1; d <= n; d++) {
int curx = cur.first + d * dirx[i], cury = cur.second + d * diry[i];
if (!valid(curx, cury)) break;
if (s[curx][cury] == '#' || vis[curx][cury]) break; // 出错的地方
dis[curx][cury] = dis[cur.first][cur.second] + 1;
vis[curx][cury] = 1;
que.push(make_pair(curx, cury));
}
}
}
}
我当时以为,只要 \((curx, cury)\) 被插入队列的话,那么就没必要在这个方向上继续扩展下去了,因为之后的位置可以通过 \((curx, cury)\) 走到,因此答案一定不劣。
真是如此吗?其实并不一定。下面给出反例:
当 \((curx, cury)\) 刚被加入堆中且还未被取出时,通过 \((curx, cury)\) 走到的步数显然比通过 \((cur.first, cur.second)\) 直接走到的步数大一。
那知道这点之后,如何修改我们的算法?
很简单,当且仅当 \(dis[cur.first][cur.second] + 1 > dis[curx][cury]\) 时,我们才敢肯定 \((curx, cury)\) 的决策不优于 \((cur.first, cur.second)\) 的决策,才能 \(break\) 掉。
修改后的代码如下,即可通过此题(借用了 \(spfa\) 的思想):
void bfs() {
memset(vis, 0, sizeof(vis));
memset(dis, inf, sizeof(dis));
queue<pair<int, int> > que;
que.push(make_pair(a1, b1));
dis[a1][b1] = 0;
while (!que.empty()) {
pair<int, int> cur = que.front(); que.pop();
vis[cur.first][cur.second] = 0;
for (int i = 0; i < 4; i++) {
for (int d = 1; d <= n; d++) {
int curx = cur.first + d * dirx[i], cury = cur.second + d * diry[i];
if (!valid(curx, cury) || s[curx][cury] == '#') break;
if (dis[cur.first][cur.second] + 1 <= dis[curx][cury]) {
dis[curx][cury] = dis[cur.first][cur.second] + 1;
if (!vis[curx][cury]) que.push(make_pair(curx, cury)), vis[curx][cury] = 1;
} else break; // 此时才能保证当前决策不优
}
}
}
}
反思与总结:
之后进行剪枝的时候要多多考虑 corner cases,不要想当然。
这题和简单的 \(bfs\) 题不太一样,本题每次移动的距离可以大于 \(1\),所以像我考场上无脑套用 \(bfs\) 模板是不可行的,需要仔细考虑剪枝的条件。
这题错的不怨,技不如人,甘拜下风,菜就多练。