Official

J - 右折/Turn Right Editorial by MMNMM


いるマスとその向きの組を状態とし、マス \((2,2)\) で \(4\) 方向それぞれを向いている状態から探索を行うことを考えます(高橋くんのありえる動きを逆向きに辿る形になります)。

ある状態から移動できる状態は、次の(高々) \(2\) つです。

  • 後ろのマスが壁でなければ、後ろに下がる
  • 左のマスが壁ならば、左を向く

あとは、これを使って幅優先探索や深さ優先探索を行えばよいです。

時間計算量や空間計算量は \(O(N ^ 2)\) などとなり、十分高速です。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <utility>

int main() {
    using namespace std;
    unsigned N;
    cin >> N;
    vector wall_cell(N, vector<bool>(N, true));
    for (unsigned i{}; i < N; ++i) {
        string S;
        cin >> S;
        for (unsigned j{}; j < N; ++j)
            if (S[j] == '.')
                wall_cell[i][j] = false;
    }

    // 向きを表す列挙型
    enum class direction : char {
        up, left, down, right
    };
    // 向きから左を向いたときの向きを求める関数
    constexpr auto turn_left{[](const direction d) {
        switch (d) {
            case direction::up:
                return direction::left;
            case direction::left:
                return direction::down;
            case direction::down:
                return direction::right;
            case direction::right:
                return direction::up;
        }
    }};
    // 今いるマスと向きから後ろに進んだマスを返す関数
    constexpr auto go_back{[](const unsigned x, const unsigned y, const direction d) -> pair<unsigned, unsigned> {
        switch (d) {
            case direction::up:
                return {x + 1, y};
            case direction::left:
                return {x, y + 1};
            case direction::down:
                return {x - 1, y};
            case direction::right:
                return {x, y - 1};
        }
    }};
    // 今いるマスと向きから左隣のマスを返す関数
    constexpr auto go_left{[](const unsigned x, const unsigned y, const direction d) -> pair<unsigned, unsigned> {
        switch (d) {
            case direction::up:
                return {x, y - 1};
            case direction::left:
                return {x + 1, y};
            case direction::down:
                return {x, y + 1};
            case direction::right:
                return {x - 1, y};
        }
    }};

    vector visited(N, vector<char>(N));
    // (今いるマス, 向き) の組を探索済みか判定する関数
    const auto is_visited{[&visited](const unsigned x, const unsigned y, const direction d) -> bool {
        return 1 & (visited[x][y] >> static_cast<char>(d));
    }};
    // (今いるマス, 向き) の組を探索済みに更新する関数
    const auto visit{[&visited](const unsigned x, const unsigned y, const direction d) -> void {
        visited[x][y] |= 1 << static_cast<char>(d);
    }};

    queue<tuple<unsigned, unsigned, direction>> bfs;
    // ゴールのマスから探索を始める
    bfs.emplace(1, 1, direction::up);
    bfs.emplace(1, 1, direction::left);
    bfs.emplace(1, 1, direction::down);
    bfs.emplace(1, 1, direction::right);
    while (!empty(bfs)) {
        const auto [x, y, d]{bfs.front()};
        bfs.pop();
        {
            // ひとつ戻れるなら、後ろを探索
            const auto &[px, py]{go_back(x, y, d)};
            if (!wall_cell[px][py] && !is_visited(px, py, d)) {
                visit(px, py, d);
                bfs.emplace(px, py, d);
            }
        }
        {
            // 左が壁なら、左に向いた状態を探索
            const auto pd{turn_left(d)};
            const auto [wx, wy]{go_left(x, y, d)};
            if (wall_cell[wx][wy] && !is_visited(x, y, pd)) {
                visit(x, y, pd);
                bfs.emplace(x, y, pd);
            }
        }
    }

    unsigned ans{};
    for (unsigned i{}; i < N; ++i)
        for (unsigned j{}; j < N; ++j)
            // どれかの向きで探索済みなら、そのマスから到達可能
            ans += [&is_visited](unsigned i, unsigned j) {
                if (is_visited(i, j, direction::up))
                    return true;
                if (is_visited(i, j, direction::left))
                    return true;
                if (is_visited(i, j, direction::down))
                    return true;
                return is_visited(i, j, direction::right);
            }(i, j);

    cout << ans << endl;
    return 0;
}

posted:
last update: