Official
D - Snaky Walk Editorial
by
解説
D - Snaky Walk Editorial
by
yuto1115
解説
この問題は、「縦移動と横移動を \(1\) 回ずつ交互に行う」という条件(*)さえなければ単なるグリッド上の最短経路問題であり幅優先探索 (BFS) で解くことができますが、この条件が厄介です。そこで、この条件を別の条件に言い換えてみましょう。
上図のように、グリッドの各マスを市松模様に黒と白で塗ることを考えましょう。今、スタートマスが黒マスであり、最初(\(1\) 回目)の移動が縦移動であると仮定します。すると、
- 奇数回目の移動は縦移動、偶数回目の移動は横移動
- 移動経路によらず、偶数回の移動を行った後は黒マス上におり、奇数回の移動を行った後は白マス上にいる
ことから、
- 黒マスから白マスに移動する時は常に縦移動、白マスから黒マスに移動する時は常に横移動
であることが言え、逆にこれを満たす経路は条件(*)を満たします。最初の移動が横移動である場合はこの逆(黒マスから白マスへの移動は横移動、白マスから黒マスへの移動は縦移動)になります。スタートマスが白マスである場合も同様です。
よって、本問題は以下のアルゴリズムで解くことができます。
- 「黒マスから白マスへの移動は縦移動、白マスから黒マスへの移動は横移動」という条件の下でのスタートマスからゴールマスへの最短経路を求める。これは、グリッドの各マスを頂点集合、「各黒マスからその上下に隣接する白マスへの有向辺」と「各白マスからその左右に隣接する黒マスへの有向辺」を辺集合とした有向グラフ上での最短経路問題なので、BFS を用いればよい。
- 同様に、「黒マスから白マスへの移動は横移動、白マスから黒マスへの移動は縦移動」という条件の下でのスタートマスからゴールマスへの最短経路を求める。
- 1 と 2 の最小値を求めて出力する。
計算量は \(O(HW)\) です。
なお、本解説のような条件の言い換えを用いずとも、現在のマスと直前の移動方向のペアを状態と見て BFS することでも本問題を解くことができます(詳細略)。
実装例 (C++) :
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
int main() {
int h, w;
cin >> h >> w;
vector<string> s(h);
for (int i = 0; i < h; i++) cin >> s[i];
int si, sj, gi, gj;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (s[i][j] == 'S') {
si = i, sj = j;
} else if (s[i][j] == 'G') {
gi = i, gj = j;
}
}
}
int ans = inf;
vector<vector<pair<int, int>>> moves(2);
moves[0] = {{0, 1},
{0, -1}};
moves[1] = {{1, 0},
{-1, 0}};
for (int p = 0; p < 2; p++) {
vector d(h, vector<int>(w, inf));
d[si][sj] = 0;
queue<pair<int, int>> q;
q.emplace(si, sj);
while (!q.empty()) {
auto [i, j] = q.front();
q.pop();
for (auto [di, dj]: moves[(i + j + p) & 1]) {
int ni = i + di, nj = j + dj;
if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
if (s[ni][nj] == '#') continue;
if (d[ni][nj] == inf) {
d[ni][nj] = d[i][j] + 1;
q.emplace(ni, nj);
}
}
}
ans = min(ans, d[gi][gj]);
}
if (ans == inf) ans = -1;
cout << ans << endl;
}
posted:
last update: