結果

問題 No.5023 Airlines Optimization
コンテスト
ユーザー prussian_coder
提出日時 2026-02-25 22:33:36
言語 C++23
(gcc 15.2.0 + boost 1.89.0)
コンパイル:
g++-15 -O2 -lm -std=c++23 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 909 ms / 1,000 ms
コード長 26,481 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 7,299 ms
コンパイル使用メモリ 383,724 KB
実行使用メモリ 7,848 KB
スコア 42,057,889
最終ジャッジ日時 2026-02-25 22:35:21
合計ジャッジ時間 104,471 ms
ジャッジサーバーID
(参考情報)
judge1 / judge4
純コード判定しない問題か言語
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 100
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

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

const int N = 47;
const int MF = 400;
const int K = 25;
const int TMIN = 360;
const int TMAX = 1260;
const int NT = 21;
const int NINF = -1000000;

int cx[N], cy[N], cw[N];
int sqa_[MF], sqb_[MF], sqs_[MF], sqt_[MF];
int ft[N][N];
bool vp[N][N];
int tgt[NT];
int ssq[N][N][NT];

mt19937 rng(42);
int top_hubs[N]; // cities ranked by hub potential (computed in best_greedy_init)
int sa_hubs[10]; // hub cities for SA neighborhoods
int n_sa_hubs = 3; // number of SA hub cities

int parse_time(const char* s) {
    return (s[0]-'0')*600 + (s[1]-'0')*60 + (s[3]-'0')*10 + (s[4]-'0');
}

string fmt(int t) {
    char b[6];
    b[0]='0'+t/600; t%=600; b[1]='0'+t/60; t%=60;
    b[2]=':'; b[3]='0'+t/10; b[4]='0'+t%10; b[5]=0;
    return string(b);
}

int cft(double d) {
    return ((int)ceil((60.0*d/800.0+40.0)/5.0))*5;
}

void compute_sq() {
    struct E { int arr, dep, from; };
    vector<E> rev[N];
    for (int i = 0; i < MF; i++)
        rev[sqb_[i]].push_back({sqt_[i], sqs_[i], sqa_[i]});
    for (int i = 0; i < N; i++)
        sort(rev[i].begin(), rev[i].end(), [](auto& a, auto& b){ return a.arr > b.arr; });

    for (int ti = 0; ti < NT; ti++) {
        int T = tgt[ti];
        for (int j = 0; j < N; j++) {
            int best[N]; fill(best, best+N, NINF);
            best[j] = T;
            priority_queue<pair<int,int>> pq;
            pq.push({T, j});
            while (!pq.empty()) {
                auto [at, c] = pq.top(); pq.pop();
                if (at < best[c]) continue;
                for (auto& e : rev[c]) {
                    if (e.arr > at) continue;
                    if (e.dep > best[e.from]) {
                        best[e.from] = e.dep;
                        pq.push({e.dep, e.from});
                    }
                }
            }
            for (int i = 0; i < N; i++) ssq[i][j][ti] = best[i];
        }
    }
}

struct Flight { int from, to, dep, arr; };
struct Plane { vector<Flight> fs; };

vector<Flight> build(const vector<int>& cities, int start) {
    vector<Flight> fs;
    int ct = start;
    for (int i = 0; i+1 < (int)cities.size(); i++) {
        int a = cities[i], b = cities[i+1];
        if (a == b) continue;
        int d = ((ct+4)/5)*5;
        if (d < TMIN) d = TMIN;
        int ar = d + ft[a][b];
        if (ar > TMAX) break;
        fs.push_back({a, b, d, ar});
        ct = ar;
    }
    return fs;
}

bool valid(const Plane& p) {
    for (int j = 0; j < (int)p.fs.size(); j++) {
        auto& f = p.fs[j];
        if (f.from == f.to || f.dep < TMIN || f.arr > TMAX) return false;
        if (f.dep%5 || f.arr%5) return false;
        if (f.arr - f.dep != ft[f.from][f.to]) return false;
        if (j > 0 && (p.fs[j-1].to != f.from || p.fs[j-1].arr > f.dep)) return false;
    }
    return true;
}

// =========================================================
// BF-based evaluation
// =========================================================
struct CF { int from, to, dep, arr; };
CF sorted_flights[500];
int nf;

