結果

問題 No.3730 Jagged Minesweeper
コンテスト
ユーザー 👑 kencho
提出日時 2026-07-22 04:04:22
言語 C++23(gcc16)
(gcc 16.1.0 + boost 1.92.0 + ACL)
コンパイル:
g++-16 -O2 -lm -std=c++23 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 813 ms / 2,500 ms
+ 546µs
コード長 26,578 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 4,028 ms
コンパイル使用メモリ 379,340 KB
実行使用メモリ 35,220 KB
最終ジャッジ日時 2026-09-19 12:35:50
合計ジャッジ時間 30,277 ms
ジャッジサーバーID
(参考情報)
judge1_0 / judge2_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 2
other AC * 43
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

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

/*
  Heuristic solver for

      bomb at v    => (# bombs in the clipped 3x3 centered at v) <= a[v]
      no bomb at v => (# bombs in the clipped 3x3 centered at v) >= a[v]

  Equivalently, with c[v] = bombs in the other at most eight cells,

      x[v] = 1  <=>  c[v] < a[v].

  This is intentionally a genuine heuristic benchmark:
  it does NOT use the guaranteed "flip any violated cell" algorithm,
  and it does NOT optimize the exact cut/potential function.

  Main ideas:
    * simulated annealing on an adaptive weighted violation objective;
    * focus most proposals on violated cells and their neighbourhoods;
    * breakout weights: persistent violations gradually become expensive;
    * occasional pair, rectangle, and whole-component flips;
    * several initialisation patterns / restarts.

  Output: 'o' = bomb, '.' = no bomb.

  The time budget can be changed at compile time, for example:
      g++ -O3 -std=c++17 -DSA_TIME_LIMIT=8.0 sa_solver_o_dot.cpp
*/

#ifndef SA_TIME_LIMIT
#define SA_TIME_LIMIT 4.8
#endif

namespace {

constexpr int DI[8] = {-1,-1,-1,0,0,1,1,1};
constexpr int DJ[8] = {-1,0,1,-1,1,-1,0,1};
constexpr int MAX_PENALTY = 4095;
constexpr int MAX_COMPONENT_MOVE = 30000;
constexpr int MAX_RECT_MOVE = 5000;

struct FastRng {
    uint64_t s;
    explicit FastRng(uint64_t seed) : s(seed ? seed : 0x9e3779b97f4a7c15ULL) {}

    uint64_t operator()() {
        // splitmix64
        uint64_t z = (s += 0x9e3779b97f4a7c15ULL);
        z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
        z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
        return z ^ (z >> 31);
    }

    uint32_t u32() { return static_cast<uint32_t>((*this)()); }

    int uniform_int(int bound) {
        if (bound <= 1) return 0;
        return static_cast<int>((__uint128_t((*this)()) * uint64_t(bound)) >> 64);
    }

    double unit() {
        return ((*this)() >> 11) * (1.0 / 9007199254740992.0);
    }
};

struct Delta {
    int bad = 0;
    long long weighted_bad = 0;
    int l1 = 0;
    int l2 = 0;
};

struct Solver {
    int n = 0;
    int V = 0;
    vector<unsigned char> a;

    // Current state.
    vector<unsigned char> x;       // 1 = bomb
    vector<unsigned char> cnt;     // bomb count among the other at most 8 cells
    vector<unsigned char> residual;

    // Dynamic weighted local-search data.
    vector<unsigned short> penalty;
    vector<int> bad_list;
    vector<int> bad_pos;
    long long weighted_bad = 0;
    long long l1 = 0;
    long long l2 = 0;

    // Cells with a in [1,8] are movable.  a=0 is forced '.', a=9 is forced 'o'.
    vector<int> movable;
    vector<unsigned char> is_movable;

    // Connected components of movable cells in the 8-neighbour graph.
    vector<int> comp_id;
    vector<int> comp_cells;
    vector<int> comp_start;
    vector<int> comp_min_i, comp_max_i, comp_min_j, comp_max_j;

    // Scratch buffers for macro moves.
    vector<int> mark;
    int mark_stamp = 1;
    vector<int> affected;
    vector<signed char> move_add;

    FastRng rng;

