結果

問題 No.5015 Escape from Labyrinth
ユーザー Jiro_tech15Jiro_tech15
提出日時 2023-04-15 16:26:34
言語 C++17
(gcc 12.3.0 + boost 1.83.0)
結果
WA  
実行時間 -
コード長 19,885 bytes
コンパイル時間 4,731 ms
コンパイル使用メモリ 217,284 KB
実行使用メモリ 167,268 KB
スコア 0
最終ジャッジ日時 2023-04-15 16:31:16
合計ジャッジ時間 281,718 ms
ジャッジサーバーID
(参考情報)
judge16 / judge11
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 WA -
testcase_01 WA -
testcase_02 WA -
testcase_03 WA -
testcase_04 WA -
testcase_05 WA -
testcase_06 WA -
testcase_07 WA -
testcase_08 WA -
testcase_09 WA -
testcase_10 WA -
testcase_11 WA -
testcase_12 WA -
testcase_13 WA -
testcase_14 WA -
testcase_15 WA -
testcase_16 WA -
testcase_17 WA -
testcase_18 WA -
testcase_19 WA -
testcase_20 WA -
testcase_21 WA -
testcase_22 WA -
testcase_23 WA -
testcase_24 WA -
testcase_25 WA -
testcase_26 WA -
testcase_27 WA -
testcase_28 WA -
testcase_29 WA -
testcase_30 WA -
testcase_31 WA -
testcase_32 WA -
testcase_33 WA -
testcase_34 WA -
testcase_35 WA -
testcase_36 WA -
testcase_37 WA -
testcase_38 WA -
testcase_39 WA -
testcase_40 WA -
testcase_41 WA -
testcase_42 WA -
testcase_43 WA -
testcase_44 WA -
testcase_45 WA -
testcase_46 WA -
testcase_47 WA -
testcase_48 WA -
testcase_49 WA -
testcase_50 WA -
testcase_51 WA -
testcase_52 WA -
testcase_53 WA -
testcase_54 WA -
testcase_55 WA -
testcase_56 WA -
testcase_57 WA -
testcase_58 WA -
testcase_59 WA -
testcase_60 WA -
testcase_61 WA -
testcase_62 WA -
testcase_63 WA -
testcase_64 WA -
testcase_65 WA -
testcase_66 WA -
testcase_67 WA -
testcase_68 WA -
testcase_69 WA -
testcase_70 WA -
testcase_71 WA -
testcase_72 WA -
testcase_73 WA -
testcase_74 WA -
testcase_75 WA -
testcase_76 WA -
testcase_77 WA -
testcase_78 WA -
testcase_79 WA -
testcase_80 WA -
testcase_81 WA -
testcase_82 WA -
testcase_83 WA -
testcase_84 WA -
testcase_85 WA -
testcase_86 WA -
testcase_87 WA -
testcase_88 WA -
testcase_89 WA -
testcase_90 WA -
testcase_91 WA -
testcase_92 WA -
testcase_93 WA -
testcase_94 WA -
testcase_95 WA -
testcase_96 WA -
testcase_97 WA -
testcase_98 WA -
testcase_99 WA -
権限があれば一括ダウンロードができます
コンパイルメッセージ
main.cpp: メンバ関数 ‘std::vector<Pos> Solver::Solve(int)’ 内:
main.cpp:798:3: 警告: 制御が非 void 関数の終りに到達しました [-Wreturn-type]
  798 |   }
      |   ^
main.cpp: 静的メンバ関数 ‘static void Pos::StaticInit(int)’ 内:
main.cpp:390:24: 警告: ‘dir’ may be used uninitialized [-Wmaybe-uninitialized]
  390 |             move_to[dir][p] = {adj_x, adj_y};
      |                        ^
main.cpp:377:17: 備考: ‘dir’ はここで宣言されています
  377 |             Dir dir;
      |                 ^~~

ソースコード

diff #

namespace atcoder {}

#ifdef LOCAL
#define dbg(x) cerr << __LINE__ << " : " << #x << " = " << (x) << endl;
#else
#define NDEBUG
#define dbg(x) true;
#pragma GCC target("avx2")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
#endif

#ifdef GTEST
#include <gtest/gtest.h>
#endif

#include <math.h>

#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#ifdef PERF
#include <gperftools/profiler.h>
#endif

using namespace std;
using namespace atcoder;
#define fast_io                     \
  ios_base::sync_with_stdio(false); \
  cin.tie(0);                       \
  cout.tie(0);