void collect_sorted(const vector<Plane>& planes) {
    nf = 0;
    for (auto& p : planes)
        for (auto& f : p.fs)
            sorted_flights[nf++] = {f.from, f.to, f.dep, f.arr};
    sort(sorted_flights, sorted_flights+nf, [](auto& a, auto& b){ return a.arr > b.arr; });
}

int best_ci[NT][N][N];
long long spp[NT][N];

long long score_for(int j, int ti, const int* best) {
    long long s = 0;
    for (int i = 0; i < N; i++)
        if (vp[i][j] && best[i] > ssq[i][j][ti])
            s += (long long)cw[i]*cw[j];
    return s;
}

void bf_solve(int j, int ti, int* best_out) {
    fill(best_out, best_out+N, NINF);
    best_out[j] = tgt[ti];
    for (int round = 0; round < 8; round++) {
        bool changed = false;
        for (int fi = 0; fi < nf; fi++) {
            auto& f = sorted_flights[fi];
            if (f.arr <= best_out[f.to] && f.dep > best_out[f.from]) {
                best_out[f.from] = f.dep;
                changed = true;
            }
        }
        if (!changed) break;
    }
}

long long eval_full(const vector<Plane>& planes) {
    collect_sorted(planes);
    long long total = 0;
    for (int ti = 0; ti < NT; ti++) {
        for (int j = 0; j < N; j++) {
            bf_solve(j, ti, best_ci[ti][j]);
            spp[ti][j] = score_for(j, ti, best_ci[ti][j]);
            total += spp[ti][j];
        }
    }
    return total;
}

struct Change { int ti, j; int old_best[N]; long long old_spp; };
Change changes[NT*N];
int nchanges;

long long eval_incr(const vector<Plane>& planes, long long old_score) {
    collect_sorted(planes);
    long long total = old_score;
    nchanges = 0;
    int new_best[N];

    for (int ti = 0; ti < NT; ti++) {
        for (int j = 0; j < N; j++) {
            bf_solve(j, ti, new_best);
            if (memcmp(new_best, best_ci[ti][j], sizeof(int)*N) == 0) continue;
            Change& ch = changes[nchanges++];
            ch.ti = ti; ch.j = j;
            memcpy(ch.old_best, best_ci[ti][j], sizeof(int)*N);
            ch.old_spp = spp[ti][j];
            long long new_s = score_for(j, ti, new_best);
            total += new_s - spp[ti][j];
            spp[ti][j] = new_s;
            memcpy(best_ci[ti][j], new_best, sizeof(int)*N);
        }
    }
    return total;
}

// Partial eval for specific time slots only
long long eval_incr_partial(const vector<Plane>& planes, long long old_score,
                            const int* ti_list, int ti_cnt) {
    collect_sorted(planes);
    long long total = old_score;
    nchanges = 0;
    int new_best[N];

    for (int k = 0; k < ti_cnt; k++) {
        int ti = ti_list[k];
        for (int j = 0; j < N; j++) {
            bf_solve(j, ti, new_best);
            if (memcmp(new_best, best_ci[ti][j], sizeof(int)*N) == 0) continue;
            Change& ch = changes[nchanges++];
            ch.ti = ti; ch.j = j;
            memcpy(ch.old_best, best_ci[ti][j], sizeof(int)*N);
            ch.old_spp = spp[ti][j];
            long long new_s = score_for(j, ti, new_best);
            total += new_s - spp[ti][j];
            spp[ti][j] = new_s;
            memcpy(best_ci[ti][j], new_best, sizeof(int)*N);
        }
    }
    return total;
}

void rollback() {
    for (int i = 0; i < nchanges; i++) {
        auto& ch = changes[i];
        memcpy(best_ci[ch.ti][ch.j], ch.old_best, sizeof(int)*N);
        spp[ch.ti][ch.j] = ch.old_spp;
    }
}

// =========================================================
// Greedy initial solution: backward construction
//
// Strategy: For each plane, build schedule from back to front.
// 1. Pick a "final destination" (high-population city)
// 2. Set deadline = TMAX (21:00)
// 3. Repeatedly prepend flights:
//    - For current position (city c, must depart by time t),
//      pick the source city a that maximizes score contribution
//      of the flight a->c arriving at or before t
//    - Update c = a, t = dep time of that flight
//    - Stop when we reach TMIN
// =========================================================