    explicit Solver(int n_, vector<unsigned char> digits, uint64_t seed)
        : n(n_), V(n_*n_), a(std::move(digits)), x(V), cnt(V), residual(V),
          penalty(V, 1), bad_pos(V, -1), is_movable(V, 0), comp_id(V, -1),
          mark(V, 0), rng(seed) {
        movable.reserve(V);
        for (int id = 0; id < V; ++id) {
            if (1 <= a[id] && a[id] <= 8) {
                is_movable[id] = 1;
                movable.push_back(id);
            }
        }
        build_components();
        affected.reserve(9 * 4096);
        move_add.reserve(4096);
    }

    inline int calc_residual(int id) const {
        // x=1 is valid iff cnt<a; x=0 is valid iff cnt>=a.
        if (x[id]) return max(0, int(cnt[id]) - int(a[id]) + 1);
        return max(0, int(a[id]) - int(cnt[id]));
    }

    inline long long energy_of(const Delta& d, double progress) const {
        // Early: let violation magnitude smooth the landscape.
        // Late: strongly prioritise removing the last violated cells.
        const double q = progress * progress;
        const long long w_bad = 24 + static_cast<long long>(232.0 * q);
        return w_bad * d.weighted_bad + 8LL * d.l1 + d.l2;
    }

    void build_components() {
        vector<int> q;
        q.reserve(V);
        comp_cells.reserve(movable.size());
        comp_start.clear();

        for (int root : movable) {
            if (comp_id[root] != -1) continue;
            const int cid = static_cast<int>(comp_start.size());
            comp_start.push_back(static_cast<int>(comp_cells.size()));

            q.clear();
            q.push_back(root);
            comp_id[root] = cid;
            int head = 0;
            int mn_i = root / n, mx_i = mn_i;
            int mn_j = root % n, mx_j = mn_j;

            while (head < static_cast<int>(q.size())) {
                int id = q[head++];
                comp_cells.push_back(id);
                int i = id / n, j = id % n;
                mn_i = min(mn_i, i); mx_i = max(mx_i, i);
                mn_j = min(mn_j, j); mx_j = max(mx_j, j);

                for (int k = 0; k < 8; ++k) {
                    int ni = i + DI[k], nj = j + DJ[k];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                    int nid = ni*n + nj;
                    if (!is_movable[nid] || comp_id[nid] != -1) continue;
                    comp_id[nid] = cid;
                    q.push_back(nid);
                }
            }
            comp_min_i.push_back(mn_i); comp_max_i.push_back(mx_i);
            comp_min_j.push_back(mn_j); comp_max_j.push_back(mx_j);
        }
        comp_start.push_back(static_cast<int>(comp_cells.size()));
    }

    void clear_bad_structure() {
        bad_list.clear();
        fill(bad_pos.begin(), bad_pos.end(), -1);
        weighted_bad = l1 = l2 = 0;
    }

    void add_bad(int id) {
        if (bad_pos[id] != -1) return;
        bad_pos[id] = static_cast<int>(bad_list.size());
        bad_list.push_back(id);
    }

    void remove_bad(int id) {
        int p = bad_pos[id];
        if (p == -1) return;
        int z = bad_list.back();
        bad_list[p] = z;
        bad_pos[z] = p;
        bad_list.pop_back();
        bad_pos[id] = -1;
    }

    void rebuild_counts_and_scores() {
        fill(cnt.begin(), cnt.end(), 0);
        for (int id = 0; id < V; ++id) {
            if (!x[id]) continue;
            int i = id / n, j = id % n;
            for (int k = 0; k < 8; ++k) {
                int ni = i + DI[k], nj = j + DJ[k];
                if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                ++cnt[ni*n + nj];
            }
        }

        clear_bad_structure();
        for (int id = 0; id < V; ++id) {
            int r = calc_residual(id);
            residual[id] = static_cast<unsigned char>(r);
            if (r) {
                add_bad(id);
                weighted_bad += penalty[id];
                l1 += r;
                l2 += 1LL*r*r;
            }
        }
    }

