結果

問題 No.5024 魔法少女うなと宝集め
コンテスト
ユーザー Rho
提出日時 2026-05-02 17:32:24
言語 C++14
(gcc 15.2.0 + boost 1.89.0)
コンパイル:
g++-15 -O2 -lm -std=c++14 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 1,905 ms / 2,000 ms
コード長 13,531 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 3,865 ms
コンパイル使用メモリ 232,256 KB
実行使用メモリ 6,400 KB
スコア 1,695,943
最終ジャッジ日時 2026-05-02 17:34:08
合計ジャッジ時間 103,121 ms
ジャッジサーバーID
(参考情報)
judge3_0 / judge2_0
純コード判定しない問題か言語
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 50
権限があれば一括ダウンロードができます
コンパイルメッセージ
main.cpp: In function 'void pop_cell()':
main.cpp:52:10: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
   52 |     auto [x, y] = path.back();
      |          ^
main.cpp: In function 'void clear_path()':
main.cpp:58:15: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
   58 |     for (auto [x, y] : path) used[x][y] = false;
      |               ^
main.cpp: In function 'void extend_random(int)':
main.cpp:66:14: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
   66 |         auto [x, y] = path.back();
      |              ^
main.cpp: In function 'void random_initial_solution()':
main.cpp:99:15: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
   99 |     for (auto [x, y] : best_init) push_cell(x, y);
      |               ^
main.cpp: In function 'bool ins_apply(InsertMove&)':
main.cpp:282:14: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
  282 |         auto [x1, y1] = path[i];
      |              ^
main.cpp:283:14: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
  283 |         auto [x2, y2] = path[i + 1];
      |              ^
main.cpp: In function 'int main()':
main.cpp:422:15: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
  422 |     for (auto [x, y] : best_path) cout << x << " " << y << "\n";
      |               ^

ソースコード

diff #
raw source code

//# pragma GCC target("avx2")
# pragma GCC optimize("O3")
# pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
#define rep(i,n) for(ll i=0;i<(n);i++)
using namespace std;
#define all(a) a.begin(),a.end()
template<typename T> bool chmin(T& a, T b){if(a > b){a = b; return true;} return false;}
template<typename T> bool chmax(T& a, T b){if(a < b){a = b; return true;} return false;}
#define compress(a) sort(all(a));a.erase(unique(all(a)),a.end())
typedef long long ll;
typedef pair<ll,ll> P;
typedef pair<ll,P> PP;
constexpr ll inf=3e18;

ll N,T;
ll A [20][20];

void input(){
    cin>>N>>T;
    rep(i,N)rep(j,N)cin>>A[i][j];
}

// =========================================================
// 焼きなまし法による単純パス最大化
// =========================================================

mt19937 rng(20260502);
const int dx4[4] = {-1, 1, 0, 0};
const int dy4[4] = {0, 0, -1, 1};

inline bool in_bounds(int x, int y) {
    return 0 <= x && x < N && 0 <= y && y < N;
}
inline bool adj(const P& a, const P& b) {
    return abs((int)(a.first - b.first)) + abs((int)(a.second - b.second)) == 1;
}

vector<P> path;
bool used[20][20];
ll score_cur = 0;

vector<P> best_path;
ll best_score = LLONG_MIN;

inline void push_cell(int x, int y) {
    path.emplace_back(x, y);
    used[x][y] = true;
    score_cur += A[x][y];
}
inline void pop_cell() {
    auto [x, y] = path.back();
    used[x][y] = false;
    score_cur -= A[x][y];
    path.pop_back();
}
void clear_path() {
    for (auto [x, y] : path) used[x][y] = false;
    path.clear();
    score_cur = 0;
}

// 末尾からランダムウォークで延伸(長さ max_len まで or 行き詰まりまで)
void extend_random(int max_len) {
    while ((int)path.size() < max_len) {
        auto [x, y] = path.back();
        int nxs[4], nys[4], cnt = 0;
        for (int k = 0; k < 4; k++) {
            int nx = x + dx4[k], ny = y + dy4[k];
            if (in_bounds(nx, ny) && !used[nx][ny]) {
                nxs[cnt] = nx; nys[cnt] = ny; cnt++;
            }
        }
        if (cnt == 0) break;
        int c = rng() % cnt;
        push_cell(nxs[c], nys[c]);
    }
}