// Precomputed spoke rankings per hub
struct SpokeVal { int city; long long val; };
vector<SpokeVal> spoke_rank[N]; // spoke_rank[hub] = sorted list of (spoke, value)

void precompute_spoke_ranks() {
    for (int hub = 0; hub < N; hub++) {
        spoke_rank[hub].clear();
        for (int s = 0; s < N; s++) {
            if (s == hub) continue;
            long long val = 0;
            if (vp[hub][s]) {
                int fwd_time = ft[hub][s];
                int rev_time = ft[s][hub];
                for (int ti = 0; ti < NT; ti++) {
                    for (int arr = tgt[ti]; arr >= TMIN + fwd_time; arr -= 30) {
                        int dep = arr - fwd_time;
                        if (dep >= TMIN && dep > ssq[hub][s][ti]) {
                            val += (long long)cw[hub] * cw[s];
                            break;
                        }
                    }
                    for (int arr = tgt[ti]; arr >= TMIN + rev_time; arr -= 30) {
                        int dep = arr - rev_time;
                        if (dep >= TMIN && dep > ssq[s][hub][ti]) {
                            val += (long long)cw[s] * cw[hub];
                            break;
                        }
                    }
                }
            }
            // Always include for short-distance cities (even if !vp)
            // They're useful as transfer points
            val += (long long)cw[hub] * cw[s] / 100;
            spoke_rank[hub].push_back({s, val});
        }
        sort(spoke_rank[hub].begin(), spoke_rank[hub].end(),
             [](auto& a, auto& b){ return a.val > b.val; });
    }
}

// Backward greedy: build hub<->spoke shuttle from back to front
// hub_assign[K]: hub city for each plane
// stagger: time stagger per plane (in 5-min units)
void greedy_backward(vector<Plane>& planes, const int* hub_assign, int stagger) {
    for (int p = 0; p < K; p++) {
        int hub = hub_assign[p];
        auto& sr = spoke_rank[hub];

        // Pick spoke: distribute among planes with same hub
        int hub_idx = 0;
        for (int q = 0; q < p; q++) if (hub_assign[q] == hub) hub_idx++;
        int spoke = sr[hub_idx % min((int)sr.size(), 15)].city;

        // Build backward
        vector<Flight> fs;
        int cur_city = hub;
        int cur_deadline = TMAX - (p % stagger) * 5;
        if (cur_deadline < TMIN + 60) cur_deadline = TMAX;

        for (int step = 0; step < 20; step++) {
            int next_city = (cur_city == hub) ? spoke : hub;
            int flight_time = ft[next_city][cur_city];
            int arr = (cur_deadline / 5) * 5;
            if (arr > TMAX) arr = TMAX;
            int dep = arr - flight_time;
            if (dep < TMIN) break;
            dep = (dep / 5) * 5;
            arr = dep + flight_time;
            if (arr > cur_deadline || arr > TMAX) break;
            fs.push_back({next_city, cur_city, dep, arr});
            cur_city = next_city;
            cur_deadline = dep;
        }
        reverse(fs.begin(), fs.end());
        planes[p].fs = fs;
    }
}

// Weighted k-means clustering of cities
// Returns cluster assignment for each city (0..nc-1) and cluster centers
void kmeans_cluster(int nc, int* assign, int* centers) {
    // Initialize centers to top nc cities by weight
    vector<pair<int,int>> by_weight(N);
    for (int i = 0; i < N; i++) by_weight[i] = {-cw[i], i};
    sort(by_weight.begin(), by_weight.end());
    for (int c = 0; c < nc; c++) centers[c] = by_weight[c].second;

    for (int iter = 0; iter < 20; iter++) {
        // Assign each city to nearest center (by flight time)
        for (int i = 0; i < N; i++) {
            int best_c = 0, best_d = ft[i][centers[0]] + ft[centers[0]][i];
            for (int c = 1; c < nc; c++) {
                int d = ft[i][centers[c]] + ft[centers[c]][i];
                if (d < best_d) { best_d = d; best_c = c; }
            }
            assign[i] = best_c;
        }
        // Update centers to highest-weight city in each cluster
        for (int c = 0; c < nc; c++) {
            int best_city = centers[c], best_w = -1;
            for (int i = 0; i < N; i++) {
                if (assign[i] == c && cw[i] > best_w) {
                    best_w = cw[i]; best_city = i;
                }
            }
            centers[c] = best_city;
        }
    }
}

