AOJ 2017 - Karakuri Doll
解法
行きの位置、向き、戻りの位置、向きを同時にもって遷移するだけなんだけど、結構めんどくさい。
一番楽なのは DFS で1マスずつ動かしていくのだと思う。
曲がれるタイミングは、行きの位置の前にあるのが壁で、かつ、戻りの位置を回転させたときに目の前に壁がある時。
戻りの位置の曲がる場所の候補は複数あるけど、DFS するときに、戻りの位置と行きの位置を同時に移動するのではなくて、別々に移動させてやると実装がスッキリする。
accomplish になるのは、行き・戻りの両方の位置が M にあって、行きの位置の前に壁があり、戻りの位置の前に壁がないとき。
実装上の注意
- DFS にすると手元だとオプションつけないとスタックオーバーフローするかも
- 到達したかのフラグを vector で持つと MLE するので、グローバルの bool 配列にしよう
ソースコード
#include <bits/stdc++.h> using namespace std; // direction // 0: N, 1: E, 2:S, 3:W constexpr int dx[4] = {0, 1, 0, -1}; constexpr int dy[4] = {-1, 0, 1, 0}; constexpr int dd[2] = {1, 3}; // turn right, left bool reach1[16][64][4]; // only go to kitchen bool reach[16][64][4][16][64][4]; int main() { int w, h; while(cin >> w >> h, w) { memset(reach1, false, sizeof(reach1)); memset(reach, false, sizeof(reach)); vector<string> v(h); for(auto& s : v) cin >> s; int sy = -1, sx = -1, sd = -1, gy = -1, gx = -1; for(int i = 0; i < h; ++i) { for(int j = 0; j < w; ++j) { if(v[i][j] == '#') continue; if(v[i][j] == 'K') { sy = i, sx = j; for(int d = 0; d < 4; ++d) { if(v[sy + dy[d]][sx + dx[d]] == '#') continue; sd = d; } } if(v[i][j] == 'M') { gy = i, gx = j; } } } function<bool(int, int, int)> go_to_kitchen = [&] (int y, int x, int d) { if(reach1[y][x][d]) return false; reach1[y][x][d] = true; if(y == gy && x == gx) return true; bool res = false; if(v[y + dy[d]][x + dx[d]] != '#') { res = go_to_kitchen(y + dy[d], x + dx[d], d); } else { res = go_to_kitchen(y, x, (d + 1) % 4); res |= go_to_kitchen(y, x, (d + 3) % 4); } return res; }; function<bool(int, int, int, int, int, int)> dfs = [&] (int y1, int x1, int d1, int y2, int x2, int d2) { if(reach[y1][x1][d1][y2][x2][d2]) return false; reach[y1][x1][d1][y2][x2][d2] = true; if(gy == y2 && gx == x2 && gy == y1 && gx == x1 && v[y2 - dy[d2]][x2 - dx[d2]] != '#' && v[y1 + dy[d1]][x1 + dx[d1]] == '#') { return true; } bool res = false; if(v[y1 + dy[d1]][x1 + dx[d1]] != '#') { res = dfs(y1 + dy[d1], x1 + dx[d1], d1, y2, x2, d2); } else { // can turn for(int i = 0; i < 2; ++i) { const int nd1 = (d1 + dd[i]) % 4, nd2 = (d2 + dd[i]) % 4; if(v[y2 - dy[nd2]][x2 - dx[nd2]] != '#') continue; // front must be wall res |= dfs(y1, x1, nd1, y2, x2, nd2); } } if(v[y2 + dy[d2]][x2 + dx[d2]] != '#') { res |= dfs(y1, x1, d1, y2 + dy[d2], x2 + dx[d2], d2); } return res; }; bool ac = false; for(int d = 0; d < 4; ++d) { ac |= dfs(sy, sx, sd, sy, sx, d); } if(ac) { cout << "He can accomplish his mission." << endl; } else if(go_to_kitchen(sy, sx, sd)) { cout << "He cannot return to the kitchen." << endl; } else { cout << "He cannot bring tea to his master." << endl; } } }
感想
最初の実装は行きと帰りの位置を同時に更新(前計算で曲がれる位置を求めておく感じ)してたので、実装が結構重くなってた。
片方ずつ動かすときれいにかけるんだなあ。
かなりバグったが、サンプルが強くて助かった。