    void initialise(int kind) {
        fill(penalty.begin(), penalty.end(), 1);
        const int phase = rng.uniform_int(2);
        const int stripe = 2 + rng.uniform_int(6);

        for (int id = 0; id < V; ++id) {
            if (a[id] == 0) {
                x[id] = 0;
                continue;
            }
            if (a[id] == 9) {
                x[id] = 1;
                continue;
            }

            int i = id / n, j = id % n;
            switch (kind % 7) {
                case 0: // unbiased random
                    x[id] = rng.u32() & 1U;
                    break;
                case 1: // threshold-biased random
                    x[id] = rng.uniform_int(10) < a[id];
                    break;
                case 2: // checkerboard
                    x[id] = static_cast<unsigned char>((i + j + phase) & 1);
                    break;
                case 3: // vertical stripes
                    x[id] = static_cast<unsigned char>(((j / stripe) + phase) & 1);
                    break;
                case 4: // horizontal stripes
                    x[id] = static_cast<unsigned char>(((i / stripe) + phase) & 1);
                    break;
                case 5: // noisy checkerboard
                    x[id] = static_cast<unsigned char>(((i + j + phase) & 1) ^ (rng.uniform_int(7) == 0));
                    break;
                default: // blocky random field
                    x[id] = static_cast<unsigned char>((((i / stripe) * 11995408973635179863ULL
                                                      + (j / stripe) * 10150724397891781847ULL
                                                      + rng.s) >> 61) & 1ULL);
                    break;
            }
        }
        rebuild_counts_and_scores();
    }

    inline void collect_single_affected(int id, int ids[9], int& m) const {
        m = 0;
        ids[m++] = id;
        int i = id / n, j = id % n;
        for (int k = 0; k < 8; ++k) {
            int ni = i + DI[k], nj = j + DJ[k];
            if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
            ids[m++] = ni*n + nj;
        }
    }

    Delta evaluate_single(int id) {
        Delta d;
        int ids[9], m;
        collect_single_affected(id, ids, m);

        long long old_w = 0;
        int old_l1 = 0, old_l2 = 0, old_bad = 0;
        for (int t = 0; t < m; ++t) {
            int z = ids[t], r = residual[z];
            if (r) {
                old_w += penalty[z];
                old_l1 += r;
                old_l2 += r*r;
                ++old_bad;
            }
        }

        int add = x[id] ? -1 : +1;
        x[id] ^= 1;
        for (int t = 1; t < m; ++t) cnt[ids[t]] += add;

        long long new_w = 0;
        int new_l1 = 0, new_l2 = 0, new_bad = 0;
        for (int t = 0; t < m; ++t) {
            int z = ids[t], r = calc_residual(z);
            if (r) {
                new_w += penalty[z];
                new_l1 += r;
                new_l2 += r*r;
                ++new_bad;
            }
        }

        for (int t = 1; t < m; ++t) cnt[ids[t]] -= add;
        x[id] ^= 1;

        d.bad = new_bad - old_bad;
        d.weighted_bad = new_w - old_w;
        d.l1 = new_l1 - old_l1;
        d.l2 = new_l2 - old_l2;
        return d;
    }

    void apply_single(int id, const Delta& d) {
        int ids[9], m;
        collect_single_affected(id, ids, m);
        int add = x[id] ? -1 : +1;
        x[id] ^= 1;
        for (int t = 1; t < m; ++t) cnt[ids[t]] += add;

        for (int t = 0; t < m; ++t) {
            int z = ids[t];
            int old_r = residual[z];
            int new_r = calc_residual(z);
            if (old_r == 0 && new_r != 0) add_bad(z);
            if (old_r != 0 && new_r == 0) remove_bad(z);
            residual[z] = static_cast<unsigned char>(new_r);
        }
        weighted_bad += d.weighted_bad;
        l1 += d.l1;
        l2 += d.l2;
    }

    void begin_macro() {
        affected.clear();
        move_add.clear();
        if (++mark_stamp == INT_MAX) {
            fill(mark.begin(), mark.end(), 0);
            mark_stamp = 1;
        }
    }

    inline void mark_affected(int id) {
        if (mark[id] == mark_stamp) return;
        mark[id] = mark_stamp;
        affected.push_back(id);
    }

    // Evaluates a set of distinct movable cells.  Leaves the move applied.
    // Caller either commit_macro() or rollback_macro().
    Delta apply_macro_tentatively(const vector<int>& flip_ids) {
        begin_macro();
        move_add.reserve(flip_ids.size());

        for (int id : flip_ids) {
            mark_affected(id);
            int i = id / n, j = id % n;
            for (int k = 0; k < 8; ++k) {
                int ni = i + DI[k], nj = j + DJ[k];
                if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                mark_affected(ni*n + nj);
            }
        }

        long long old_w = 0;
        int old_l1 = 0, old_l2 = 0, old_bad = 0;
        for (int z : affected) {
            int r = residual[z];
            if (r) {
                old_w += penalty[z];
                old_l1 += r;
                old_l2 += r*r;
                ++old_bad;
            }
        }

        for (int id : flip_ids) {
            int add = x[id] ? -1 : +1;
            move_add.push_back(static_cast<signed char>(add));
            x[id] ^= 1;
            int i = id / n, j = id % n;
            for (int k = 0; k < 8; ++k) {
                int ni = i + DI[k], nj = j + DJ[k];
                if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                cnt[ni*n + nj] += add;
            }
        }

        long long new_w = 0;
        int new_l1 = 0, new_l2 = 0, new_bad = 0;
        for (int z : affected) {
            int r = calc_residual(z);
            if (r) {
                new_w += penalty[z];
                new_l1 += r;
                new_l2 += r*r;
                ++new_bad;
            }
        }

        Delta d;
        d.bad = new_bad - old_bad;
        d.weighted_bad = new_w - old_w;
        d.l1 = new_l1 - old_l1;
        d.l2 = new_l2 - old_l2;
        return d;
    }

