Problem p02579 (C++) — buggy submission, verdict: Time Limit Exceeded

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool is_valid_move(vector<string> &board, vector<vector<bool>> &checked, int h,
                   int w) {
  int h_size = board.size();
  int w_size = board.at(0).size();

  // 移動先が表の外
  if (h <= -1 || h >= h_size || w <= -1 || w >= w_size) {
    return false;
  }
  // 移動先が壁
  if (board.at(h).at(w) == '#') {
    return false;
  }
  // 移動先が調査済み
  if (checked.at(h).at(w) == true) {
    return false;
  }
  // それ以外は移動可能
  return true;
}

bool walkable(vector<string> &board, vector<vector<bool>> &checked,
              vector<pair<int, int>> &reachable_list, int h, int w) {
  // cout << "h, w: " << h << ", " << w << endl;
  checked.at(h).at(w) = true;
  reachable_list.push_back(make_pair(h, w));

  // ベースケース
  if (board.at(h).at(w) == 'g') {
    return true;
  }

  // 再帰ステップ
  // 上
  if (is_valid_move(board, checked, h - 1, w) &&
      walkable(board, checked, reachable_list, h - 1, w)) {
    return true;
  }
  // 右
  if (is_valid_move(board, checked, h, w + 1) &&
      walkable(board, checked, reachable_list, h, w + 1)) {
    return true;
  }
  // 下
  if (is_valid_move(board, checked, h + 1, w) &&
      walkable(board, checked, reachable_list, h + 1, w)) {
    return true;
  }
  // 左
  if (is_valid_move(board, checked, h, w - 1) &&
      walkable(board, checked, reachable_list, h, w - 1)) {
    return true;
  }

  return false;
}

void warpable(vector<string> &board, vector<vector<bool>> &checked,
              vector<pair<int, int>> &reachable_list, int h, int w) {
  vector<int> dh{-2, -1, 0, 1, 2};
  vector<int> dw{-2, -1, 0, 1, 2};

  for (int i = 0; i < (int)dh.size(); i++) {
    for (int j = 0; j < (int)dw.size(); j++) {
      int next_h = h + dh.at(i);
      int next_w = w + dw.at(j);

      if (is_valid_move(board, checked, next_h, next_w)) {
        reachable_list.push_back(make_pair(next_h, next_w));
      }
    }
  }
}

int main() {
  int h, w, ch, cw, dh, dw;
  cin >> h >> w >> ch >> cw >> dh >> dw;
  ch--;
  cw--;
  dh--;
  dw--; // 0-indexed

  // 入力受取
  vector<string> board(h);
  for (int i = 0; i < h; i++) {
    cin >> board.at(i);
  }
  // ゴールを g とする
  board.at(dh).at(dw) = 'g';
  // 移動情報の初期化
  vector<vector<bool>> checked(h, vector<bool>(w, false));

  int cost = 0;
  // コスト -1 で辿り着けるマス = スタート地点を列挙
  vector<pair<int, int>> reachable_list{make_pair(ch, cw)};

  while (1) {
    vector<pair<int, int>> dup = reachable_list;
    reachable_list = {};
    // コスト cost で辿り着けるマスを列挙
    for (pair<int, int> p : dup) {
      bool goal = walkable(board, checked, reachable_list, p.first, p.second);
      // ゴールを見つけた場合、その時点の cost がワープ利用の最小回数となる
      if (goal) {
        cout << cost << endl;
        return 0;
      }
    }

    // 徒歩でゴールが見つからなかった場合、ワープ先を探索する
    // コスト cost でたどり着いたマス reachable_list から、ワープ先を探す
    dup = reachable_list;
    reachable_list = {};
    for (pair<int, int> p : dup) {
      // ワープ先の候補を探すだけで、訪問は行わない
      warpable(board, checked, reachable_list, p.first, p.second);
    }

    // ワープ先の候補がない場合、徒歩でもワープでも到達不可能なため、探索をやめる
    if (reachable_list.size() == 0) {
      break;
    }

    // ワープ先の候補をユニークにしておく
    sort(reachable_list.begin(), reachable_list.end());
    auto itr = unique(reachable_list.begin(), reachable_list.end());
    reachable_list.erase(itr, reachable_list.end());

    // ワープ先をコスト +1 で探索する
    cost++;
  }

  cout << -1 << endl;
  return 0;
}