#define ll long long int
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
#define reps(i, n) for (int i = 1; i <= (int)(n); i++)
#define REP(i, n) for (int i = n - 1; i >= 0; i--)
#define REPS(i, n) for (int i = n; i > 0; i--)
#define MOD (long long int)(1e9 + 7)
#define INF (int)(1e9)
#define LINF (long long int)(1e18)
#define chmax(a, b) a = (((a) < (b)) ? (b) : (a))
#define chmin(a, b) a = (((a) > (b)) ? (b) : (a))
#define all(v) v.begin(), v.end()
typedef pair<int, int> Pii;
typedef pair<ll, ll> Pll;
constexpr double PI = acos(-1);

#ifdef NDEBUG
#define CHECK(v1, op, v2)
#else
#define CHECK(v1, op, v2)                            \
  if (!((v1)op(v2))) {                               \
    cerr << "ERROR:" << (v1) << " " << (v2) << endl; \
    assert((v1)op(v2));                              \
  }
#endif

long double nCr(const int n, const int r) {
  long double ret = 1;
  rep(t, r) {
    ret *= (n - t);
    ret /= (r - t);
  }
  return ret;
}

template <typename T>
string to_string(const vector<T>& vec) {
  string ret = "";
  rep(i, vec.size()) {
    ret += vec[i].to_string();
    if (i + 1 != vec.size()) {
      ret += ",";
    }
  }
  return ret;
}

template <typename T>
ostream& operator<<(ostream& os, const vector<T>& vec) {
  os << to_string(vec);
  return os;
}

uint32_t xorshift() {
  static uint32_t x = 12345789;
  static uint32_t y = 362436069;
  static uint32_t z = 521288629;
  static uint32_t w = 88675123;
  uint32_t t;
  t = x ^ (x << 11);
  x = y;
  y = z;
  z = w;
  w ^= t ^ (t >> 8) ^ (w >> 19);

  return w;
}

int rand(const uint32_t l, const uint32_t r) {
  return xorshift() % (r - l) + l;
}

uint32_t rand_other_than(const uint32_t l, const uint32_t r,
                         const uint32_t other) {
  const uint32_t num = rand(l, r - 1);
  return num + (num >= other);
}

template <typename T>
const T& rand_vec(const vector<T>& vec) {
  assert(vec.size() > 0);
  return vec[rand(0, vec.size())];
}

template <typename T>
void shuffle(vector<T>& vec) {
  rep(l, (int)vec.size() - 1) {
    const int idx = rand(l, vec.size());
    swap(vec[idx], vec[l]);
  }
}






class Timer {
  chrono::system_clock::time_point _start, _end;
  ll _sum = 0, _count = 0;

 public:
  void start() { _start = chrono::system_clock::now(); }

  void stop() { _end = chrono::system_clock::now(); }

  void add() {
    const chrono::system_clock::time_point now = chrono::system_clock::now();
    _sum += static_cast<double>(
        chrono::duration_cast<chrono::nanoseconds>(now - _start).count());
    _count++;
  }

  ll sum() const { return _sum / 1000; }

  int count() const { return _count; }

  string average() const {
    if (_count == 0) {
      return "NaN";
    }
    return to_string(_sum / 1000 / _count);
  }

  void reset() {
    _start = chrono::system_clock::now();
    _sum = 0;
    _count = 0;
  }

  inline int ms() const {
    const chrono::system_clock::time_point now = chrono::system_clock::now();
    return static_cast<double>(
        chrono::duration_cast<chrono::microseconds>(now - _start).count() /
        1000);
  }

  inline int ns() const {
    const chrono::system_clock::time_point now = chrono::system_clock::now();
    return static_cast<double>(
        chrono::duration_cast<chrono::microseconds>(now - _start).count());
  }
};

#ifdef LOCAL
struct Timers : unordered_map<string, Timer> {
  friend ostream& operator<<(ostream& os, const Timers& timers) {
    for (const auto& pa : timers) {
      os << pa.first << " time: " << pa.second.sum() / 1000
         << " count: " << pa.second.count() << endl;
    }
    return os;
  }
};
#else
struct Timers {
  struct Dummy {
    void start() const {}
    void add() const {}
  };
  Dummy dummy;
  const Dummy& operator[](const std::string& str) { return dummy; }
  friend ostream& operator<<(ostream& os, const Timers& timers) { return os; }
};
#endif

Timers global_timers;


// NBD = neighborhood
template <class Solution, class NBDGenerator>
struct SimulatedAnnealing {
 public:
  SimulatedAnnealing(double end_time, double start_temp, double end_temp)
      : end_time_(end_time),
        inv_time_(1.0 / end_time_),
        cur_time_(0.0),
        start_temp_(start_temp),
        end_temp_(end_temp),
        diff_temp_(end_temp - start_temp),
        cur_temp_(start_temp) {
    timer_.start();
  }

  Solution run(const Solution& initial_sol, NBDGenerator& nbd_generator) {
    Solution sol(initial_sol);

    int acc = 0;
    int g = 0;
    for (;; ++g) {
      // if (g % 10000 == 0) {
      //   cout << sol.Eval() << " " << sol.Score() << endl;
      //   cout << sol.Convert() << endl << endl;
      // }
      if ((g & 0xf) == 0) {
#ifdef SA_MAX_G
        if (g >= SA_MAX_G) {
          break;
        }
#endif
        UpdateTime();
        UpdateTemp();
        if (cur_time_ > end_time_) {
          break;
        }
      }

      // const auto prev_sol = sol;
      const auto nbd = nbd_generator.Generate(&sol, g);
      constexpr int mod = 1'000'000'000;
      const int r = rand(0, mod);
      if (static_cast<double>(r) / mod < exp(-nbd->Diff() / cur_temp_)) {
        acc++;
        nbd->Update(&sol);
      } else {
        // const auto mid_sol = sol;
        nbd->Restore(&sol);
        // if (prev_sol != sol) {
        //   for (const auto& obj : prev_sol.pos_pairs) {
        //     cerr << obj.Idx() << " ";
        //   }
        //   for (const auto& obj : mid_sol.pos_pairs) {
        //     cerr << obj.Idx() << " ";
        //   }
        //   for (const auto& obj : sol.pos_pairs) {
        //     cerr << obj.Idx() << " ";
        //   }
        //   abort();
        // }
      }
    }
    cerr << "g,acc: " << g << " " << acc << endl;
    return sol;
  }

 private:
  double end_time_, inv_time_, cur_time_;
  double start_temp_, end_temp_, diff_temp_, cur_temp_;
  Timer timer_;

  void UpdateTime() { cur_time_ = timer_.ms(); }
  void UpdateTemp() {
    cur_temp_ = max(0.0, start_temp_ - cur_time_ * diff_temp_ * inv_time_);
  }
};



enum Dir {
  // y-1, y+1, x-1, x+1
  kU,
  kD,
  kL,
  kR
};

struct Pos {
  int idx_;
  Pos() {}
  explicit Pos(const int _idx) : idx_(_idx) {}
  Pos(int _x, int _y) : idx_(_y * N + _x) { assert(N > 0); }
  int X() const { return pos_2_x[*this]; }
  int Y() const { return pos_2_y[*this]; }
  int Idx() const { return idx_; }
  operator int() const { return Idx(); }
  operator size_t() const { return Idx(); }
  const vector<Pos>& Adj() const { return adj_poses[*this]; }
  const vector<Dir>& AdjDirs() const { return adj_dirs[*this]; }
  int Manhattan(const Pos& other) const {
    return abs(X() - other.X()) + abs(Y() - other.Y());
  }
  bool Move(const Dir dir) {
    if (move_to[dir][*this].IsDummy()) {
      return false;
    } else {
      *this = move_to[dir][*this];
      return true;
    }
  }
  bool operator<(const Pos& other) const { return this->Idx() < other.Idx(); }
  bool operator==(const Pos& other) const { return this->Idx() == other.Idx(); }
  bool operator!=(const Pos& other) const { return this->Idx() != other.Idx(); }
  friend ostream& operator<<(ostream& os, const Pos& pos) {
    os << pos.X() << " " << pos.Y();
    return os;
  }
  bool IsDummy() const { return this->idx_ < 0; }
  static Pos Dummy() {
    Pos p;
    p.idx_ = -1;
    return p;
  }

  static Pos TryCreate(const int x, const int y, const int z) {
    if (y < 0 || y >= N || x < 0 || x >= N) {
      return Pos::Dummy();
    } else {
      return Pos(x, y);
    }
  }

  static void StaticInit(const int n) {
    N = n;
    N2 = N * N;
    N4 = N2 * N2;
    adj_poses = vector<vector<Pos>>(N2);
    adj_dirs = vector<vector<Dir>>(N2);
    move_to = vector<vector<Pos>>(4, vector<Pos>(N2, Pos::Dummy()));
    pos_2_y.resize(N2);
    pos_2_x.resize(N2);

    rep(y, N) {
      rep(x, N) {
        const Pos p{x, y};
        pos_2_y[p] = y;
        pos_2_x[p] = x;
        for (int dy = -1; dy <= 1; ++dy) {
          for (int dx = -1; dx <= 1; ++dx) {
            if (abs(dy) + abs(dx) != 1) continue;
            const int adj_y = y + dy;
            const int adj_x = x + dx;
            if (adj_y < 0 || adj_y >= N) continue;
            if (adj_x < 0 || adj_x >= N) continue;
            adj_poses[p].emplace_back(adj_x, adj_y);

            Dir dir;
            if (dy == -1) {
              dir = kU;
            } else if (dy == 1) {
              dir = kD;
            } else if (dx == -1) {
              dir = kL;
            } else if (dx == 1) {
              dir = kR;
            } else {
              assert(false);
            }

            move_to[dir][p] = {adj_x, adj_y};
            adj_dirs[p].emplace_back(dir);
          }
        }
      }
    }
  }
  static int N, N2, N4;
  static vector<vector<Pos>> adj_poses;
  static vector<vector<Dir>> adj_dirs;
  static vector<vector<Pos>> move_to;
  static vector<int> pos_2_x, pos_2_y;
};

vector<vector<Pos>> Pos::adj_poses;
vector<vector<Dir>> Pos::adj_dirs;
vector<vector<Pos>> Pos::move_to;
vector<int> Pos::pos_2_x, Pos::pos_2_y;
int Pos::N, Pos::N2, Pos::N4;









/* start */

template <typename Point, typename Dist>
class Graph_T {
 public:
  struct Edge {
    Point to;
    Dist dist;

    Edge(const Point _to, const Dist _dist) : to(_to), dist(_dist) {}
  };
  explicit Graph_T(const int n) : n_(n), adjs_(n) {}
  int N() const { return n_; }
  void AddEdge(const Point a, const Point b, const Dist dist) {
    adjs_[a].emplace_back(b, dist);
  }
  const vector<Edge>& Adj(const Point a) const { return adjs_[a]; }
  void RemoveEdge(const Point a, const Point b) {
    {
      const auto itr = std::find_if(
          all(adjs_[a]), [&](const Edge& edge) { return edge.to == b; });
      adjs_[a].erase(itr);
    }
  }

 private:
  int n_;
  vector<vector<Edge>> adjs_;
};

template <typename Point, typename Dist>
struct DijkstraData {
  Dist dist = 1e9;
  Point prev;
};

template <class Graph, typename Point, typename Dist>
vector<DijkstraData<Point, Dist>> Dijkstra(const Graph& graph,
                                           const Point& base_point) {
  vector<DijkstraData<Point, Dist>> datas(graph.N());
  using P = tuple<Dist, Point, Point>;
  priority_queue<P, vector<P>, greater<P>> pq;

  datas[base_point] = {0, base_point};
  pq.emplace(0, base_point, base_point);
  while (pq.size() > 0) {
    const auto [dist, point, prev_point] = pq.top();
    pq.pop();
    if (datas[point].dist < dist) continue;
    for (const auto& edge : graph.Adj(point)) {
      const Point to = edge.to;
      const Dist edge_dist = edge.dist;
      const Dist new_dist = dist + edge_dist;
      if (datas[to].dist > new_dist) {
        datas[to].dist = new_dist;
        datas[to].prev = point;
        pq.emplace(new_dist, to, point);
      }
    }
  }
  return datas;
}

template <typename Point, typename Dist>
vector<Point> Keiro(const vector<DijkstraData<Point, Dist>>& dijkstra_results,
                    const Point& from, const Point& to) {
  // 到達可能であることは仮定
  vector<Point> ret;
  Point p = to;
  while (p != from) {
    ret.emplace_back(p);
    p = dijkstra_results[p].prev;
  }

  ret.emplace_back(from);

  reverse(all(ret));
  return ret;
}

/* start */

struct Board {
  using Graph = Graph_T<Pos, float>;
  Board() {}
  Board(istream& is) {
    is >> N >> D >> H;
    Pos::StaticInit(N);
    rep(i, N) {
      string s;
      is >> s;
      S += s;
    }
    is >> M;
    rep(i, M) {
      int y, x, d;
      is >> y >> x >> d;
      Tanchiki tanchiki{Pos(x, y), d};
      tanchikies_.emplace_back(tanchiki);
    }

    rep(idx, Pos::N2) {
      const Pos p(idx);
      if (S[p] == 'S') {
        start_pos = p;
      } else if (S[p] == 'G') {
        goal_pos = p;
      } else if (S[p] == 'K') {
        key_pos = p;
      } else if (S[p] == 'J') {
        jewel_count++;
      }

      if (IsCheckPoint(p)) {
        check_points.emplace_back(p);
      }
    }

    InitBeamCount();
    InitDist();
  }

  void InitBeamCount() {
    pm2beams_.resize(Pos::N2, vector<int>(60));
    for (const auto& tanchiki : tanchikies_) {
      for (const auto dir : {kU, kD, kL, kR}) {
        Pos p = tanchiki.p;
        while (p.Move(dir) && CanMove(p)) {
          rep(t, 60) {
            if (t % tanchiki.d == 0) {
              pm2beams_[p][t]++;
            }
          }
        }
      }
    }
  }

  void InitDist() {
    Graph graph(Pos::N2);
    rep(idx1, Pos::N2) {
      const Pos p1(idx1);
      if (!CanMove(p1)) continue;
      for (const auto& p2 : p1.Adj()) {
        if (!CanMove(p2)) continue;
        float expected_hps =
            std::accumulate(all(pm2beams_[p2]), 0) * D / 60.0 + 1.0;
        graph.AddEdge(p1, p2, expected_hps);
      }
    }

    dists_.resize(Pos::N2);
    keiros_.resize(Pos::N2);

    rep(idx1, Pos::N2) {
      const Pos p1(idx1);
      if (!IsCheckPoint(p1)) continue;
      dists_[idx1].resize(Pos::N2, INF);
      keiros_[idx1].resize(Pos::N2);

      const auto ret = Dijkstra<Graph, Pos, float>(graph, p1);
      rep(idx2, Pos::N2) {
        const Pos p2(idx2);
        if (!IsCheckPoint(p2)) continue;

        const auto& [dist, _] = ret[idx2];
        dists_[idx1][idx2] = dist;
        if (dist < INF * 0.1) {
          keiros_[idx1][idx2] = ::Keiro(ret, p1, p2);
        }
      }
    }
  }

  bool IsCheckPoint(const Pos& p) const {
    // TODO: Fも追加
    return S[p] == 'S' || S[p] == 'G' || S[p] == 'K' || S[p] == 'J';
  }

  int BeamCount(const Pos& p, const int t) const {
    return pm2beams_[p][t % 60];
  }

  bool CanMove(const Pos& p) const {
    return S[p] != '#' && S[p] != 'B' && S[p] != 'E';
  }

  float Dist(const Pos& p1, const Pos& p2) const { return dists_[p1][p2]; }

  const vector<Pos>& Keiro(const Pos& from, const Pos& to) const {
    return keiros_[from][to];
  }

  // 消費したHPを返す
  int Simulate(const vector<Pos>& poses, int t) const {
    // posesはSとGも含む
    int hp = (int)poses.size() - 1;
    reps(i, (int)poses.size() - 1) {
      const auto& p = poses[i];
      hp += pm2beams_[p][(t + i) % 60] * D;
    }
    return hp;
  }

  static vector<Pos> Merge(const vector<vector<Pos>>& keiros) {
    assert(keiros.size() > 0);
    vector<Pos> ret;
    for (const auto& kerio : keiros) {
      assert(kerio.size() > 0);
      ret.insert(ret.end(), kerio.begin(),
                 kerio.begin() + ((int)kerio.size() - 1));
    }
    ret.emplace_back(keiros.back().back());
    return ret;
  }

  // public

  int N, D, H, M;
  string S;

  Pos start_pos, goal_pos, key_pos;
  int jewel_count = 0;

  // private
  struct Tanchiki {
    Pos p;
    int d;
  };
  vector<vector<float>> dists_;
  vector<vector<vector<Pos>>> keiros_;
  vector<Tanchiki> tanchikies_;
  vector<Pos> check_points;

  // [pos][mod]
  vector<vector<int>> pm2beams_;
};

ostream& operator<<(ostream& os, const vector<Pos> keiro) {
  rep(i, (int)keiro.size() - 1) {
    const Pos from = keiro[i];
    const Pos to = keiro[i + 1];
    if (from == to) {
      os << "S";
    } else if (from.Y() - 1 == to.Y()) {
      os << "M U";
    } else if (from.Y() + 1 == to.Y()) {
      os << "M D";
    } else if (from.X() - 1 == to.X()) {
      os << "M L";
    } else if (from.X() + 1 == to.X()) {
      os << "M R";
    }
    if (i != (int)keiro.size()) {
      os << "\n";
    }
  }
  return os;
}

/* start */

Board global_board;

struct Individual {
  vector<Pos> points;
  float hp = INF;
  bitset<3600> exists = 0;

  float DiffInsert(const Board& board, const int idx, const Pos& pos) const {
    assert(idx >= 1);
    assert(idx < (int)points.size());
    return board.Dist(points[idx - 1], pos) + board.Dist(pos, points[idx]);
  }

  void Insert(const Board& board, const int idx, const Pos& pos) {
    hp += DiffInsert(board, idx, pos);
    points.insert(points.begin() + idx, pos);
    exists[pos] = true;
  }
};

struct Population {
  vector<Individual> inds;

  Population(const Board& board) : inds(board.jewel_count + 2) {
    inds[3].points =
        vector<Pos>{board.start_pos, board.key_pos, board.goal_pos};
    inds[3].hp = board.Dist(board.start_pos, board.key_pos) +
                 board.Dist(board.key_pos, board.goal_pos);
    inds[3].exists[board.start_pos] = true;
    inds[3].exists[board.key_pos] = true;
    inds[3].exists[board.goal_pos] = true;
  }
};

struct NBD {
  float Diff() const { return diff_; }
  virtual void Update(Population*) const = 0;
  void Restore(Population*) const {}

  float diff_;
};

struct NBD_insert : public NBD {
  NBD_insert() {}
  NBD_insert(Population* const sol, const int ind_idx, const int insert_idx,
             const Pos& p) {
    ind_idx_ = ind_idx;
    insert_idx_ = insert_idx;
    p_ = p;
    const auto& ind = sol->inds[ind_idx];
    assert(!ind.exists[p]);
    diff_ = ind.DiffInsert(global_board, insert_idx, p) + ind.hp -
            sol->inds[ind_idx + 1].hp;
  }
  void Update(Population* sol) const override {
    auto ind = sol->inds[ind_idx_];
    ind.Insert(global_board, insert_idx_, p_);
    sol->inds[ind_idx_ + 1] = ind;
  }

  int ind_idx_;
  int insert_idx_;
  Pos p_;
};

struct NBDGenerator {
  NBDGenerator() {}
  NBD* Generate(Population* const sol, const int g) {
    int ind_idx = rand(0, sol->inds.size());
    while (sol->inds[ind_idx].hp == INF) {
      ind_idx = rand(0, sol->inds.size());
    }
    while (true) {
      // insert
      const int insert_idx = rand(1, sol->inds[ind_idx].points.size());
      const Pos p = rand_vec(global_board.check_points);
      if (sol->inds[ind_idx].exists[p]) continue;
      nbd_insert0 = NBD_insert(sol, ind_idx, insert_idx, p);
      return &nbd_insert0;
    }
  }

  NBD_insert nbd_insert0;
};

class Solver {
 public:
  explicit Solver(istream& is) : board_(is) { global_board = board_; }
  vector<Pos> Solve(int time_limit) {
    Timer timer;
    timer.start();

    NBDGenerator nbd_generator;
    time_limit -= timer.ms();

    SimulatedAnnealing<Population, NBDGenerator> sa(time_limit - 300, 0.3, 0.1);
    Population initial_pop(board_);
    auto best_pop = sa.run(initial_pop, nbd_generator);

    REP(i, best_pop.inds.size()) {
      vector<vector<Pos>> keiros;
      const auto& ind = best_pop.inds[i];
      if (ind.hp == INF) continue;
      rep(j, (int)ind.points.size() - 1) {
        auto keiro = board_.Keiro(ind.points[j], ind.points[j + 1]);
        keiros.emplace_back(std::move(keiro));
      }

      const auto ans = Board::Merge(keiros);

      const int hp = board_.Simulate(ans, 0);
      // TODO:正確に
      if (hp < board_.H) {
        return ans;
      }
    }

    assert(false);
  }

 private:
  Board board_;
};

int main(int argc, char* argv[]) {
  fast_io;

  if (argc >= 2) {
    // int idx = 0;
    // for (int i = 1; i < argc; ++i) {
    //   PARAMS[idx++] = std::stod(argv[i]);
    // }
  }

  Timer timer;
  timer.start();
  Solver solver(cin);
  const auto ans = solver.Solve(2950 - timer.ms());
  cout << ans << endl;

  return 0;
}
0