    void rollback_macro(const vector<int>& flip_ids) {
        for (int p = 0; p < static_cast<int>(flip_ids.size()); ++p) {
            int id = flip_ids[p];
            int add = move_add[p];
            x[id] ^= 1;
            int i = id / n, j = id % n;
            for (int k = 0; k < 8; ++k) {
                int ni = i + DI[k], nj = j + DJ[k];
                if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                cnt[ni*n + nj] -= add;
            }
        }
    }

    void commit_macro(const Delta& d) {
        for (int z : affected) {
            int old_r = residual[z];
            int new_r = calc_residual(z);
            if (old_r == 0 && new_r != 0) add_bad(z);
            if (old_r != 0 && new_r == 0) remove_bad(z);
            residual[z] = static_cast<unsigned char>(new_r);
        }
        weighted_bad += d.weighted_bad;
        l1 += d.l1;
        l2 += d.l2;
    }

    int random_movable() {
        return movable[rng.uniform_int(static_cast<int>(movable.size()))];
    }

    int random_bad() {
        return bad_list[rng.uniform_int(static_cast<int>(bad_list.size()))];
    }

    int local_candidate_around(int center, int radius = 1) {
        int ci = center / n, cj = center % n;
        for (int tries = 0; tries < 12; ++tries) {
            int ni = ci + rng.uniform_int(2*radius + 1) - radius;
            int nj = cj + rng.uniform_int(2*radius + 1) - radius;
            if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
            int id = ni*n + nj;
            if (is_movable[id]) return id;
        }
        return is_movable[center] ? center : random_movable();
    }

    int propose_single(double /*progress*/) {
        if (bad_list.empty()) return random_movable();
        uint32_t r = rng.u32() % 100U;
        if (r < 48) {
            int z = random_bad();
            return is_movable[z] ? z : local_candidate_around(z, 1);
        }
        if (r < 82) return local_candidate_around(random_bad(), r < 70 ? 1 : 2);
        return random_movable();
    }

    double calibrate_temperature(double progress) {
        vector<long long> positive;
        positive.reserve(512);
        for (int s = 0; s < 1024 && !movable.empty(); ++s) {
            int id = propose_single(progress);
            Delta d = evaluate_single(id);
            long long e = energy_of(d, progress);
            if (e > 0) positive.push_back(e);
        }
        if (positive.empty()) return 16.0;
        nth_element(positive.begin(), positive.begin() + positive.size()/2, positive.end());
        double med = static_cast<double>(positive[positive.size()/2]);
        // A median uphill single flip is initially accepted with probability about 0.62.
        return max(2.0, med / -log(0.62));
    }

    void increase_breakout_weights(int sample_limit = 50000) {
        if (bad_list.empty()) return;
        int m = static_cast<int>(bad_list.size());
        if (m <= sample_limit) {
            for (int id : bad_list) {
                if (penalty[id] < MAX_PENALTY) {
                    ++penalty[id];
                    ++weighted_bad;
                }
            }
        } else {
            // Sampling keeps this O(sample_limit) even when almost the whole board is bad.
            for (int t = 0; t < sample_limit; ++t) {
                int id = bad_list[rng.uniform_int(m)];
                if (penalty[id] < MAX_PENALTY) {
                    ++penalty[id];
                    ++weighted_bad;
                }
            }
        }
    }

    bool metropolis(long long delta_energy, double temperature, double size_scale = 1.0) {
        if (delta_energy <= 0) return true;
        double denom = max(1e-9, temperature * size_scale);
        double z = static_cast<double>(delta_energy) / denom;
        if (z >= 50.0) return false;
        return rng.unit() < exp(-z);
    }

