ARC005 C - 器物損壊!高橋君
目次
# 問題
https://atcoder.jp/contests/arc005/tasks/arc005_3
# 入力
...
.
- - マップの縦・横の長さ
- - マスの種類
.
は道、s
はスタート地点、g
はゴール、#
は塀
# 出力
s
からg
まで到着することが可能かどうかを答える問題です.
#
は通ることが出来ませんが、2回だけ無視して通過することが出来ます.
# 解説
まず迷路系なので幅優先探索が適していそうです.
残弾数2でスタートし、塀を壊すたびにして、4近傍を移動していきます.
ゴールに到達出来たら"可能"、出来なければ"不可能"とします.
一度訪れたマスであっても
これは答えとしては正解ですが、計算量が心配です.
一度の移動で4マス可能性があり、それを全マスなので最大繰り返します.
の最大値がなのでこれは天文学的数字になり間に合いません.
そこでスタート地点からの距離を記録して行くことにしましょう.
距離といってもここでは移動回数ではなく、壁を壊した回数とします.
0からスタートし壁を通るたびにします.
すでに訪問済みのマスに到着した場合は、より短い距離で到着した場合のみ探索を継続します.
その他の場合は無駄なので探索を打ち切ります.
探索終了後g
のマスの距離が2以下であれば2回の塀の破壊で到着出来るということになります.
計算量の見積もりが難しいので適当にいくつかのサイズのマップを作って試してみます.
それぞれマップの全マス数とマス目の全訪問数です.
マップサイズ(HW) | 全訪問回数 |
---|---|
100 | 264 |
400 | 1554 |
800 | 3986 |
1600 | 8610 |
となります.
が増えるにつれて若干線形よりも早く増加しています.
なので仮にとおくと、です.
の最大値はなので程度で余裕で間に合います.
答えだけを知りたい場合はここまで十分です.
ここからは蛇足です.
計算量が見積もりにくい解答は厄介です.
実際に実行してみるまで間に合うか確信が持てないからです.
なんとか同じマス目に移動しないように答えを見つけられればすべてのマス目に1度ずつ訪問することにります.
計算量としてはです.
そこでこの問題を多点スタートの幅優先探索BFSとして考えると見通しが良くなります.
最初はs
の1点から開始するので1点スタートです.
BFSを開始する前にそのスタートベクターを一度空にし、壁に出会うたびにその壁をスタートベクターに追加していきます.
すべてのマスの探索が完了したらスタートベクターにはスタート地点から到達出来るすべての壁の座標が入っています.
もう一度先程追加していったスタートベクターのそれぞれの座標からBFSを開始します.
訪問履歴も空にしておく必要があります.
2回目の完了時にはスタートベクターにはスタート地点から壁を1つだけ壊して到達出来るすべての壁の座標が格納されています.
3回目完了時には壁2つを乗り越えた場合に行ける範囲をすべて網羅しているはずです.
この3回で見つけられなければゴールへ到達不可能ということです.
最も重要なのは、BFSの計算量もベクターを空にする計算量もですが、BFS以外のの操作がすべてBFSの外で行われているということです.
なので全計算量はたかだかの定数倍に収まります.
# 計算量
# バージョン1
実行時間: 439ms
# バージョン2
実行時間: 34ms
# 解答
# バージョン1
#define MAX_H 500
#define INFTY (MAX_H*MAX_H)
Int H, W;
Vector2 s, g;
char c_;
vector<bool> M(MAX_H * MAX_H, false);
vector<Int> dist(MAX_H*MAX_H, INFTY);
Int at(Int x, Int y) {
return y*W+x;
}
Int at(Vector2 &v) {
return v.y*W+v.x;
}
bool isValid(Vector2 &v) {
return !(v.x < 0 || v.x >= W || v.y < 0 || v.y >= H);
}
void input() {
cin >> H >> W;
loop(h,0,H) {
loop(w,0,W) {
cin >> c_;
if (c_ == '#') {
M[at(w, h)] = true;
} else if (c_ == 's') {
s.x = w, s.y = h;
} else if (c_ == 'g') {
g.x = w, g.y = h;
}
}
}
M.resize(H * W);
}
vector<Vector2> udlr = { {0, 1}, {0, -1}, {-1, 0}, {1, 0} };
bool success(Vector2 &v) {
return v == g;
}
void bfs() {
queue<Vector2> Q;
Q.push(s);
dist.at(at(s)) = 0;
while (Q.size()) {
Vector2 u = Q.front(); Q.pop();
for (auto dxdy: udlr) {
Vector2 v = u + dxdy;
if (!isValid(v)) continue;
Int cost = dist.at(at(u)) + M.at(at(v));
// 1) INFTYなら未訪問 -> 継続
// 2) dist > cost: すでに悪いの経路が探索済み -> 継続
// 3) dist <= cost: より良い経路が探索済み -> 終了
if (dist.at(at(v)) > cost) {
dist.at(at(v)) = cost;
Q.push(v);
}
}
}
}
void solve() {
bfs();
if (dist.at(at(g)) <= 2) cout << "YES";
else cout << "NO";
cout << endl;
}
int main(void) {
input();
solve();
return 0;
}
# バージョン2
#define MAX_H 500
#define INFTY (MAX_H*MAX_H)
Int H, W;
Vector2 g;
char c_;
vector<bool> M(MAX_H * MAX_H, false);
vector<Int> dist(MAX_H*MAX_H, INFTY);
vector<Vector2> starts;
#define N_BFS 3
Int at(Int x, Int y) {
return y*W+x;
}
Int at(Vector2 &v) {
return v.y*W+v.x;
}
bool isValid(Vector2 &v) {
return !(v.x < 0 || v.x >= W || v.y < 0 || v.y >= H);
}
void input() {
cin >> H >> W;
loop(h,0,H) {
loop(w,0,W) {
cin >> c_;
if (c_ == '#') {
M[at(w, h)] = true;
} else if (c_ == 's') {
starts.push_back(Vector2(w, h));
} else if (c_ == 'g') {
g.x = w, g.y = h;
}
}
}
M.resize(H * W);
}
vector<Vector2> udlr = { {0, 1}, {0, -1}, {-1, 0}, {1, 0} };
bool success(Vector2 &v) {
return v == g;
}
bool bfs() {
queue<Vector2> Q;
rep(N_BFS) {
loop(n,0,dist.size()) dist[n] = INFTY;
for(auto v: starts) {
Q.push(v);
dist.at(at(v)) = 0;
}
starts.clear();
while (Q.size()) {
Vector2 u = Q.front(); Q.pop();
for (auto dxdy: udlr) {
Vector2 v = u + dxdy;
if (!isValid(v)) continue;
if (dist.at(at(v)) < INFTY) continue;
dist.at(at(v)) = dist.at(at(u)) + 1;
if (success(v)) return true;
if (M.at(at(v))) starts.push_back(v);
else Q.push(v);
}
}
}
return false;
}
void solve() {
if (bfs()) cout << "YES";
else cout << "NO";
cout << endl;
}
int main(void) {
input();
solve();
return 0;
}