提出 #67843994
ソースコード 拡げる
/**************************************************************/
// Euler Tour の辺を保持する差分更新ビームサーチライブラリを使うサンプルコード(hashなし版)
// 全体行数が長そうに見えるが、ライブラリ部分を除くとそれなりに短い。
// ライブラリ部分はnamespaceで囲っているので、折りたたんで読むことを推奨。
/**************************************************************/
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
#ifdef ONLINE_JUDGE
// clang-format off
#define cerr if (false) std::cerr
// clang-format on
#define NDEBUG
#endif
#include <atcoder/segtree>
using namespace std;
constexpr int n = 30;
constexpr int m = n * (n + 1) / 2;
constexpr int max_turn = 10000;
constexpr size_t beam_width = 1700;
constexpr size_t tour_capacity = 15 * beam_width;
constexpr uint32_t hash_map_capacity = 16 * 3 * beam_width;
constexpr int target_coefficient = 600;
inline int get_pyramid_index(int x, int y)
{
return x * (x - 1) / 2 + y;
}
/**************************************************************/
// プログラムの実行時間を計測するライブラリ
// インクルードした時点から計測が開始されるため、
// ユーザ側は何も気にせずtimer.getTime()を呼べば現時点の実行時間がわかる
/**************************************************************/
#pragma once
#ifndef TIMER_HPP
#define TIMER_HPP
#include <bits/stdc++.h>
// 内部のusing namespace std;が他のプログラムを破壊する可能性があるため、
// ライブラリ全体をnamespaceで囲っている。
namespace timer_library
{
using namespace std;
//@brief プログラムの実行時間を計測する
struct Timer
{
chrono::system_clock::time_point start;
Timer()
{
start = chrono::system_clock::now();
}
void reset()
{
start = chrono::system_clock::now();
}
//@brief プログラムの実行時間を取得する
//@return プログラムの実行時間(秒)
double getTime()
{
auto now = chrono::system_clock::now();
return chrono::duration<double>(now - start).count();
}
};
Timer timer;
} // namespace timer_library
using namespace timer_library;
#endif
/**************************************************************/
// Euler Tour の辺を保持する差分更新ビームサーチライブラリ
// Hashを用いた同一盤面除去をする場合は
// Hash, Action, Cost, State を自分で定義、実装して
// using BeamSearchUser = BeamSearch<Hash, Action, Cost, StateBase>;
// Hashの処理を記述するのが面倒な場合は
// Action, Cost, State を自分で定義、実装して
// using BeamSearchUser = BeamSearchNoHash<Action, Cost, StateBase>;
// と記述し、
// BeamSearchUser beam_search;
// を用いてビームサーチを実行する。
// Action以外はそれぞれのconceptに準拠している必要がある。
// テンプレートメタプログラミングによって実現しているので、
// 抽象クラスを継承する場合と比べてオーバーヘッドがかからないはず。
// eijirouさんの記事を参考にしているが、
// Evaluatorを使っていないのでどこを変えるべきかがやや明確な気がする。
// 参考: https://eijirou-kyopro.hatenablog.com/entry/2024/02/01/115639
// 要件
// ac-liblrary: https://github.com/atcoder/ac-library
// 推奨
// 単にコピペしただけでも使えるが、ymatsuxさんの分割コンパイルツールと合わせると
// ローカルのコードがすっきりしてオススメ
// https://github.com/ymatsux/competitive-programming/tree/main/combiner
/**************************************************************/
#pragma once
#ifndef EDGE_BEAM_HPP
#define EDGE_BEAM_HPP
#include <bits/stdc++.h>
#include <atcoder/segtree>
namespace edge_beam_library
{
using namespace std;
// 連想配列
// Keyにハッシュ関数を適用しない
// open addressing with linear probing
// unordered_mapよりも速い
// nは格納する要素数よりも16倍ほど大きくする
template <class Key, class T>
struct HashMap
{
public:
explicit HashMap(uint32_t n)
{
if (n % 2 == 0)
{
++n;
}
n_ = n;
valid_.resize(n_, false);
data_.resize(n_);
}
// 戻り値
// - 存在するならtrue、存在しないならfalse
// - index
pair<bool, int> get_index(Key key) const
{
Key i = key % n_;
while (valid_[i])
{
if (data_[i].first == key)
{
return {true, i};
}
if (++i == n_)
{
i = 0;
}
}
return {false, i};
}
// 指定したindexにkeyとvalueを格納する
void set(int i, Key key, T value)
{
valid_[i] = true;
data_[i] = {key, value};
}
// 指定したindexのvalueを返す
T get(int i) const
{
assert(valid_[i]);
return data_[i].second;
}
void clear()
{
fill(valid_.begin(), valid_.end(), false);
}
private:
uint32_t n_;
vector<bool> valid_;
vector<pair<Key, T>> data_;
};
template <typename HashType>
concept HashConcept = requires(HashType hash) {
{ std::is_unsigned_v<HashType> };
};
template <typename CostType>
concept CostConcept = requires(CostType cost) {
{ std::is_arithmetic_v<CostType> };
};
template <typename StateType, typename HashType, typename CostType, typename ActionType, typename SelectorType>
concept StateConcept = HashConcept<HashType> &&
CostConcept<CostType> &&
requires(StateType state, SelectorType selector) {
{ state.expand(std::declval<int>(), selector) } -> same_as<void>;
{ state.move_forward(std::declval<ActionType>()) } -> same_as<void>;
{ state.move_backward(std::declval<ActionType>()) } -> same_as<void>;
};
template <HashConcept Hash, typename Action, CostConcept Cost, template <typename> class State>
struct EdgeBeamSearch
{
// ビームサーチの設定
struct Config
{
int max_turn;
size_t beam_width;
size_t tour_capacity;
uint32_t hash_map_capacity;
// 実行可能解が見つかったらすぐに返すかどうか
// ビーム内のターンと問題文のターンが同じレイヤーで、
// かつターン数最小化問題であればtrueにする。
// そうでなければfalse
bool return_finished_immediately;
};
// 展開するノードの候補を表す構造体
struct Candidate
{
Action action;
Cost cost;
Hash hash;
int parent;
Candidate(Action action, Cost cost, Hash hash, int parent) : action(action),
cost(cost),
hash(hash),
parent(parent) {}
};
// ノードの候補から実際に追加するものを選ぶクラス
// ビーム幅の個数だけ、評価がよいものを選ぶ
// ハッシュ値が一致したものについては、評価がよいほうのみを残す
class Selector
{
public:
explicit Selector(const Config &config) : hash_to_index_(config.hash_map_capacity)
{
beam_width = config.beam_width;
candidates_.reserve(beam_width);
full_ = false;
costs_.resize(beam_width);
for (size_t i = 0; i < beam_width; ++i)
{
costs_[i] = {0, i};
}
}
// 候補を追加する
// ターン数最小化型の問題で、candidateによって実行可能解が得られる場合にのみ finished = true とする
// ビーム幅分の候補をCandidateを追加したときにsegment treeを構築する
void push(const Action &action, const Cost &cost, const Hash &hash, int parent, bool finished)
{
Candidate candidate(action, cost, hash, parent);
if (finished)
{
finished_candidates_.emplace_back(candidate);
return;
}
if (full_ && cost >= st_.all_prod().first)
{
// 保持しているどの候補よりもコストが小さくないとき
return;
}
auto [valid, i] = hash_to_index_.get_index(candidate.hash);
if (valid)
{
int j = hash_to_index_.get(i);
if (candidate.hash == candidates_[j].hash)
{
// ハッシュ値が等しいものが存在しているとき
if (full_)
{
// segment treeが構築されている場合
if (cost < st_.get(j).first)
{
candidates_[j] = candidate;
st_.set(j, {cost, j});
}
}
else
{
// segment treeが構築されていない場合
if (cost < costs_[j].first)
{
candidates_[j] = candidate;
costs_[j].first = cost;
}
}
return;
}
}
if (full_)
{
// segment treeが構築されている場合
int j = st_.all_prod().second;
hash_to_index_.set(i, candidate.hash, j);
candidates_[j] = candidate;
st_.set(j, {cost, j});
}
else
{
// segment treeが構築されていない場合
int j = candidates_.size();
hash_to_index_.set(i, candidate.hash, j);
candidates_.emplace_back(candidate);
costs_[j].first = cost;
if (candidates_.size() == beam_width)
{
// 保持している候補がビーム幅分になったときにsegment treeを構築する
full_ = true;
st_ = MaxSegtree(costs_);
}
}
}
// 選んだ候補を返す
const vector<Candidate> &select() const
{
return candidates_;
}
// 実行可能解が見つかったか
bool have_finished() const
{
return !finished_candidates_.empty();
}
// 実行可能解に到達するCandidateを返す
vector<Candidate> get_finished_candidates() const
{
return finished_candidates_;
}
// 最もよいCandidateを返す
Candidate calculate_best_candidate() const
{
if (full_)
{
size_t best = 0;
for (size_t i = 0; i < beam_width; ++i)
{
if (st_.get(i).first < st_.get(best).first)
{
best = i;
}
}
return candidates_[best];
}
else
{
size_t best = 0;
for (size_t i = 0; i < candidates_.size(); ++i)
{
if (costs_[i].first < costs_[best].first)
{
best = i;
}
}
return candidates_[best];
}
}
void clear()
{
candidates_.clear();
hash_to_index_.clear();
full_ = false;
}
void clear_finished_candidates()
{
finished_candidates_.clear();
}
private:
// 削除可能な優先度付きキュー
using MaxSegtree = atcoder::segtree<
pair<Cost, int>,
[](pair<Cost, int> a, pair<Cost, int> b)
{
if (a.first >= b.first)
{
return a;
}
else
{
return b;
}
},
[]()
{ return make_pair(numeric_limits<Cost>::min(), -1); }>;
size_t beam_width;
vector<Candidate> candidates_;
HashMap<Hash, int> hash_to_index_;
bool full_;
vector<pair<Cost, int>> costs_;
MaxSegtree st_;
vector<Candidate> finished_candidates_;
};
// Euler Tourを管理するためのクラス
class Tree
{
public:
explicit Tree(const State<Selector> &state, const Config &config) : state_(state)
{
curr_tour_.reserve(config.tour_capacity);
next_tour_.reserve(config.tour_capacity);
leaves_.reserve(config.beam_width);
buckets_.assign(config.beam_width, {});
}
// 状態を更新しながら深さ優先探索を行い、次のノードの候補を全てselectorに追加する
void dfs(Selector &selector)
{
if (curr_tour_.empty())
{
// 最初のターン
auto [cost, hash] = state_.make_initial_node();
state_.expand(0, selector);
return;
}
for (auto [leaf_index, action] : curr_tour_)
{
if (leaf_index >= 0)
{
// 葉
state_.move_forward(action);
auto &[cost, hash] = leaves_[leaf_index];
state_.expand(leaf_index, selector);
state_.move_backward(action);
}
else if (leaf_index == -1)
{
// 前進辺
state_.move_forward(action);
}
else
{
// 後退辺
state_.move_backward(action);
}
}
}
// 木を更新する
void update(const vector<Candidate> &candidates)
{
leaves_.clear();
if (curr_tour_.empty())
{
// 最初のターン
for (const Candidate &candidate : candidates)
{
curr_tour_.push_back({(int)leaves_.size(), candidate.action});
leaves_.push_back({candidate.cost, candidate.hash});
}
return;
}
for (const Candidate &candidate : candidates)
{
buckets_[candidate.parent].push_back({candidate.action, candidate.cost, candidate.hash});
}
auto it = curr_tour_.begin();
// 一本道を反復しないようにする
while (it->first == -1 && it->second == curr_tour_.back().second)
{
Action action = (it++)->second;
state_.move_forward(action);
direct_road_.push_back(action);
curr_tour_.pop_back();
}
// 葉の追加や不要な辺の削除をする
while (it != curr_tour_.end())
{
auto [leaf_index, action] = *(it++);
if (leaf_index >= 0)
{
// 葉
if (buckets_[leaf_index].empty())
{
continue;
}
next_tour_.push_back({-1, action});
for (auto [new_action, cost, hash] : buckets_[leaf_index])
{
int new_leaf_index = leaves_.size();
next_tour_.push_back({new_leaf_index, new_action});
leaves_.push_back({cost, hash});
}
buckets_[leaf_index].clear();
next_tour_.push_back({-2, action});
}
else if (leaf_index == -1)
{
// 前進辺
next_tour_.push_back({-1, action});
}
else
{
// 後退辺
auto [old_leaf_index, old_action] = next_tour_.back();
if (old_leaf_index == -1)
{
next_tour_.pop_back();
}
else
{
next_tour_.push_back({-2, action});
}
}
}
swap(curr_tour_, next_tour_);
next_tour_.clear();
}
// 根からのパスを取得する
vector<Action> calculate_path(int parent, int turn) const
{
// cerr << curr_tour_.size() << endl;
vector<Action> ret = direct_road_;
ret.reserve(turn);
for (auto [leaf_index, action] : curr_tour_)
{
if (leaf_index >= 0)
{
if (leaf_index == parent)
{
ret.push_back(action);
return ret;
}
}
else if (leaf_index == -1)
{
ret.push_back(action);
}
else
{
ret.pop_back();
}
}
assert(false);
return {};
}
private:
State<Selector> state_;
vector<pair<int, Action>> curr_tour_;
vector<pair<int, Action>> next_tour_;
vector<pair<Cost, Hash>> leaves_;
vector<vector<tuple<Action, Cost, Hash>>> buckets_;
vector<Action> direct_road_;
};
// ビームサーチを行う関数
vector<Action> beam_search(const Config &config, const State<Selector> &state)
{
Tree tree(state, config);
// 新しいノード候補の集合
Selector selector(config);
// config.return_finished_immediately が false のときに、
// 実行可能解の中で一番よいものを覚えておくための変数
// ビームサーチ内で扱うturnと問題のturnが一致しないときに使う
Cost best_cost = numeric_limits<Cost>::max();
vector<Action> best_ret;
for (int turn = 0; turn < config.max_turn; ++turn)
{
// Euler Tourでselectorに候補を追加する
tree.dfs(selector);
if (selector.have_finished())
{
// ターン数最小化型の問題で実行可能解が見つかったとき
Candidate candidate = selector.get_finished_candidates()[0];
vector<Action> ret = tree.calculate_path(candidate.parent, turn + 1);
ret.push_back(candidate.action);
if (config.return_finished_immediately)
{
return ret;
}
else
{
if (best_cost > candidate.cost)
{
best_cost = candidate.cost;
best_ret = ret;
}
}
selector.clear_finished_candidates();
}
if (selector.select().empty())
{
return best_ret;
}
assert(!selector.select().empty());
if (turn == config.max_turn - 1)
{
assert(selector.select().empty());
// ターン数固定型の問題で全ターンが終了したとき
Candidate best_candidate = selector.calculate_best_candidate();
vector<Action> ret = tree.calculate_path(best_candidate.parent, turn + 1);
ret.push_back(best_candidate.action);
return ret;
}
// 木を更新する
tree.update(selector.select());
selector.clear();
}
assert(false);
return {};
}
// StateConcept のチェックを構造体内で実施
static_assert(StateConcept<State<Selector>, Hash, Cost, Action, Selector>,
"State template must satisfy StateConcept with BeamSearch::Selector");
}; // EdgeBeamSearch
template <typename StateType, typename CostType, typename ActionType, typename SelectorType>
concept StateConceptNoHash =
CostConcept<CostType> &&
requires(StateType state, SelectorType selector) {
{ state.expand(std::declval<int>(), selector) } -> same_as<void>;
{ state.move_forward(std::declval<ActionType>()) } -> same_as<void>;
{ state.move_backward(std::declval<ActionType>()) } -> same_as<void>;
};
template <typename Action, CostConcept Cost, template <typename> class State>
struct EdgeBeamSearchNoHash
{
// ビームサーチの設定
struct Config
{
int max_turn;
size_t beam_width;
size_t tour_capacity;
// 実行可能解が見つかったらすぐに返すかどうか
// ビーム内のターンと問題文のターンが同じレイヤーで、
// かつターン数最小化問題であればtrueにする。
// そうでなければfalse
bool return_finished_immediately;
};
// 展開するノードの候補を表す構造体
struct Candidate
{
Action action;
Cost cost;
int parent;
Candidate(Action action, Cost cost, int parent) : action(action),
cost(cost),
parent(parent) {}
};
// ノードの候補から実際に追加するものを選ぶクラス
// ビーム幅の個数だけ、評価がよいものを選ぶ
// ハッシュ値が一致したものについては、評価がよいほうのみを残す
class Selector
{
public:
explicit Selector(const Config &config)
{
beam_width = config.beam_width;
candidates_.reserve(beam_width);
full_ = false;
costs_.resize(beam_width);
for (size_t i = 0; i < beam_width; ++i)
{
costs_[i] = {0, i};
}
}
// 候補を追加する
// ターン数最小化型の問題で、candidateによって実行可能解が得られる場合にのみ finished = true とする
// ビーム幅分の候補をCandidateを追加したときにsegment treeを構築する
void push(const Action &action, const Cost &cost, int parent, bool finished)
{
Candidate candidate(action, cost, parent);
if (finished)
{
finished_candidates_.emplace_back(candidate);
return;
}
if (full_ && cost >= st_.all_prod().first)
{
// 保持しているどの候補よりもコストが小さくないとき
return;
}
if (full_)
{
// segment treeが構築されている場合
int j = st_.all_prod().second;
candidates_[j] = candidate;
st_.set(j, {cost, j});
}
else
{
// segment treeが構築されていない場合
int j = candidates_.size();
candidates_.emplace_back(candidate);
costs_[j].first = cost;
if (candidates_.size() == beam_width)
{
// 保持している候補がビーム幅分になったときにsegment treeを構築する
full_ = true;
st_ = MaxSegtree(costs_);
}
}
}
// 選んだ候補を返す
const vector<Candidate> &select() const
{
return candidates_;
}
// 実行可能解が見つかったか
bool have_finished() const
{
return !finished_candidates_.empty();
}
// 実行可能解に到達するCandidateを返す
vector<Candidate> get_finished_candidates() const
{
return finished_candidates_;
}
// 最もよいCandidateを返す
Candidate calculate_best_candidate() const
{
if (full_)
{
size_t best = 0;
for (size_t i = 0; i < beam_width; ++i)
{
if (st_.get(i).first < st_.get(best).first)
{
best = i;
}
}
return candidates_[best];
}
else
{
size_t best = 0;
for (size_t i = 0; i < candidates_.size(); ++i)
{
if (costs_[i].first < costs_[best].first)
{
best = i;
}
}
return candidates_[best];
}
}
void clear()
{
candidates_.clear();
full_ = false;
}
void clear_finished_candidates()
{
finished_candidates_.clear();
}
private:
// 削除可能な優先度付きキュー
using MaxSegtree = atcoder::segtree<
pair<Cost, int>,
[](pair<Cost, int> a, pair<Cost, int> b)
{
if (a.first >= b.first)
{
return a;
}
else
{
return b;
}
},
[]()
{ return make_pair(numeric_limits<Cost>::min(), -1); }>;
size_t beam_width;
vector<Candidate> candidates_;
bool full_;
vector<pair<Cost, int>> costs_;
MaxSegtree st_;
vector<Candidate> finished_candidates_;
};
// Euler Tourを管理するためのクラス
class Tree
{
public:
explicit Tree(const State<Selector> &state, const Config &config) : state_(state)
{
curr_tour_.reserve(config.tour_capacity);
next_tour_.reserve(config.tour_capacity);
leaves_.reserve(config.beam_width);
buckets_.assign(config.beam_width, {});
}
// 状態を更新しながら深さ優先探索を行い、次のノードの候補を全てselectorに追加する
void dfs(Selector &selector)
{
if (curr_tour_.empty())
{
// 最初のターン
auto cost = state_.make_initial_node();
state_.expand(0, selector);
return;
}
for (auto [leaf_index, action] : curr_tour_)
{
if (leaf_index >= 0)
{
// 葉
state_.move_forward(action);
auto cost = leaves_[leaf_index];
state_.expand(leaf_index, selector);
state_.move_backward(action);
}
else if (leaf_index == -1)
{
// 前進辺
state_.move_forward(action);
}
else
{
// 後退辺
state_.move_backward(action);
}
}
}
// 木を更新する
void update(const vector<Candidate> &candidates)
{
leaves_.clear();
if (curr_tour_.empty())
{
// 最初のターン
for (const Candidate &candidate : candidates)
{
curr_tour_.push_back({(int)leaves_.size(), candidate.action});
leaves_.push_back(candidate.cost);
}
return;
}
for (const Candidate &candidate : candidates)
{
buckets_[candidate.parent].push_back({candidate.action, candidate.cost});
}
auto it = curr_tour_.begin();
// 一本道を反復しないようにする
while (it->first == -1 && it->second == curr_tour_.back().second)
{
Action action = (it++)->second;
state_.move_forward(action);
direct_road_.push_back(action);
curr_tour_.pop_back();
}
// 葉の追加や不要な辺の削除をする
while (it != curr_tour_.end())
{
auto [leaf_index, action] = *(it++);
if (leaf_index >= 0)
{
// 葉
if (buckets_[leaf_index].empty())
{
continue;
}
next_tour_.push_back({-1, action});
for (auto [new_action, cost] : buckets_[leaf_index])
{
int new_leaf_index = leaves_.size();
next_tour_.push_back({new_leaf_index, new_action});
leaves_.push_back(cost);
}
buckets_[leaf_index].clear();
next_tour_.push_back({-2, action});
}
else if (leaf_index == -1)
{
// 前進辺
next_tour_.push_back({-1, action});
}
else
{
// 後退辺
auto [old_leaf_index, old_action] = next_tour_.back();
if (old_leaf_index == -1)
{
next_tour_.pop_back();
}
else
{
next_tour_.push_back({-2, action});
}
}
}
swap(curr_tour_, next_tour_);
next_tour_.clear();
}
// 根からのパスを取得する
vector<Action> calculate_path(int parent, int turn) const
{
// cerr << curr_tour_.size() << endl;
vector<Action> ret = direct_road_;
ret.reserve(turn);
for (auto [leaf_index, action] : curr_tour_)
{
if (leaf_index >= 0)
{
if (leaf_index == parent)
{
ret.push_back(action);
return ret;
}
}
else if (leaf_index == -1)
{
ret.push_back(action);
}
else
{
ret.pop_back();
}
}
assert(false);
return {};
}
private:
State<Selector> state_;
vector<pair<int, Action>> curr_tour_;
vector<pair<int, Action>> next_tour_;
vector<Cost> leaves_;
vector<vector<tuple<Action, Cost>>> buckets_;
vector<Action> direct_road_;
};
// ビームサーチを行う関数
vector<Action> beam_search(const Config &config, const State<Selector> &state)
{
Tree tree(state, config);
// 新しいノード候補の集合
Selector selector(config);
// config.return_finished_immediately が false のときに、
// 実行可能解の中で一番よいものを覚えておくための変数
// ビームサーチ内で扱うturnと問題のturnが一致しないときに使う
Cost best_cost = numeric_limits<Cost>::max();
vector<Action> best_ret;
for (int turn = 0; turn < config.max_turn; ++turn)
{
// Euler Tourでselectorに候補を追加する
tree.dfs(selector);
if (selector.have_finished())
{
// ターン数最小化型の問題で実行可能解が見つかったとき
Candidate candidate = selector.get_finished_candidates()[0];
vector<Action> ret = tree.calculate_path(candidate.parent, turn + 1);
ret.push_back(candidate.action);
if (config.return_finished_immediately)
{
return ret;
}
else
{
if (best_cost > candidate.cost)
{
best_cost = candidate.cost;
best_ret = ret;
}
}
selector.clear_finished_candidates();
}
if (selector.select().empty())
{
return best_ret;
}
assert(!selector.select().empty());
if (turn == config.max_turn - 1)
{
assert(selector.select().empty());
// ターン数固定型の問題で全ターンが終了したとき
Candidate best_candidate = selector.calculate_best_candidate();
vector<Action> ret = tree.calculate_path(best_candidate.parent, turn + 1);
ret.push_back(best_candidate.action);
return ret;
}
// 木を更新する
tree.update(selector.select());
selector.clear();
}
assert(false);
return {};
}
// StateConcept のチェックを構造体内で実施
static_assert(StateConceptNoHash<State<Selector>, Cost, Action, Selector>,
"State template must satisfy StateConcept with BeamSearch::Selector");
}; // EdgeBeamSearchNoHash
} // namespace beam_search
using namespace edge_beam_library;
#endif
struct Input
{
vector<vector<int>> b;
void input()
{
b.resize(n);
for (int x = 0; x < n; ++x)
{
b[x] = vector<int>(x + 1);
for (int y = 0; y <= x; ++y)
{
cin >> b[x][y];
}
}
}
};
using Cost = int;
// 状態遷移を行うために必要な情報
// メモリ使用量をできるだけ小さくしてください
struct Action
{
int xyxy;
Action(int x1, int y1, int x2, int y2)
{
xyxy = x1 | (y1 << 8) | (x2 << 16) | (y2 << 24);
}
tuple<int, int, int, int> decode() const
{
return {xyxy & 255, (xyxy >> 8) & 255, (xyxy >> 16) & 255, xyxy >> 24};
}
bool operator==(const Action &other) const
{
return xyxy == other.xyxy;
}
};
// 深さ優先探索に沿って更新する情報をまとめたクラス
template <typename Selector>
class StateBase
{
public:
vector<vector<int>> b_;
array<pair<int, int>, m> positions_;
int target_ball_;
int potential_;
vector<int> target_ball_history_;
StateBase() = default;
explicit StateBase(const Input &input)
{
target_ball_history_.reserve(max_turn);
b_ = input.b;
for (int x = 0; x < n; ++x)
{
for (int y = 0; y <= x; ++y)
{
positions_[b_[x][y]] = {x, y};
}
}
auto new_target_ball = update_target_ball(0);
this->potential_ = 0;
this->target_ball_ = new_target_ball;
}
// Costの初期値を返す
Cost make_initial_node()
{
return 0;
}
Cost evaluate() const
{
return potential_ - target_coefficient * target_ball_;
}
// 次の状態候補を全てselectorに追加する
// 引数
// evaluator : 今の評価器
// parent : 今のノードID(次のノードにとって親となる)
void expand(int parent, Selector &selector)
{
auto push_candidate = [&](int x1, int y1, int x2, int y2)
{
assert(x1 > x2);
assert(b_[x1][y1] < b_[x2][y2]);
Action new_action(x1, y1, x2, y2);
move_forward(new_action);
auto new_target_ball = this->target_ball_;
auto new_potential = this->potential_;
auto new_cost = evaluate();
move_backward(new_action);
bool finished = (new_target_ball == m);
selector.push(new_action, new_cost, parent, finished);
};
auto [x, y] = positions_[target_ball_];
if (can_move_left(x, y))
{
push_candidate(x, y, x - 1, y - 1);
if (can_move_left(x - 1, y - 1))
{
push_candidate(x - 1, y - 1, x - 2, y - 2);
}
if (can_move_right(x - 1, y - 1))
{
push_candidate(x - 1, y - 1, x - 2, y - 1);
}
}
if (can_move_right(x, y))
{
push_candidate(x, y, x - 1, y);
if (can_move_left(x - 1, y))
{
push_candidate(x - 1, y, x - 2, y - 1);
}
if (can_move_right(x - 1, y))
{
push_candidate(x - 1, y, x - 2, y);
}
}
}
// actionを実行して次の状態に遷移する
void move_forward(Action action)
{
target_ball_history_.emplace_back(target_ball_);
auto [x1, y1, x2, y2] = action.decode();
potential_ += b_[x1][y1] - b_[x2][y2];
swap_balls(x1, y1, x2, y2);
target_ball_ = update_target_ball(target_ball_);
}
// actionを実行する前の状態に遷移する
// 今の状態は、親からactionを実行して遷移した状態である
void move_backward(Action action)
{
auto [x1, y1, x2, y2] = action.decode();
swap_balls(x1, y1, x2, y2);
potential_ -= b_[x1][y1] - b_[x2][y2];
if (!target_ball_history_.empty())
{
target_ball_ = target_ball_history_.back();
target_ball_history_.pop_back();
}
}
void swap_balls(int x1, int y1, int x2, int y2)
{
int b1 = b_[x1][y1];
int b2 = b_[x2][y2];
b_[x1][y1] = b2;
b_[x2][y2] = b1;
positions_[b2] = {x1, y1};
positions_[b1] = {x2, y2};
}
bool can_move_left(int x, int y) const
{
return y && b_[x - 1][y - 1] > b_[x][y];
}
bool can_move_right(int x, int y) const
{
return y < x && b_[x - 1][y] > b_[x][y];
}
int update_target_ball(int target_ball) const
{
while (target_ball < m)
{
auto [x, y] = positions_[target_ball];
if (can_move_left(x, y) || can_move_right(x, y))
{
break;
}
else
{
++target_ball;
}
}
return target_ball;
}
};
using BeamSearchUser = EdgeBeamSearchNoHash<Action, Cost, StateBase>;
using State = StateBase<BeamSearchUser::Selector>;
BeamSearchUser beam_search;
struct Solver
{
const Input input;
vector<Action> output;
Solver(const Input &input) : input(input) {}
void solve()
{
BeamSearchUser::Config config = {
.max_turn = max_turn,
.beam_width = beam_width,
.tour_capacity = tour_capacity,
.return_finished_immediately = true};
State state(input);
output = beam_search.beam_search(config, state);
}
void print() const
{
cout << output.size() << "\n";
for (Action action : output)
{
auto [x1, y1, x2, y2] = action.decode();
cout << x1 << " " << y1 << " " << x2 << " " << y2 << "\n";
}
}
};
int main()
{
ios::sync_with_stdio(false);
std::cin.tie(nullptr);
Input input;
input.input();
Solver solver(input);
solver.solve();
solver.print();
cerr << timer_library::timer.getTime() << " sec" << endl;
return 0;
}
提出情報
| 提出日時 |
|
| 問題 |
A - Pyramid Sorting |
| ユーザ |
thunder |
| 言語 |
C++ 23 (gcc 12.2) |
| 得点 |
13538695 |
| コード長 |
45481 Byte |
| 結果 |
AC |
| 実行時間 |
1903 ms |
| メモリ |
9740 KiB |
コンパイルエラー
Main.cpp:37:9: warning: #pragma once in main file
37 | #pragma once
| ^~~~
Main.cpp:98:9: warning: #pragma once in main file
98 | #pragma once
| ^~~~
Main.cpp: In instantiation of ‘void edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::Tree::dfs(edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::Selector&) [with Action = Action; Cost = int; State = StateBase]’:
Main.cpp:986:25: required from ‘std::vector<_Tp> edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::beam_search(const Config&, const State<Selector>&) [with Action = Action; Cost = int; State = StateBase]’
Main.cpp:1260:41: required from here
Main.cpp:826:26: warning: unused variable ‘cost’ [-Wunused-variable]
826 | auto cost = state_.make_initial_node();
| ^~~~
Main.cpp:837:30: warning: unused variable ‘cost’ [-Wunused-variable]
837 | auto cost = leaves_[leaf_index];
| ^~~~
Main.cpp: In instantiation of ‘void StateBase<Selector>::expand(int, Selector&) [with Selector = edge_beam_library::EdgeBeamSearchNoHash<Action, int, StateBase>::Selector]’:
Main.cpp:827:34: required from ‘void edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::Tree::dfs(edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::Selector&) [with Action = Action; Cost = int; State = StateBase]’
Main.cpp:986:25: required from ‘std::vector<_Tp> edge_beam_library::EdgeBeamSearchNoHash<Action, Cost, State>::beam_search(const Config&, const State<Selector>&) [with Action = Action; Cost = int; State = StateBase]’
Main.cpp:1260:41: required from here
Main.cpp:1143:18: warning: unused variable ‘new_potential’ [-Wunused-variable]
1143 | auto new_potential = this->potential_;
| ^~~~~~~~~~~~~
ジャッジ結果
| セット名 |
test_ALL |
| 得点 / 配点 |
13538695 / 15000000 |
| 結果 |
|
| セット名 |
テストケース |
| test_ALL |
test_0000.txt, test_0001.txt, test_0002.txt, test_0003.txt, test_0004.txt, test_0005.txt, test_0006.txt, test_0007.txt, test_0008.txt, test_0009.txt, test_0010.txt, test_0011.txt, test_0012.txt, test_0013.txt, test_0014.txt, test_0015.txt, test_0016.txt, test_0017.txt, test_0018.txt, test_0019.txt, test_0020.txt, test_0021.txt, test_0022.txt, test_0023.txt, test_0024.txt, test_0025.txt, test_0026.txt, test_0027.txt, test_0028.txt, test_0029.txt, test_0030.txt, test_0031.txt, test_0032.txt, test_0033.txt, test_0034.txt, test_0035.txt, test_0036.txt, test_0037.txt, test_0038.txt, test_0039.txt, test_0040.txt, test_0041.txt, test_0042.txt, test_0043.txt, test_0044.txt, test_0045.txt, test_0046.txt, test_0047.txt, test_0048.txt, test_0049.txt, test_0050.txt, test_0051.txt, test_0052.txt, test_0053.txt, test_0054.txt, test_0055.txt, test_0056.txt, test_0057.txt, test_0058.txt, test_0059.txt, test_0060.txt, test_0061.txt, test_0062.txt, test_0063.txt, test_0064.txt, test_0065.txt, test_0066.txt, test_0067.txt, test_0068.txt, test_0069.txt, test_0070.txt, test_0071.txt, test_0072.txt, test_0073.txt, test_0074.txt, test_0075.txt, test_0076.txt, test_0077.txt, test_0078.txt, test_0079.txt, test_0080.txt, test_0081.txt, test_0082.txt, test_0083.txt, test_0084.txt, test_0085.txt, test_0086.txt, test_0087.txt, test_0088.txt, test_0089.txt, test_0090.txt, test_0091.txt, test_0092.txt, test_0093.txt, test_0094.txt, test_0095.txt, test_0096.txt, test_0097.txt, test_0098.txt, test_0099.txt, test_0100.txt, test_0101.txt, test_0102.txt, test_0103.txt, test_0104.txt, test_0105.txt, test_0106.txt, test_0107.txt, test_0108.txt, test_0109.txt, test_0110.txt, test_0111.txt, test_0112.txt, test_0113.txt, test_0114.txt, test_0115.txt, test_0116.txt, test_0117.txt, test_0118.txt, test_0119.txt, test_0120.txt, test_0121.txt, test_0122.txt, test_0123.txt, test_0124.txt, test_0125.txt, test_0126.txt, test_0127.txt, test_0128.txt, test_0129.txt, test_0130.txt, test_0131.txt, test_0132.txt, test_0133.txt, test_0134.txt, test_0135.txt, test_0136.txt, test_0137.txt, test_0138.txt, test_0139.txt, test_0140.txt, test_0141.txt, test_0142.txt, test_0143.txt, test_0144.txt, test_0145.txt, test_0146.txt, test_0147.txt, test_0148.txt, test_0149.txt |
| ケース名 |
結果 |
実行時間 |
メモリ |
| test_0000.txt |
AC |
886 ms |
4792 KiB |
| test_0001.txt |
AC |
852 ms |
6028 KiB |
| test_0002.txt |
AC |
879 ms |
4912 KiB |
| test_0003.txt |
AC |
903 ms |
6008 KiB |
| test_0004.txt |
AC |
933 ms |
6056 KiB |
| test_0005.txt |
AC |
841 ms |
4744 KiB |
| test_0006.txt |
AC |
1014 ms |
6040 KiB |
| test_0007.txt |
AC |
1005 ms |
5980 KiB |
| test_0008.txt |
AC |
996 ms |
6256 KiB |
| test_0009.txt |
AC |
970 ms |
5932 KiB |
| test_0010.txt |
AC |
822 ms |
5992 KiB |
| test_0011.txt |
AC |
1103 ms |
9740 KiB |
| test_0012.txt |
AC |
1090 ms |
6628 KiB |
| test_0013.txt |
AC |
1031 ms |
6196 KiB |
| test_0014.txt |
AC |
827 ms |
4296 KiB |
| test_0015.txt |
AC |
1093 ms |
6024 KiB |
| test_0016.txt |
AC |
1371 ms |
8376 KiB |
| test_0017.txt |
AC |
785 ms |
4776 KiB |
| test_0018.txt |
AC |
804 ms |
4964 KiB |
| test_0019.txt |
AC |
943 ms |
4904 KiB |
| test_0020.txt |
AC |
884 ms |
5796 KiB |
| test_0021.txt |
AC |
1018 ms |
5984 KiB |
| test_0022.txt |
AC |
1007 ms |
8352 KiB |
| test_0023.txt |
AC |
1081 ms |
6184 KiB |
| test_0024.txt |
AC |
766 ms |
4824 KiB |
| test_0025.txt |
AC |
787 ms |
4768 KiB |
| test_0026.txt |
AC |
1090 ms |
5980 KiB |
| test_0027.txt |
AC |
1057 ms |
8668 KiB |
| test_0028.txt |
AC |
927 ms |
6164 KiB |
| test_0029.txt |
AC |
1273 ms |
6544 KiB |
| test_0030.txt |
AC |
921 ms |
5012 KiB |
| test_0031.txt |
AC |
1094 ms |
5988 KiB |
| test_0032.txt |
AC |
1088 ms |
5912 KiB |
| test_0033.txt |
AC |
1079 ms |
6056 KiB |
| test_0034.txt |
AC |
1017 ms |
6152 KiB |
| test_0035.txt |
AC |
870 ms |
6024 KiB |
| test_0036.txt |
AC |
1108 ms |
6280 KiB |
| test_0037.txt |
AC |
907 ms |
5992 KiB |
| test_0038.txt |
AC |
1110 ms |
6568 KiB |
| test_0039.txt |
AC |
1262 ms |
6688 KiB |
| test_0040.txt |
AC |
947 ms |
6012 KiB |
| test_0041.txt |
AC |
1186 ms |
6024 KiB |
| test_0042.txt |
AC |
996 ms |
5936 KiB |
| test_0043.txt |
AC |
940 ms |
6024 KiB |
| test_0044.txt |
AC |
952 ms |
6148 KiB |
| test_0045.txt |
AC |
1076 ms |
6272 KiB |
| test_0046.txt |
AC |
1012 ms |
5992 KiB |
| test_0047.txt |
AC |
979 ms |
5192 KiB |
| test_0048.txt |
AC |
886 ms |
5016 KiB |
| test_0049.txt |
AC |
1065 ms |
6324 KiB |
| test_0050.txt |
AC |
841 ms |
4964 KiB |
| test_0051.txt |
AC |
835 ms |
6144 KiB |
| test_0052.txt |
AC |
913 ms |
6024 KiB |
| test_0053.txt |
AC |
736 ms |
4720 KiB |
| test_0054.txt |
AC |
970 ms |
5912 KiB |
| test_0055.txt |
AC |
973 ms |
4960 KiB |
| test_0056.txt |
AC |
1178 ms |
8372 KiB |
| test_0057.txt |
AC |
982 ms |
5916 KiB |
| test_0058.txt |
AC |
1004 ms |
6064 KiB |
| test_0059.txt |
AC |
1164 ms |
8452 KiB |
| test_0060.txt |
AC |
1180 ms |
6048 KiB |
| test_0061.txt |
AC |
970 ms |
6016 KiB |
| test_0062.txt |
AC |
1131 ms |
6004 KiB |
| test_0063.txt |
AC |
988 ms |
5988 KiB |
| test_0064.txt |
AC |
1136 ms |
8376 KiB |
| test_0065.txt |
AC |
926 ms |
6004 KiB |
| test_0066.txt |
AC |
1088 ms |
8096 KiB |
| test_0067.txt |
AC |
1127 ms |
6252 KiB |
| test_0068.txt |
AC |
990 ms |
6036 KiB |
| test_0069.txt |
AC |
1218 ms |
8340 KiB |
| test_0070.txt |
AC |
1047 ms |
6056 KiB |
| test_0071.txt |
AC |
1154 ms |
8396 KiB |
| test_0072.txt |
AC |
985 ms |
6508 KiB |
| test_0073.txt |
AC |
1011 ms |
5984 KiB |
| test_0074.txt |
AC |
834 ms |
5040 KiB |
| test_0075.txt |
AC |
1017 ms |
6000 KiB |
| test_0076.txt |
AC |
1316 ms |
8424 KiB |
| test_0077.txt |
AC |
1345 ms |
8392 KiB |
| test_0078.txt |
AC |
1021 ms |
5992 KiB |
| test_0079.txt |
AC |
1182 ms |
6524 KiB |
| test_0080.txt |
AC |
1126 ms |
6008 KiB |
| test_0081.txt |
AC |
938 ms |
6008 KiB |
| test_0082.txt |
AC |
904 ms |
6052 KiB |
| test_0083.txt |
AC |
1133 ms |
8428 KiB |
| test_0084.txt |
AC |
1032 ms |
6076 KiB |
| test_0085.txt |
AC |
1244 ms |
8420 KiB |
| test_0086.txt |
AC |
920 ms |
5992 KiB |
| test_0087.txt |
AC |
830 ms |
4964 KiB |
| test_0088.txt |
AC |
1307 ms |
8424 KiB |
| test_0089.txt |
AC |
1098 ms |
8312 KiB |
| test_0090.txt |
AC |
1101 ms |
6060 KiB |
| test_0091.txt |
AC |
1189 ms |
6284 KiB |
| test_0092.txt |
AC |
747 ms |
4864 KiB |
| test_0093.txt |
AC |
908 ms |
6056 KiB |
| test_0094.txt |
AC |
1241 ms |
6016 KiB |
| test_0095.txt |
AC |
1043 ms |
6512 KiB |
| test_0096.txt |
AC |
1108 ms |
6016 KiB |
| test_0097.txt |
AC |
982 ms |
5964 KiB |
| test_0098.txt |
AC |
970 ms |
6020 KiB |
| test_0099.txt |
AC |
1193 ms |
8416 KiB |
| test_0100.txt |
AC |
949 ms |
6536 KiB |
| test_0101.txt |
AC |
950 ms |
6064 KiB |
| test_0102.txt |
AC |
1146 ms |
6176 KiB |
| test_0103.txt |
AC |
966 ms |
6004 KiB |
| test_0104.txt |
AC |
994 ms |
5988 KiB |
| test_0105.txt |
AC |
955 ms |
6012 KiB |
| test_0106.txt |
AC |
1034 ms |
5952 KiB |
| test_0107.txt |
AC |
1077 ms |
8380 KiB |
| test_0108.txt |
AC |
924 ms |
5968 KiB |
| test_0109.txt |
AC |
846 ms |
4992 KiB |
| test_0110.txt |
AC |
1278 ms |
8388 KiB |
| test_0111.txt |
AC |
831 ms |
4732 KiB |
| test_0112.txt |
AC |
1109 ms |
6472 KiB |
| test_0113.txt |
AC |
841 ms |
4660 KiB |
| test_0114.txt |
AC |
1054 ms |
8440 KiB |
| test_0115.txt |
AC |
1083 ms |
8680 KiB |
| test_0116.txt |
AC |
929 ms |
6424 KiB |
| test_0117.txt |
AC |
1089 ms |
5968 KiB |
| test_0118.txt |
AC |
971 ms |
5996 KiB |
| test_0119.txt |
AC |
871 ms |
6012 KiB |
| test_0120.txt |
AC |
1066 ms |
6036 KiB |
| test_0121.txt |
AC |
954 ms |
5960 KiB |
| test_0122.txt |
AC |
910 ms |
5984 KiB |
| test_0123.txt |
AC |
915 ms |
6016 KiB |
| test_0124.txt |
AC |
1024 ms |
8384 KiB |
| test_0125.txt |
AC |
1074 ms |
6544 KiB |
| test_0126.txt |
AC |
1153 ms |
8424 KiB |
| test_0127.txt |
AC |
1175 ms |
6536 KiB |
| test_0128.txt |
AC |
958 ms |
6028 KiB |
| test_0129.txt |
AC |
939 ms |
6052 KiB |
| test_0130.txt |
AC |
875 ms |
5920 KiB |
| test_0131.txt |
AC |
825 ms |
5968 KiB |
| test_0132.txt |
AC |
1903 ms |
8600 KiB |
| test_0133.txt |
AC |
1077 ms |
5992 KiB |
| test_0134.txt |
AC |
1264 ms |
6016 KiB |
| test_0135.txt |
AC |
1115 ms |
5996 KiB |
| test_0136.txt |
AC |
768 ms |
4964 KiB |
| test_0137.txt |
AC |
884 ms |
4764 KiB |
| test_0138.txt |
AC |
861 ms |
4920 KiB |
| test_0139.txt |
AC |
921 ms |
6036 KiB |
| test_0140.txt |
AC |
1038 ms |
6196 KiB |
| test_0141.txt |
AC |
1109 ms |
6432 KiB |
| test_0142.txt |
AC |
922 ms |
6120 KiB |
| test_0143.txt |
AC |
1242 ms |
8428 KiB |
| test_0144.txt |
AC |
1006 ms |
6264 KiB |
| test_0145.txt |
AC |
867 ms |
4956 KiB |
| test_0146.txt |
AC |
1175 ms |
6520 KiB |
| test_0147.txt |
AC |
779 ms |
4948 KiB |
| test_0148.txt |
AC |
866 ms |
4976 KiB |
| test_0149.txt |
AC |
1200 ms |
8448 KiB |