// Build cluster-based init:
// - Inter-cluster planes shuttle between cluster centers
// - Intra-cluster planes tour cities within each cluster via center
void cluster_init(vector<Plane>& planes, int nc, int n_inter) {
    int assign[N], centers[10];
    kmeans_cluster(nc, assign, centers);

    // Collect cities per cluster (sorted by weight desc, excluding center)
    vector<vector<int>> cluster_cities(nc);
    for (int c = 0; c < nc; c++) {
        for (int i = 0; i < N; i++) {
            if (assign[i] == c && i != centers[c]) {
                cluster_cities[c].push_back(i);
            }
        }
        sort(cluster_cities[c].begin(), cluster_cities[c].end(),
             [](int a, int b){ return cw[a] > cw[b]; });
    }

    int pi = 0;

    // Inter-cluster planes: shuttle between pairs of cluster centers
    // Build round-trip routes between neighboring centers
    for (int i = 0; i < nc && pi < n_inter; i++) {
        for (int j = i+1; j < nc && pi < n_inter; j++) {
            int a = centers[i], b = centers[j];
            if (!vp[a][b]) continue;
            // Build backward shuttle
            vector<Flight> fs;
            int cur = a, deadline = TMAX - (pi % 5) * 5;
            for (int step = 0; step < 20; step++) {
                int next = (cur == a) ? b : a;
                int flight_time = ft[next][cur];
                int arr = (deadline / 5) * 5;
                if (arr > TMAX) arr = TMAX;
                int dep = arr - flight_time;
                if (dep < TMIN) break;
                dep = (dep / 5) * 5;
                arr = dep + flight_time;
                if (arr > deadline || arr > TMAX) break;
                fs.push_back({next, cur, dep, arr});
                cur = next; deadline = dep;
            }
            reverse(fs.begin(), fs.end());
            planes[pi].fs = fs;
            pi++;
        }
    }

    // Intra-cluster planes: hub<->spoke shuttle within each cluster
    for (int c = 0; c < nc; c++) {
        auto& cities = cluster_cities[c];
        if (cities.empty()) continue;
        int n_planes_for_cluster = max(1, (K - n_inter) * (int)cities.size() / (N - nc));
        if (n_planes_for_cluster + pi > K) n_planes_for_cluster = K - pi;

        for (int p = 0; p < n_planes_for_cluster && pi < K; p++) {
            int spoke = cities[p % cities.size()];
            int hub = centers[c];
            vector<Flight> fs;
            int cur = hub, deadline = TMAX - (pi % 5) * 5;
            for (int step = 0; step < 20; step++) {
                int next = (cur == hub) ? spoke : hub;
                int flight_time = ft[next][cur];
                int arr = (deadline / 5) * 5;
                if (arr > TMAX) arr = TMAX;
                int dep = arr - flight_time;
                if (dep < TMIN) break;
                dep = (dep / 5) * 5;
                arr = dep + flight_time;
                if (arr > deadline || arr > TMAX) break;
                fs.push_back({next, cur, dep, arr});
                cur = next; deadline = dep;
            }
            reverse(fs.begin(), fs.end());
            planes[pi].fs = fs;
            pi++;
        }
    }
}