void random_initial_solution() {
    vector<P> best_init;
    ll best_init_score = LLONG_MIN;
    int best_init_len = 0;

    rep(trial, 50) {
        clear_path();
        int sx = rng() % N, sy = rng() % N;
        push_cell(sx, sy);
        extend_random(T);
        int len = path.size();
        if (len > best_init_len ||
            (len == best_init_len && score_cur > best_init_score)) {
            best_init = path;
            best_init_score = score_cur;
            best_init_len = len;
        }
    }
    clear_path();
    for (auto [x, y] : best_init) push_cell(x, y);
    best_path = path;
    best_score = score_cur;
}

// =========================================================
// Greedy 初期解: 5x5 ブロック 16 個を蛇行順に巡る Hamiltonian (400 セル)
// を構築し、長さ T の最大スコア連続部分列を採用。
// 各ブロック内は entry/exit を固定し、5x5 上の DFS で Hamiltonian を求める。
// =========================================================

// ブロック 16 個分の (entry_r, entry_c, exit_r, exit_c) (グローバル座標)
// 並びは serpentine: (0,0)(0,1)(0,2)(0,3) → (1,3)(1,2)(1,1)(1,0) → (2,*) → (3,*)
const int BLOCK_DATA[16][4] = {
    { 0, 0,  4, 4},  // (0,0)
    { 4, 5,  0, 9},  // (0,1)
    { 0,10,  4,14},  // (0,2)
    { 4,15,  4,19},  // (0,3) 折り返し下へ
    { 5,19,  9,15},  // (1,3)
    { 9,14,  5,10},  // (1,2)
    { 5, 9,  9, 5},  // (1,1)
    { 9, 4,  9, 0},  // (1,0) 折り返し下へ
    {10, 0, 14, 4},  // (2,0)
    {14, 5, 10, 9},  // (2,1)
    {10,10, 14,14},  // (2,2)
    {14,15, 14,19},  // (2,3) 折り返し下へ
    {15,19, 19,15},  // (3,3)
    {19,14, 15,10},  // (3,2)
    {15, 9, 19, 5},  // (3,1)
    {19, 4, 15, 0},  // (3,0) 終端
};
const int BLOCK_TL[16][2] = {
    { 0, 0},{ 0, 5},{ 0,10},{ 0,15},
    { 5,15},{ 5,10},{ 5, 5},{ 5, 0},
    {10, 0},{10, 5},{10,10},{10,15},
    {15,15},{15,10},{15, 5},{15, 0},
};

bool dfs_vis[5][5];
vector<P> dfs_buf;

bool dfs_block(int r, int c, int r0, int c0, int tr, int tc, int visited) {
    dfs_vis[r - r0][c - c0] = true;
    dfs_buf.push_back(P(r, c));
    if (visited == 25) {
        if (r == tr && c == tc) return true;
        dfs_vis[r - r0][c - c0] = false;
        dfs_buf.pop_back();
        return false;
    }
    // 枝刈り: 残り歩数で目的地に到達できるか (Manhattan + パリティ)
    int steps_left = 25 - visited;
    int dist = abs(r - tr) + abs(c - tc);
    if (dist > steps_left || ((steps_left - dist) & 1)) {
        dfs_vis[r - r0][c - c0] = false;
        dfs_buf.pop_back();
        return false;
    }
    for (int k = 0; k < 4; k++) {
        int nr = r + dx4[k], nc = c + dy4[k];
        int br = nr - r0, bc = nc - c0;
        if (br < 0 || br >= 5 || bc < 0 || bc >= 5) continue;
        if (dfs_vis[br][bc]) continue;
        if (dfs_block(nr, nc, r0, c0, tr, tc, visited + 1)) return true;
    }
    dfs_vis[r - r0][c - c0] = false;
    dfs_buf.pop_back();
    return false;
}

// 16 ブロックを連結した 400 セルの蛇行 Hamiltonian
vector<P> build_snake_of_snakes() {
    vector<P> snake;
    snake.reserve(400);
    for (int idx = 0; idx < 16; idx++) {
        int sr = BLOCK_DATA[idx][0], sc = BLOCK_DATA[idx][1];
        int er = BLOCK_DATA[idx][2], ec = BLOCK_DATA[idx][3];
        int r0 = BLOCK_TL[idx][0], c0 = BLOCK_TL[idx][1];
        memset(dfs_vis, 0, sizeof(dfs_vis));
        dfs_buf.clear();
        if (!dfs_block(sr, sc, r0, c0, er, ec, 1)) {
            cerr << "[snake] block " << idx << " ham not found" << endl;
            return {};
        }
        for (auto p : dfs_buf) snake.push_back(p);
    }
    // 隣接性チェック (デバッグ)
    for (int i = 1; i < (int)snake.size(); i++) {
        if (!adj(snake[i - 1], snake[i])) {
            cerr << "[snake] non-adjacent at " << i << ": "
                 << snake[i-1].first << "," << snake[i-1].second << " -> "
                 << snake[i].first << "," << snake[i].second << endl;
            return {};
        }
    }
    return snake;
}