Error: TLE

Fixed submission:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool ok = false;

bool is_valid_move(vector<vector<string>> &board, vector<vector<bool>> &checked,
                   int h, int w) {
  int h_size = board.size();
  int w_size = board.at(0).size();

  // 移動先が表の外
  if (h <= -1 || h >= h_size || w <= -1 || w >= w_size) {
    return false;
  }
  // 移動先が壁
  if (board.at(h).at(w) == "#") {
    return false;
  }
  // 移動先が調査済み
  if (checked.at(h).at(w) == true) {
    return false;
  }
  // それ以外は移動可能
  return true;
}

void paintable(vector<vector<string>> &board, vector<vector<bool>> &checked,
               int h, int w, int num) {
  // cout << "h, w, num : " << h << ", " << w << ", " << num << endl;
  board.at(h).at(w) = to_string(num);
  checked.at(h).at(w) = true;

  // ベースケース
  //
  // 無し

  // 再帰ステップ
  // 上
  if (is_valid_move(board, checked, h - 1, w)) {
    paintable(board, checked, h - 1, w, num);
  }
  // 下
  if (is_valid_move(board, checked, h + 1, w)) {
    paintable(board, checked, h + 1, w, num);
  }
  // 左
  if (is_valid_move(board, checked, h, w - 1)) {
    paintable(board, checked, h, w - 1, num);
  }
  // 右
  if (is_valid_move(board, checked, h, w + 1)) {
    paintable(board, checked, h, w + 1, num);
  }
}

void assign_number(vector<vector<string>> &board, int h, int w) {
  vector<vector<bool>> checked(h, vector<bool>(w, false));
  int num = 1;

  for (int i = 0; i < h; i++) {
    for (int j = 0; j < w; j++) {
      if (board.at(i).at(j) == "#") {
        continue;
      }
      if (checked.at(i).at(j) == true) {
        continue;
      }
      paintable(board, checked, i, j, num);
      num++;
    }
  }
}

bool is_edge_square(vector<vector<string>> &board, int h, int w) {
  int h_size = board.size();
  int w_size = board.at(0).size();

  // 隣接する4マスの内、1マス以上が壁である
  // 上
  if (h - 1 >= 0 && board.at(h - 1).at(w) == "#") {
    return true;
  }
  // 下
  if (h + 1 < h_size && board.at(h + 1).at(w) == "#") {
    return true;
  }
  // 左
  if (w - 1 >= 0 && board.at(h).at(w - 1) == "#") {
    return true;
  }
  // 右
  if (w + 1 < w_size && board.at(h).at(w + 1) == "#") {
    return true;
  }

  return false;
}

