#define _USE_MATH_DEFINES #pragma GCC target("avx2") #pragma GCC optimize("O3") #include #include using namespace std; #ifdef _MSC_VER inline unsigned long long __builtin_ctzll(unsigned long long x) { unsigned long r; _BitScanForward64(&r, x); return r; } inline unsigned long long __builtin_clzll(unsigned long long x) { unsigned long r; _BitScanReverse64(&r, x); return 63 - r; } inline unsigned long long __builtin_ffsll(unsigned long long x) { unsigned long r; return _BitScanForward64(&r, x) ? r + 1 : 0; } inline unsigned long long __builtin_popcountll(unsigned long long x) { return __popcnt64(x); } #endif // _MSC_VER #define LP(I,S,G) for (long long int I = (S); I < (G); ++I) #define IN(X) for (int in = 0; in < X.size(); in++)cin >> X[in] #define OUT(X) for (int in = 0; in < X.size(); in++)cout << X[in]<<" " #define SORT(X) sort((X).begin(), (X).end()) #define CSORT(X,Y) sort(X.begin(), X.end(),Y) #define COPY(X,Y) copy(X.begin(), X.end(), Y.begin()) #define ALL(X,Y) for (auto (X) :(Y)) #define FULL(a) (a).begin(),(a).end() #define BFS(Q,S) for(Q.push(S);Q.size()!=0;Q.pop()) typedef long long int ll; typedef unsigned long long int ull; long long int M = 998244353; chrono::system_clock::time_point starttime; using namespace std::chrono; using namespace atcoder; #ifndef ONLINE_JUDGE #define DEBUG #endif inline float getTime() { #ifdef DEBUG return duration_cast(system_clock::now() - starttime).count() / 2; #else return duration_cast(system_clock::now() - starttime).count(); #endif } int dx[] = { -1,0,1,0 }, dy[] = { 0,1,0,-1 }; inline long long int xor128() { static long long int x = 123456789, y = 362436069, z = 521288629, w = 88675123; long long int t = (x ^ (x << 11)); x = y; y = z; z = w; return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))); } struct ZobristHash { array, 400> table; ZobristHash() { for (int i = 0; i < 400; ++i) { for (int j = 0; j < 2; ++j)table[i][j] = xor128(); } } uint64_t init() { uint64_t res = 0; for (int i = 0; i < 400; ++i)res ^= table[i][0]; return res; } uint64_t flip(uint64_t hash, int idx) { hash ^= (table[idx][0] ^ table[idx][1]); return hash; } }; namespace beam_search { // メモリの再利用を行いつつ集合を管理するクラス template class ObjectPool { public: // 配列と同じようにアクセスできる T& operator[](int i) { return data_[i]; } // 配列の長さを変更せずにメモリを確保する void reserve(size_t capacity) { data_.reserve(capacity); } // 要素を追加し、追加されたインデックスを返す int push(const T& x) { if (garbage_.empty()) { data_.push_back(x); return data_.size() - 1; } else { int i = garbage_.top(); garbage_.pop(); data_[i] = x; return i; } } // 要素を(見かけ上)削除する void pop(int i) { garbage_.push(i); } // 使用した最大のインデックス(+1)を得る // この値より少し大きい値をreserveすることでメモリの再割り当てがなくなる size_t size() { return data_.size(); } private: vector data_; stack garbage_; }; // 連想配列 // Keyにハッシュ関数を適用しない // open addressing with linear probing // unordered_mapよりも速い // nは格納する要素数よりも4~16倍ほど大きくする template struct HashMap { public: explicit HashMap(uint32_t n) { n_ = n; valid_.resize(n_, false); data_.resize(n_); } // 戻り値 // - 存在するならtrue、存在しないならfalse // - index pair 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 valid_; vector> data_; }; using Hash = uint64_t; // TODO // 状態遷移を行うために必要な情報 // メモリ使用量をできるだけ小さくしてください struct Action { char x, y, px, py; Action(char ix, char iy, char ipx, char ipy) { x = ix, y = iy; px = ipx, py = ipy; } }; using Cost = int; // TODO // 状態のコストを評価するための構造体 // メモリ使用量をできるだけ小さくしてください struct Evaluator { // TODO int score; Evaluator(int is) { score = is; } // 低いほどよい Cost evaluate() const { return score; } }; // 展開するノードの候補を表す構造体 struct Candidate { Action action; Evaluator evaluator; Hash hash; int parent; Cost cost; Candidate(Action action, Evaluator evaluator, Hash hash, int parent, Cost cost) : action(action), evaluator(evaluator), hash(hash), parent(parent), cost(cost) { } }; // ビームサーチの設定 struct Config { int max_turn; size_t beam_width; size_t nodes_capacity; uint32_t hash_map_capacity; }; // 削除可能な優先度付きキュー using MaxSegtree = atcoder::segtree < pair, [](pair a, pair b) { if (a.first >= b.first) { return a; } else { return b; } }, []() { return make_pair(numeric_limits::min(), -1); } > ; // ノードの候補から実際に追加するものを選ぶクラス // ビーム幅の個数だけ、評価がよいものを選ぶ // ハッシュ値が一致したものについては、評価がよいほうのみを残す 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; st_original_.resize(beam_width); } // 候補を追加する // ターン数最小化型の問題で、candidateによって実行可能解が得られる場合にのみ finished = true とする // ビーム幅分の候補をCandidateを追加したときにsegment treeを構築する void push(Action action, const Evaluator& evaluator, Hash hash, int parent, bool finished) { Cost cost = evaluator.evaluate(); if (finished) { finished_candidates_.emplace_back(Candidate(action, evaluator, hash, parent, cost)); return; } if (full_ && cost >= st_.all_prod().first) { // 保持しているどの候補よりもコストが小さくないとき return; } auto [valid, i] = hash_to_index_.get_index(hash); if (valid) { int j = hash_to_index_.get(i); if (hash == candidates_[j].hash) { // ハッシュ値が等しいものが存在しているとき if (cost < candidates_[j].cost) { // 更新する場合 candidates_[j] = Candidate(action, evaluator, hash, parent, cost); if (full_) { st_.set(j, { cost, j }); } } return; } } if (full_) { // segment treeが構築されている場合 int j = st_.all_prod().second; hash_to_index_.set(i, hash, j); candidates_[j] = Candidate(action, evaluator, hash, parent, cost); st_.set(j, { cost, j }); } else { // segment treeが構築されていない場合 hash_to_index_.set(i, hash, candidates_.size()); candidates_.emplace_back(Candidate(action, evaluator, hash, parent, cost)); if (candidates_.size() == beam_width) { // 保持している候補がビーム幅分になったとき construct_segment_tree(); } } } // 選んだ候補を返す const vector& select() const { return candidates_; } // 実行可能解が見つかったか bool have_finished() const { return !finished_candidates_.empty(); } // 実行可能解に到達する「候補」を返す vector get_finished_candidates() const { return finished_candidates_; } void clear() { candidates_.clear(); hash_to_index_.clear(); full_ = false; } private: size_t beam_width; vector candidates_; HashMap hash_to_index_; bool full_; vector> st_original_; MaxSegtree st_; vector finished_candidates_; void construct_segment_tree() { full_ = true; for (size_t i = 0; i < beam_width; ++i) { st_original_[i] = { candidates_[i].cost, i }; } st_ = MaxSegtree(st_original_); } }; // 深さ優先探索に沿って更新する情報をまとめたクラス class State { vector> a; bitset<400> b; ZobristHash hashTable; int x = -1, y = -1; int n = 20; public: Hash nowHash; explicit State(vector>& ia) { a = ia; nowHash = hashTable.init(); } // 次の状態候補を全てselectorに追加する // 引数 // evaluator : 今の評価器 // hash : 今のハッシュ値 // parent : 今のノードID(次のノードにとって親となる) void expand(const Evaluator& evaluator, Hash hash, int parent, Selector& selector) { if (x == -1) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { Hash next = hashTable.flip(hash, i * n + j); selector.push(Action(i, j, x, y), Evaluator(-a[i][j]), next, parent, false); } } } else { bool ok = 0; for (int i = 0; i < 4; ++i) { int nx = x + dx[i], ny = y + dy[i]; if (nx < 0 || ny < 0 || nx >= n || ny >= n)continue; if (b[nx * n + ny])continue; Hash next = hashTable.flip(hash, nx * n + ny); selector.push(Action(nx, ny, x, y), Evaluator(evaluator.evaluate() - a[nx][ny]), next, parent, false); ok = 1; } if (!ok) { selector.push(Action(x, y, x, y), Evaluator(evaluator.evaluate()), hash, parent, false); } } } // actionを実行して次の状態に遷移する void move_forward(Action action) { x = action.x, y = action.y; b[x * n + y] = 1; } // actionを実行する前の状態に遷移する // 今の状態は、親からactionを実行して遷移した状態である void move_backward(Action action) { b[x * n + y] = 0; x = action.px, y = action.py; } private: // TODO }; // 探索木(二重連鎖木)のノード struct Node { Action action; Evaluator evaluator; Hash hash; int parent, child, left, right; // 根のコンストラクタ Node(Action action, const Evaluator& evaluator, Hash hash) : action(action), evaluator(evaluator), hash(hash), parent(-1), child(-1), left(-1), right(-1) { } // 通常のコンストラクタ Node(const Candidate& candidate, int right) : action(candidate.action), evaluator(candidate.evaluator), hash(candidate.hash), parent(candidate.parent), child(-1), left(-1), right(right) { } }; // 二重連鎖木に対する操作をまとめたクラス class Tree { public: explicit Tree(const State& state, size_t nodes_capacity, const Node& root) : state_(state) { nodes_.reserve(nodes_capacity); root_ = nodes_.push(root); } // 状態を更新しながら深さ優先探索を行い、次のノードの候補を全てselectorに追加する void dfs(Selector& selector) { update_root(); int v = root_; while (true) { v = move_to_leaf(v); state_.expand(nodes_[v].evaluator, nodes_[v].hash, v, selector); v = move_to_ancestor(v); if (v == root_) { break; } v = move_to_right(v); } } // 根からノードvまでのパスを取得する vector get_path(int v) { // cerr << nodes_.size() << endl; vector path; while (nodes_[v].parent != -1) { path.push_back(nodes_[v].action); v = nodes_[v].parent; } reverse(path.begin(), path.end()); return path; } // 新しいノードを追加する int add_leaf(const Candidate& candidate) { int parent = candidate.parent; int sibling = nodes_[parent].child; int v = nodes_.push(Node(candidate, sibling)); nodes_[parent].child = v; if (sibling != -1) { nodes_[sibling].left = v; } return v; } // ノードvに子がいなかった場合、vと不要な先祖を削除する void remove_if_leaf(int v) { if (nodes_[v].child == -1) { remove_leaf(v); } } // 最も評価がよいノードを返す int get_best_leaf(const vector& last_nodes) { assert(!last_nodes.empty()); int ret = last_nodes[0]; for (int v : last_nodes) { if (nodes_[v].evaluator.evaluate() < nodes_[ret].evaluator.evaluate()) { ret = v; } } cerr << -nodes_[ret].evaluator.evaluate() << "\n"; return ret; } private: State state_; ObjectPool nodes_; int root_; // 根から一本道の部分は往復しないようにする void update_root() { int child = nodes_[root_].child; while (child != -1 && nodes_[child].right == -1) { root_ = child; state_.move_forward(nodes_[child].action); child = nodes_[child].child; } } // ノードvの子孫で、最も左にある葉に移動する int move_to_leaf(int v) { int child = nodes_[v].child; while (child != -1) { v = child; state_.move_forward(nodes_[child].action); child = nodes_[child].child; } return v; } // ノードvの先祖で、右への分岐があるところまで移動する int move_to_ancestor(int v) { while (v != root_ && nodes_[v].right == -1) { state_.move_backward(nodes_[v].action); v = nodes_[v].parent; } return v; } // ノードvの右のノードに移動する int move_to_right(int v) { state_.move_backward(nodes_[v].action); v = nodes_[v].right; state_.move_forward(nodes_[v].action); return v; } // 不要になった葉を再帰的に削除する void remove_leaf(int v) { while (true) { int left = nodes_[v].left; int right = nodes_[v].right; if (left == -1) { int parent = nodes_[v].parent; if (parent == -1) { cerr << "ERROR: root is removed" << endl; exit(-1); } nodes_.pop(v); nodes_[parent].child = right; if (right != -1) { nodes_[right].left = -1; return; } v = parent; } else { nodes_.pop(v); nodes_[left].right = right; if (right != -1) { nodes_[right].left = left; } return; } } } }; // ビームサーチを行う関数 vector beam_search(const Config& config, State state, Node root) { Tree tree(state, config.nodes_capacity, root); // 探索中のノード集合 vector curr_nodes; curr_nodes.reserve(config.beam_width); // 本来は curr_nodes = {state.root_} とすべきだが, 省略しても問題ない // 新しいノードの集合 vector next_nodes; next_nodes.reserve(config.beam_width); // 新しいノード候補の集合 Selector selector(config); 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 ret = tree.get_path(candidate.parent); ret.push_back(candidate.action); return ret; } // 新しいノードを追加する for (const Candidate& candidate : selector.select()) { next_nodes.push_back(tree.add_leaf(candidate)); } if (next_nodes.empty()) { // 新しいノードがないとき cerr << "ERROR: Failed to find any valid solution" << endl; cerr << turn << "\n"; return {}; } // 不要なノードを再帰的に削除する for (int v : curr_nodes) { tree.remove_if_leaf(v); } // ダブルバッファリングで配列を使い回す swap(curr_nodes, next_nodes); next_nodes.clear(); selector.clear(); } // ターン数固定型の問題で全ターンが終了したとき int best_leaf = tree.get_best_leaf(curr_nodes); return tree.get_path(best_leaf); } } // namespace beam_search struct Solver { int n, t; vector> a; vector ans; Solver() { cin >> n >> t; a = vector(n, vector(n)); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j)cin >> a[i][j]; } } void solve() { int max_turn = t; size_t beam_width = 1000; size_t nodes_capacity = 4 * beam_width; uint32_t hash_map_capacity = 16 * 3 * beam_width; beam_search::Config conf = beam_search::Config({ max_turn,beam_width,nodes_capacity,hash_map_capacity }); beam_search::State state(a); beam_search::Node root(beam_search::Action(-1, -1, -1, -1), beam_search::Evaluator(0), state.nowHash); ans = beam_search::beam_search(conf, state, root); for (int i = 1; i < ans.size(); ++i) { if (ans[i].x == ans[i - 1].x && ans[i].y == ans[i - 1].y) { ans.erase(ans.begin() + i, ans.end()); } } } void answer() { cout << ans.size() << "\n"; for (int i = 0; i < ans.size(); ++i) { cout << (int)ans[i].x << " " << (int)ans[i].y << "\n"; } } }; int main(int argc, char* argv[]) { starttime = chrono::system_clock::now(); ios::sync_with_stdio(false); std::cin.tie(nullptr); Solver s; s.solve(); s.answer(); exit(0); }