    vector<int> make_component_move(int anchor) {
        vector<int> ids;
        int cid = (0 <= anchor && anchor < V) ? comp_id[anchor] : -1;
        if (cid < 0) return ids;
        int l = comp_start[cid], r = comp_start[cid+1];
        int sz = r-l;
        if (sz <= 0 || sz > MAX_COMPONENT_MOVE) return ids;
        ids.insert(ids.end(), comp_cells.begin()+l, comp_cells.begin()+r);
        return ids;
    }

    vector<int> make_rectangle_move(int anchor, int max_side) {
        vector<int> ids;
        int ci = anchor / n, cj = anchor % n;
        int h = 2 + rng.uniform_int(max(1, max_side-1));
        int w = 2 + rng.uniform_int(max(1, max_side-1));

        // Sometimes use a thin stripe; sometimes a roughly square patch.
        int type = rng.uniform_int(4);
        if (type == 0) h = 1 + rng.uniform_int(3);
        if (type == 1) w = 1 + rng.uniform_int(3);

        int top = max(0, min(n-h, ci - rng.uniform_int(max(1, h))));
        int left = max(0, min(n-w, cj - rng.uniform_int(max(1, w))));
        int bottom = min(n, top+h), right = min(n, left+w);

        ids.reserve(min(MAX_RECT_MOVE, (bottom-top)*(right-left)));
        for (int i = top; i < bottom; ++i) {
            for (int j = left; j < right; ++j) {
                int id = i*n+j;
                if (is_movable[id]) ids.push_back(id);
                if (static_cast<int>(ids.size()) >= MAX_RECT_MOVE) return ids;
            }
        }
        return ids;
    }

    vector<int> make_pair_move(int anchor) {
        vector<int> ids;
        int first = is_movable[anchor] ? anchor : local_candidate_around(anchor, 1);
        int second = local_candidate_around(first, 2);
        ids.push_back(first);
        if (second != first) ids.push_back(second);
        return ids;
    }

    bool try_macro(double progress, double temperature, bool force_component = false) {
        if (movable.empty()) return false;
        int anchor = bad_list.empty() ? random_movable() : random_bad();
        vector<int> ids;
        int kind = force_component ? 0 : rng.uniform_int(100);

        if (kind < 38) {
            ids = make_component_move(anchor);
            if (ids.empty()) ids = make_rectangle_move(anchor, 48);
        } else if (kind < 76) {
            int side = 6 + static_cast<int>(42.0 * (1.0-progress));
            ids = make_rectangle_move(anchor, max(6, side));
        } else {
            ids = make_pair_move(anchor);
        }

        if (ids.empty()) return false;
        Delta d = apply_macro_tentatively(ids);
        long long e = energy_of(d, progress);
        // Large-neighbourhood proposals are tempered by sqrt(size), otherwise
        // almost every non-improving patch would have zero acceptance probability.
        double scale = max(1.0, sqrt(static_cast<double>(ids.size())));
        if (metropolis(e, temperature, scale)) {
            commit_macro(d);
            return true;
        }
        rollback_macro(ids);
        return false;
    }

    bool verify_current() const {
        for (int id = 0; id < V; ++id) {
            if (calc_residual(id) != 0) return false;
        }
        return true;
    }