int reachable(vector<vector<string>> &board, vector<vector<bool>> checked,
              int &goal_h, int &goal_w, int h, int w, int magic_cnt) {
  // cout << "h: " << h << ", w:" << w << ", magic_cnt: " << magic_cnt << endl;
  checked.at(h).at(w) = true;

  // ベースケース
  if (h == goal_h && w == goal_w) {
    // cout << "--> goal" << endl;
    ok = true;
    return magic_cnt;
  }

  // 再帰ステップ
  int ans = 1e8; // 十分に大きい

  // 徒歩
  // 上
  if (is_valid_move(board, checked, h - 1, w)) {
    int ret = reachable(board, checked, goal_h, goal_w, h - 1, w, magic_cnt);
    ans = min(ans, ret);
  }
  // 右
  if (is_valid_move(board, checked, h, w + 1)) {
    int ret = reachable(board, checked, goal_h, goal_w, h, w + 1, magic_cnt);
    ans = min(ans, ret);
  }
  // 下
  if (is_valid_move(board, checked, h + 1, w)) {
    int ret = reachable(board, checked, goal_h, goal_w, h + 1, w, magic_cnt);
    ans = min(ans, ret);
  }
  // 左
  if (is_valid_move(board, checked, h, w - 1)) {
    int ret = reachable(board, checked, goal_h, goal_w, h, w - 1, magic_cnt);
    ans = min(ans, ret);
  }

  // ワープ
  vector<int> dh{-2, -1, 0, 1, 2};
  vector<int> dw{-2, -1, 0, 1, 2};
  // 島の端にいる時のみ、ワープを検討する。
  // 例えば上下左右を道 "."
  // で囲まれたマスにいる場合、徒歩で1マス以上移動可能である。
  // ワープを行うより、徒歩で行けるところまで移動して、そこでワープを行った方が、
  // ワープの検討回数を減らすことができる。
  if (is_edge_square(board, h, w)) {
    for (int i = 0; i < (int)dh.size(); i++) {
      for (int j = 0; j < (int)dw.size(); j++) {
        int next_h = h + dh.at(i);
        int next_w = w + dw.at(j);

        if (is_valid_move(board, checked, next_h, next_w)) {
          // ワープ元とワープ先が同じエリアの場合はワープしない
          if (board.at(h).at(w) == board.at(next_h).at(next_w)) {
            continue;
          }

          // cout << "# move" << endl;
          // cout << "h: " << h << endl;
          // cout << "w: " << w << endl;
          // cout << "dh.at(i): " << dh.at(i) << endl;
          // cout << "dw.at(j): " << dw.at(j) << endl;
          // cout << "next_h: " << next_h << endl;
          // cout << "next_w: " << next_w << endl;
          // cout << "magic_cnt+1:" << magic_cnt+1 << endl;
          int ret = reachable(board, checked, goal_h, goal_w, next_h, next_w,
                              magic_cnt + 1);
          ans = min(ans, ret);
        }
      }
    }
  }

  return ans;
}

int main() {
  int h, w, ch, cw, dh, dw;
  cin >> h >> w >> ch >> cw >> dh >> dw;
  ch--;
  cw--;
  dh--;
  dw--; // 0-indexed

  // 表
  //
  // 型を vector<string> ではなく vector<vector<string>> としているのは、
  // 後続の処理で各マスについて、壁で分断されたエリアごとに採番を行うため。
  //
  // 採番する番号の最大値は、H,W <= 10**3 のため、
  // 以下のように交互に道が存在する場合は 500 * 500 = 25000 となる。
  //
  // *#*#...*#*#
  // #*#*...#*#*
  // *#*#...*#*#
  // #*#*...#*#*
  //
  // そのため、vector<string> つまり char 型で 0-9
  // をエリアの番号として保持可能とした場合、
  // 登録可能な数の範囲が不足するため、vector<vector<string>> としている。
  //
  vector<vector<string>> board(h, vector<string>(w));
  for (int i = 0; i < h; i++) {
    string s;
    cin >> s;
    for (int j = 0; j < w; j++) {
      board.at(i).at(j) = s.at(j);
    }
  }
  // 表の壁で分断されたエリアごとに 1-indexed で採番する
  assign_number(board, h, w);

#ifndef ONLINE_JUDGE
  for (int i = 0; i < h; i++) {
    for (int j = 0; j < w; j++) {
      cout << board.at(i).at(j) << " ";
    }
    cout << endl;
  }
#endif

  // チェック表
  vector<vector<bool>> checked(h, vector<bool>(w, false));
  int ans = reachable(board, checked, dh, dw, ch, cw, 0);

  if (ok) {
    cout << ans << endl;
  } else {
    cout << -1 << endl;
  }
  return 0;
}