void greedy_initial_solution() {
    if (N != 20) { random_initial_solution(); return; }

    vector<P> snake = build_snake_of_snakes();
    if ((int)snake.size() != 400) { random_initial_solution(); return; }

    int W = (int)min((ll)400, T);
    vector<ll> psum(401, 0);
    for (int i = 0; i < 400; i++) {
        psum[i + 1] = psum[i] + A[snake[i].first][snake[i].second];
    }

    // 上位 T/2 個の高価値マスを参考までに集計
    vector<pair<ll, P>> all_cells;
    rep(i, N) rep(j, N) all_cells.push_back({A[i][j], P(i, j)});
    sort(all_cells.begin(), all_cells.end(),
         [](const auto& a, const auto& b) { return a.first > b.first; });
    int top_k = max(1, (int)(T / 2));
    set<P> top_set;
    for (int i = 0; i < top_k && i < (int)all_cells.size(); i++) {
        top_set.insert(all_cells[i].second);
    }

    int best_start = 0;
    ll best_sum = LLONG_MIN;
    for (int s = 0; s + W <= 400; s++) {
        ll sum = psum[s + W] - psum[s];
        if (sum > best_sum) { best_sum = sum; best_start = s; }
    }

    clear_path();
    int hits = 0;
    for (int i = best_start; i < best_start + W; i++) {
        push_cell(snake[i].first, snake[i].second);
        if (top_set.count(snake[i])) hits++;
    }
    best_path = path;
    best_score = score_cur;

    cerr << "[greedy] start=" << best_start << " W=" << W
         << " sum=" << best_sum
         << " top" << top_k << "_hit=" << hits << "/" << top_k << endl;
}

// === 近傍 1: 末尾 k 削除 → 再延伸 ===
struct TailMove {
    bool reversed;
    int prefix_len;
    vector<P> popped;
};
bool tr_apply(TailMove& m) {
    int len = path.size();
    if (len < 2) return false;
    m.reversed = (rng() & 1);
    if (m.reversed) reverse(path.begin(), path.end());
    int kmax = min(len - 1, 6);
    int k = 1 + (int)(rng() % kmax);
    m.prefix_len = len - k;
    m.popped.clear();
    m.popped.reserve(k);
    for (int i = 0; i < k; i++) {
        m.popped.push_back(path.back());
        pop_cell();
    }
    extend_random(T);
    return true;
}
void tr_rollback(TailMove& m) {
    while ((int)path.size() > m.prefix_len) pop_cell();
    for (int i = (int)m.popped.size() - 1; i >= 0; i--) {
        push_cell(m.popped[i].first, m.popped[i].second);
    }
    if (m.reversed) reverse(path.begin(), path.end());
}

// === 近傍 2: 挿入(path[i] と path[i+1] の共通隣接セルを差し込む)===
struct InsertMove {
    int pos;
    P cell;
};
bool ins_apply(InsertMove& m) {
    int len = path.size();
    if (len < 2 || len >= T) return false;
    for (int trial = 0; trial < 10; trial++) {
        int i = rng() % (len - 1);
        auto [x1, y1] = path[i];
        auto [x2, y2] = path[i + 1];
        P cands[4]; int cnt = 0;
        for (int k = 0; k < 4; k++) {
            int cx = x1 + dx4[k], cy = y1 + dy4[k];
            if (!in_bounds(cx, cy)) continue;
            if (used[cx][cy]) continue;
            if (abs(cx - x2) + abs(cy - y2) != 1) continue;
            cands[cnt++] = P(cx, cy);
        }
        if (cnt == 0) continue;
        P c = cands[rng() % cnt];
        m.pos = i + 1;
        m.cell = c;
        path.insert(path.begin() + m.pos, c);
        used[c.first][c.second] = true;
        score_cur += A[c.first][c.second];
        return true;
    }
    return false;
}
void ins_rollback(InsertMove& m) {
    used[m.cell.first][m.cell.second] = false;
    score_cur -= A[m.cell.first][m.cell.second];
    path.erase(path.begin() + m.pos);
}