// Generate multiple hub assignment patterns and pick the best
void best_greedy_init(vector<Plane>& planes) {
    precompute_spoke_ranks();

    // Rank cities by hub potential (sum of spoke values)
    vector<pair<long long,int>> hub_potential(N);
    for (int h = 0; h < N; h++) {
        long long total = 0;
        for (auto& sv : spoke_rank[h]) total += sv.val;
        hub_potential[h] = {total, h};
    }
    sort(hub_potential.rbegin(), hub_potential.rend());
    for (int i = 0; i < N; i++) top_hubs[i] = hub_potential[i].second;
    // Distribution patterns: {hub_rank, plane_count}[]
    // Use hub ranks (0=best, 1=2nd best, etc.)
    struct HubPattern {
        vector<pair<int,int>> hub_ranks; // (hub_rank, count)
        int stagger;
    };

    vector<HubPattern> patterns = {
        {{{0,15},{1,5},{2,5}}, 5},
        {{{0,15},{1,5},{2,5}}, 3},
        {{{0,17},{1,4},{2,4}}, 4},
        {{{0,14},{1,6},{2,5}}, 5},
        {{{0,10},{1,8},{2,7}}, 5},
        {{{0,9},{1,8},{2,8}}, 5},
        {{{0,25}}, 5},
        {{{0,18},{1,7}}, 5},
    };

    long long best_score = -1;
    vector<Plane> best_result(K);

    for (auto& pat : patterns) {
        int ha[K];
        int idx = 0;
        for (auto& [hr, cnt] : pat.hub_ranks) {
            int city = top_hubs[hr];
            for (int i = 0; i < cnt && idx < K; i++) ha[idx++] = city;
        }
        while (idx < K) ha[idx++] = top_hubs[0];

        vector<Plane> trial(K);
        greedy_backward(trial, ha, pat.stagger);

        long long score = eval_full(trial);
        if (score > best_score) {
            best_score = score;
            best_result = trial;
        }
    }

    // Also try cluster-based init patterns
    long long best_cluster = -1;
    vector<Plane> best_cluster_result(K);
    int best_nc = 0;
    int best_centers[10];
    for (int nc = 3; nc <= 5; nc++) {
        int assign[N], centers[10];
        kmeans_cluster(nc, assign, centers);
        for (int n_inter : {3, 5, 7}) {
            if (n_inter >= K) continue;
            vector<Plane> trial(K);
            cluster_init(trial, nc, n_inter);
            long long score = eval_full(trial);
            if (score > best_cluster) {
                best_cluster = score;
                best_cluster_result = trial;
                best_nc = nc;
                memcpy(best_centers, centers, sizeof(int)*nc);
            }
        }
    }
    // Pick the best overall
    bool use_cluster = (best_cluster > best_score);
    if (use_cluster) {
        best_score = best_cluster;
        best_result = best_cluster_result;
        // Set SA hubs to cluster centers
        n_sa_hubs = best_nc;
        for (int i = 0; i < best_nc; i++) sa_hubs[i] = best_centers[i];
    } else {
        // Set SA hubs to top 3 hub cities
        n_sa_hubs = 3;
        for (int i = 0; i < 3; i++) sa_hubs[i] = top_hubs[i];
    }

    planes = best_result;
}

// =========================================================
// Phase-based SA evaluation
// =========================================================
int phase_ti[3][NT];
int phase_nt[3];
double phase_scale[3];

void init_phases() {
    // Phase 0: every 3rd slot (7 slots)
    phase_nt[0] = 0;
    for (int i = 0; i < NT; i += 3) phase_ti[0][phase_nt[0]++] = i;
    phase_scale[0] = (double)NT / phase_nt[0];

    // Phase 1: every 2nd slot (11 slots)
    phase_nt[1] = 0;
    for (int i = 0; i < NT; i += 2) phase_ti[1][phase_nt[1]++] = i;
    phase_scale[1] = (double)NT / phase_nt[1];

    // Phase 2: all slots (21 slots)
    phase_nt[2] = NT;
    for (int i = 0; i < NT; i++) phase_ti[2][i] = i;
    phase_scale[2] = 1.0;
}