    vector<unsigned char> run(double time_limit_seconds) {
        if (movable.empty()) {
            for (int id = 0; id < V; ++id) x[id] = (a[id] == 9);
            rebuild_counts_and_scores();
            return x;
        }

        const auto time_begin = chrono::steady_clock::now();
        auto elapsed = [&]() {
            return chrono::duration<double>(chrono::steady_clock::now()-time_begin).count();
        };

        vector<unsigned char> best_x(V, 0);
        int best_bad = INT_MAX;
        long long best_l1 = LLONG_MAX;
        long long last_snapshot_iter = -1000000;
        int restart = 0;

        auto snapshot_if_better = [&](long long iter, bool force = false) {
            int cur_bad = static_cast<int>(bad_list.size());
            if (cur_bad < best_bad || (cur_bad == best_bad && l1 < best_l1)) {
                bool substantial = force || best_bad == INT_MAX || cur_bad == 0
                                || cur_bad + max(1, best_bad/100) <= best_bad
                                || iter-last_snapshot_iter >= 200000;
                // best_bad / best_l1 always describe best_x.  Do not advance the
                // recorded metric unless the corresponding board is copied too.
                if (substantial) {
                    best_bad = cur_bad;
                    best_l1 = l1;
                    best_x = x;
                    last_snapshot_iter = iter;
                }
            }
        };

        while (elapsed() < time_limit_seconds) {
            initialise(restart++);
            long long iter = 0;
            long long last_bad_improvement_iter = 0;
            int local_best_bad = static_cast<int>(bad_list.size());
            int weight_bumps = 0;
            double global_progress = min(1.0, elapsed()/time_limit_seconds);
            double T0 = calibrate_temperature(global_progress);
            double reheat = 1.0;
            snapshot_if_better(iter, true);

            while (elapsed() < time_limit_seconds) {
                if (bad_list.empty()) {
                    if (verify_current()) return x;
                    rebuild_counts_and_scores();
                    if (bad_list.empty()) return x;
                }

                if ((iter & 1023LL) == 0) {
                    double now = elapsed();
                    if (now >= time_limit_seconds) break;
                    global_progress = min(1.0, now/time_limit_seconds);

                    int cur_bad = static_cast<int>(bad_list.size());
                    if (cur_bad < local_best_bad) {
                        local_best_bad = cur_bad;
                        last_bad_improvement_iter = iter;
                        reheat = max(1.0, reheat*0.92);
                        snapshot_if_better(iter);
                    }

                    long long stagnant = iter-last_bad_improvement_iter;
                    if (stagnant >= 60000LL + 10000LL*weight_bumps) {
                        increase_breakout_weights();
                        ++weight_bumps;
                        reheat = min(8.0, reheat*1.45);
                        last_bad_improvement_iter = iter;

                        // A persistent set of violations often indicates a wrong phase
                        // inside one weakly-coupled component.  Try a component inversion.
                        try_macro(global_progress, T0*reheat, true);
                        snapshot_if_better(iter);
                    }

                    // Restart after several unsuccessful breakout rounds.  Preserve more
                    // time for late restarts by lowering the threshold as the clock advances.
                    int max_bumps = global_progress < 0.65 ? 7 : 4;
                    if (weight_bumps >= max_bumps &&
                        iter-last_bad_improvement_iter >= 90000) {
                        break;
                    }
                }

                // Geometric cooling, with temporary reheating after stagnation.
                double p = global_progress;
                double base_T = T0 * pow(0.012, p);
                double T = max(0.08, base_T*reheat);

                // Roughly once per 512 micro moves, make a large-neighbourhood proposal.
                if ((iter & 511LL) == 0 && (rng.u32() & 3U) != 0U) {
                    try_macro(p, T, false);
                    ++iter;
                    continue;
                }

                // Best-of-k focused single-flip proposal.  k decreases near the start to
                // keep diversity, then increases near the end to sharpen the descent.
                int choices = (p < 0.25 ? 2 : (p < 0.75 ? 3 : 5));
                int chosen = -1;
                Delta chosen_d;
                long long chosen_e = LLONG_MAX;
                for (int z = 0; z < choices; ++z) {
                    int id = propose_single(p);
                    Delta d = evaluate_single(id);
                    long long e = energy_of(d, p);
                    if (e < chosen_e) {
                        chosen_e = e;
                        chosen = id;
                        chosen_d = d;
                    }
                }

                if (chosen >= 0 && metropolis(chosen_e, T)) {
                    apply_single(chosen, chosen_d);
                }
                ++iter;
            }

            snapshot_if_better(iter, true);
        }

        // The best observed state may still be invalid: this is a heuristic benchmark.
        x = best_x;
        rebuild_counts_and_scores();
        return x;
    }
};

} // namespace

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N;
    if (!(cin >> N)) return 0;
    vector<unsigned char> digit(N*N);
    string s;
    for (int i = 0; i < N; ++i) {
        cin >> s;
        if (static_cast<int>(s.size()) != N) return 0;
        for (int j = 0; j < N; ++j) digit[i*N+j] = static_cast<unsigned char>(s[j]-'0');
    }

    uint64_t seed = chrono::high_resolution_clock::now().time_since_epoch().count();
    seed ^= uint64_t(reinterpret_cast<uintptr_t>(&seed));
    Solver solver(N, std::move(digit), seed);
    vector<unsigned char> answer = solver.run(SA_TIME_LIMIT);

    string out(N, '.');
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) out[j] = answer[i*N+j] ? 'o' : '.';
        cout << out << '\n';
    }
    return 0;
}
0