// === 近傍 3: 削除(path[i-1] と path[i+1] が隣接していれば path[i] を削除)===
struct DeleteMove {
    int pos;
    P cell;
};
bool del_apply(DeleteMove& m) {
    int len = path.size();
    if (len < 3) return false;
    for (int trial = 0; trial < 10; trial++) {
        int i = 1 + (int)(rng() % (len - 2));
        if (!adj(path[i - 1], path[i + 1])) continue;
        m.pos = i;
        m.cell = path[i];
        used[m.cell.first][m.cell.second] = false;
        score_cur -= A[m.cell.first][m.cell.second];
        path.erase(path.begin() + i);
        return true;
    }
    return false;
}
void del_rollback(DeleteMove& m) {
    path.insert(path.begin() + m.pos, m.cell);
    used[m.cell.first][m.cell.second] = true;
    score_cur += A[m.cell.first][m.cell.second];
}

const double TIME_LIMIT_MS = 1900.0;

inline double elapsed_ms(chrono::steady_clock::time_point st) {
    return chrono::duration<double, milli>(chrono::steady_clock::now() - st).count();
}

int main(){
    input();
    auto t_input = chrono::steady_clock::now();
    auto start = t_input;

    // 温度スケールは A の値域から決める
    ll amax = LLONG_MIN, amin = LLONG_MAX;
    rep(i, N) rep(j, N) { amax = max(amax, A[i][j]); amin = min(amin, A[i][j]); }
    double T_start = max(1.0, (double)(amax - amin));
    double T_end = max(0.1, T_start * 0.001);

    greedy_initial_solution();
    double t_init_ms = elapsed_ms(start);
    ll initial_score = best_score;

    ll iter = 0, accepted = 0, improved = 0;
    double now_ms = 0;
    while (true) {
        if ((iter & 1023) == 0) {
            now_ms = elapsed_ms(start);
            if (now_ms > TIME_LIMIT_MS) break;
        }
        iter++;

        double progress = now_ms / TIME_LIMIT_MS;
        double temp = T_start * pow(T_end / T_start, progress);

        ll old_score = score_cur;
        int op = rng() % 100;
        bool applied = false;
        TailMove tm; InsertMove im; DeleteMove dm;
        int kind = -1;

        if (op < 60) { applied = tr_apply(tm); kind = 0; }
        else if (op < 80) { applied = ins_apply(im); kind = 1; }
        else              { applied = del_apply(dm); kind = 2; }

        if (!applied) continue;

        ll delta = score_cur - old_score;
        bool accept;
        if (delta >= 0) accept = true;
        else {
            double prob = exp((double)delta / temp);
            double r = (double)(rng() & 0xFFFFFF) / (double)0x1000000;
            accept = r < prob;
        }

        if (accept) {
            accepted++;
            if (score_cur > best_score) {
                improved++;
                best_score = score_cur;
                best_path = path;
            }
        } else {
            if (kind == 0) tr_rollback(tm);
            else if (kind == 1) ins_rollback(im);
            else del_rollback(dm);
        }
    }

    double t_total_ms = elapsed_ms(start);
    double t_sa_ms = t_total_ms - t_init_ms;
    cerr << fixed << setprecision(2);
    cerr << "[time] init=" << t_init_ms << "ms"
         << " sa=" << t_sa_ms << "ms"
         << " total=" << t_total_ms << "ms"
         << " (limit=" << TIME_LIMIT_MS << "ms)" << endl;
    cerr << "[stat] iter=" << iter
         << " iter/s=" << (iter * 1000.0 / max(1.0, t_sa_ms))
         << " accepted=" << accepted
         << " (" << (accepted * 100.0 / max((ll)1, iter)) << "%)"
         << " improved=" << improved << endl;
    cerr << "[score] init=" << initial_score
         << " best=" << best_score
         << " gain=" << (best_score - initial_score)
         << " len=" << best_path.size() << "/" << T << endl;

    // 出力(フォーマットは問題に合わせて要調整)
    cout << best_path.size() << "\n";
    for (auto [x, y] : best_path) cout << x << " " << y << "\n";

    return 0;
}
0