int main() {
    auto t0 = chrono::steady_clock::now();
    auto el = [&]() { return chrono::duration<double>(chrono::steady_clock::now()-t0).count(); };

    int n, r; scanf("%d%d", &n, &r);
    for (int i = 0; i < N; i++) scanf("%d%d%d", &cx[i], &cy[i], &cw[i]);

    for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) {
        if (i == j) { ft[i][j] = 0; vp[i][j] = false; continue; }
        double dx = cx[i]-cx[j], dy = cy[i]-cy[j];
        double d = sqrt(dx*dx+dy*dy);
        ft[i][j] = cft(d);
        vp[i][j] = d >= 250.0;
    }

    int m; scanf("%d", &m);
    for (int i = 0; i < m; i++) {
        char s1[6], s2[6];
        scanf("%d%5s%d%5s", &sqa_[i], s1, &sqb_[i], s2);
        sqa_[i]--; sqb_[i]--;
        sqs_[i] = parse_time(s1);
        sqt_[i] = parse_time(s2);
    }
    int k; scanf("%d", &k);
    for (int i = 0; i < NT; i++) tgt[i] = 660 + i*30;

    compute_sq();
    init_phases();

    long long tw = 0;
    for (int i = 0; i < N; i++) for (int j = 0; j < N; j++)
        if (vp[i][j]) tw += (long long)cw[i]*cw[j];
    tw *= NT;

    fprintf(stderr, "Precomp: %.3fs\n", el());

    // =========================================================
    // Initial solution: try both greedy and hub-spoke, pick better
    // =========================================================
    vector<Plane> planes(K);

    auto shuttle = [](int hub, int spoke, int start) -> vector<Flight> {
        vector<int> route;
        int cur = hub, ct = start;
        route.push_back(cur);
        while (true) {
            int next = (cur==hub)?spoke:hub;
            int d = ((ct+4)/5)*5;
            if (d < TMIN) d = TMIN;
            if (d+ft[cur][next] > TMAX) break;
            route.push_back(next);
            ct = d+ft[cur][next];
            cur = next;
        }
        return build(route, start);
    };

    // Option A: original hub-spoke
    vector<Plane> planes_hs(K);
    {
        int asgn[][2] = {
            {0,1},{0,2},{0,3},{0,4},{0,5},{0,6},{0,7},{0,8},
            {1,0},{1,2},{1,3},{1,4},{1,5},{1,6},
            {2,0},{2,1},{2,3},{2,4},{2,5},
            {0,9},{0,10},{0,11},{0,12},{0,13},{0,14}
        };
        for (int i = 0; i < K; i++)
            planes_hs[i].fs = shuttle(asgn[i][0], asgn[i][1], TMIN + i*12);
    }
    long long score_hs = eval_full(planes_hs);
    fprintf(stderr, "Hub-spoke init: %lld share=%.4f (%.3fs)\n", score_hs, (double)score_hs/tw, el());

    // Option B: best of multiple greedy backward patterns
    vector<Plane> planes_gr(K);
    best_greedy_init(planes_gr);
    long long score_gr = eval_full(planes_gr);
    fprintf(stderr, "Best greedy init: %lld share=%.4f (%.3fs)\n", score_gr, (double)score_gr/tw, el());

    // Pick the better one
    if (score_gr > score_hs) {
        planes = planes_gr;
        fprintf(stderr, "Using greedy init\n");
    } else {
        planes = planes_hs;
        fprintf(stderr, "Using hub-spoke init\n");
    }

    long long cur = eval_full(planes);
    long long best = cur;
    vector<Plane> best_p = planes;
    fprintf(stderr, "Init: %lld share=%.4f (%.3fs)\n", cur, (double)cur/tw, el());

    // =========================================================
    // SA with phased precision
    // =========================================================
    const double phase_boundary[2] = {0.35, 0.70};
    const double sa_lim = 0.90;

    int cur_phase = 0;
    // Compute partial score for current phase from cache
    long long cur_partial = 0;
    for (int k2 = 0; k2 < phase_nt[cur_phase]; k2++) {
        int ti = phase_ti[cur_phase][k2];
        for (int j = 0; j < N; j++) cur_partial += spp[ti][j];
    }

    double Ts_base = 3e13, Te_base = 1e9;
    int it = 0, ac = 0;
    int phase_its[3] = {0, 0, 0};

    while (el() < sa_lim) {
        double t_now = el();
        double prog = t_now / sa_lim;
        double sc = phase_scale[cur_phase];
        double Ts_eff = Ts_base / sc, Te_eff = Te_base / sc;
        double T = Ts_eff * pow(Te_eff/Ts_eff, prog);

        // Phase transition
        int new_phase = (prog < phase_boundary[0]) ? 0 :
                        (prog < phase_boundary[1]) ? 1 : 2;
        if (new_phase != cur_phase) {
            cur_phase = new_phase;
            eval_full(planes);
            cur_partial = 0;
            for (int k2 = 0; k2 < phase_nt[cur_phase]; k2++) {
                int ti = phase_ti[cur_phase][k2];
                for (int j = 0; j < N; j++) cur_partial += spp[ti][j];
            }
            long long full_now = 0;
            for (int ti = 0; ti < NT; ti++)
                for (int j = 0; j < N; j++) full_now += spp[ti][j];
            if (full_now > best) { best = full_now; best_p = planes; }
        }

        it++;
        phase_its[cur_phase]++;

        int pi = rng() % K;
        Plane old = planes[pi];
        int op = rng() % 100;

        if (op < 40) {
            int hub, spoke;
            int r2 = rng()%10;
            hub = (r2<5)?0:(r2<8)?1:2;
            spoke = rng()%N;
            if (spoke == hub) spoke = (hub+1+rng()%(N-1))%N;
            int start = TMIN + (rng()%72)*5;
            planes[pi].fs = shuttle(hub, spoke, start);
        } else if (op < 60) {
            int shift = ((int)(rng()%9)-4)*5;
            if (!shift) shift = 5;
            bool ok = true;
            for (auto& f : planes[pi].fs) {
                f.dep += shift; f.arr += shift;
                if (f.dep < TMIN || f.arr > TMAX) { ok = false; break; }
            }
            if (!ok) { planes[pi] = old; continue; }
        } else if (op < 80) {
            int sc2; int r2=rng()%10;
            sc2 = (r2<4)?rng()%3:(r2<7)?rng()%10:rng()%N;
            int start = TMIN + (rng()%72)*5;
            vector<int> route = {sc2};
            int c = sc2, ct = start;
            for (int t = 0; t < 15; t++) {
                int nx; int r3=rng()%10;
                nx = (r3<3)?rng()%3:(r3<6)?rng()%10:(r3<8)?rng()%20:rng()%N;
                if (nx == c) continue;
                int d = ((ct+4)/5)*5;
                if (d < TMIN) d = TMIN;
                if (d+ft[c][nx] > TMAX) continue;
                route.push_back(nx);
                ct = d+ft[c][nx];
                c = nx;
            }
            planes[pi].fs = build(route, start);
        } else {
            auto& fs = planes[pi].fs;
            if (fs.size() < 2) { planes[pi] = old; continue; }
            int split = 1+rng()%((int)fs.size()-1);
            int c = fs[split-1].to, ct = fs[split-1].arr;
            vector<Flight> nfs(fs.begin(), fs.begin()+split);
            for (int t = 0; t < 10; t++) {
                int nx; int r3=rng()%10;
                nx = (r3<3)?rng()%3:(r3<6)?rng()%10:rng()%N;
                if (nx == c) continue;
                int d = ((ct+4)/5)*5;
                if (d+ft[c][nx] > TMAX) break;
                nfs.push_back({c, nx, d, d+ft[c][nx]});
                ct = d+ft[c][nx]; c = nx;
            }
            planes[pi].fs = nfs;
        }

        if (!valid(planes[pi])) { planes[pi] = old; continue; }

        long long ns = eval_incr_partial(planes, cur_partial,
                                         phase_ti[cur_phase], phase_nt[cur_phase]);
        long long delta = ns - cur_partial;
        if (delta > 0 || exp((double)delta/T) > (double)(rng()%1000000)/1e6) {
            cur_partial = ns; ac++;
        } else {
            planes[pi] = old;
            rollback();
        }
    }

    // Final full evaluation
    {
        long long final_score = eval_full(planes);
        if (final_score > best) { best = final_score; best_p = planes; }
    }

    planes = best_p;
    fprintf(stderr, "SA: it=%d ac=%d best=%lld share=%.4f\n", it, ac, best, (double)best/tw);
    fprintf(stderr, "Phase iters: [%d, %d, %d]\n", phase_its[0], phase_its[1], phase_its[2]);

    long long verify = eval_full(planes);
    fprintf(stderr, "Verify: %lld (diff=%lld)\n", verify, verify - best);

    for (int p = 0; p < K; p++) {
        printf("%d\n", (int)planes[p].fs.size());
        for (auto& f : planes[p].fs) {
            string sd = fmt(f.dep), sa2 = fmt(f.arr);
            printf("%d %s %d %s\n", f.from+1, sd.c_str(), f.to+1, sa2.c_str());
        }
    }
    fprintf(stderr, "Score: %lld\nTotal: %.3fs\n", (long long)((double)best/tw*1e6), el());
    return 0;
}
0