結果

問題 No.5023 Airlines Optimization
コンテスト
ユーザー てんぷら
提出日時 2026-02-26 00:18:58
言語 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
結果
TLE  
実行時間 -
コード長 26,780 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 5,494 ms
コンパイル使用メモリ 301,276 KB
実行使用メモリ 10,584 KB
スコア 1,616,950
最終ジャッジ日時 2026-02-26 00:21:03
合計ジャッジ時間 123,361 ms
ジャッジサーバーID
(参考情報)
judge1 / judge4
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 3 TLE * 97
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
using namespace std;

using ll = long long;

struct City {
    int x, y;
    ll w;
};

struct Flight {
    int a, b;
    int s, t;
};

struct PlanePlan {
    vector<int> route;
    int offset = 0;
    int duration = 0;
};

struct DemandTerm {
    int src, dst;
    long double w;
    array<int, 21> sq;
};

constexpr int START_HOUR = 6;
constexpr int END_HOUR = 21;
constexpr int SLOT_MIN = 5;
constexpr int MAX_SLOT = (END_HOUR - START_HOUR) * 60 / SLOT_MIN; // 180
constexpr int TARGET_CNT = 21;
constexpr int TARGET_START = (11 - START_HOUR) * 60 / SLOT_MIN; // 60
constexpr int TARGET_STEP = 30 / SLOT_MIN;                      // 6
constexpr int NEG_INF = -1e9;

int parseTimeToSlot(const string &s) {
    auto pos = s.find(':');
    int hh = stoi(s.substr(0, pos));
    int mm = stoi(s.substr(pos + 1));
    return (hh * 60 + mm - START_HOUR * 60) / SLOT_MIN;
}

string slotToTime(int slot) {
    int total = START_HOUR * 60 + slot * SLOT_MIN;
    int hh = total / 60;
    int mm = total % 60;
    char buf[16];
    snprintf(buf, sizeof(buf), "%02d:%02d", hh, mm);
    return string(buf);
}

int calcDurationSlot(const City &c1, const City &c2) {
    double dx = double(c1.x) - double(c2.x);
    double dy = double(c1.y) - double(c2.y);
    double d = sqrt(dx * dx + dy * dy);
    double minutes = 60.0 * d / 800.0 + 40.0;
    return (int)ceil(minutes / 5.0 - 1e-12);
}

int routeDuration(const vector<int> &route, const vector<vector<int>> &dur) {
    int total = 0;
    for(int i = 1; i < (int)route.size(); ++i) {
        if(route[i - 1] == route[i])
            return MAX_SLOT + 1;
        total += dur[route[i - 1]][route[i]];
        if(total > MAX_SLOT)
            return total;
    }
    return total;
}

void normalizePlane(PlanePlan &p, const vector<vector<int>> &dur) {
    if(p.route.empty())
        p.route.push_back(0);
    vector<int> fixed;
    fixed.reserve(p.route.size());
    fixed.push_back(p.route[0]);
    for(int i = 1; i < (int)p.route.size(); ++i) {
        if(p.route[i] != p.route[i - 1])
            fixed.push_back(p.route[i]);
    }
    p.route.swap(fixed);
    if(p.route.empty())
        p.route.push_back(0);
    p.duration = routeDuration(p.route, dur);
    while(p.duration > MAX_SLOT && (int)p.route.size() > 1) {
        p.route.pop_back();
        p.duration = routeDuration(p.route, dur);
    }
    int lim = max(0, MAX_SLOT - p.duration);
    p.offset = min(max(0, p.offset), lim);
}

void appendPlaneFlights(const PlanePlan &p, const vector<vector<int>> &dur, vector<Flight> &out) {
    int tm = p.offset;
    for(int i = 1; i < (int)p.route.size(); ++i) {
        int a = p.route[i - 1];
        int b = p.route[i];
        int nt = tm + dur[a][b];
        out.push_back({a, b, tm, nt});
        tm = nt;
    }
}

vector<Flight> flattenPlans(const vector<PlanePlan> &plans, const vector<vector<int>> &dur) {
    vector<Flight> flights;
    for(const auto &p : plans)
        appendPlaneFlights(p, dur, flights);
    return flights;
}

struct ScoreEvaluator {
    int N;
    uint64_t allMask;

    explicit ScoreEvaluator(int n) : N(n) {
        allMask = (N == 64 ? ~0ULL : ((1ULL << N) - 1));
    }

    vector<vector<Flight>> buildByStart(const vector<Flight> &flights, int target) const {
        vector<vector<Flight>> byStart(target + 1);
        for(const auto &f : flights) {
            if(f.s < 0 || f.s > target || f.t < 0 || f.t > target)
                continue;
            byStart[f.s].push_back(f);
        }
        return byStart;
    }

    vector<vector<int>> latestMatrixForTarget(const vector<vector<Flight>> &byStart, int target) const {
        vector<vector<uint64_t>> reach(target + 1, vector<uint64_t>(N, 0ULL));
        for(int c = 0; c < N; ++c)
            reach[target][c] = (1ULL << c);

        for(int tm = target - 1; tm >= 0; --tm) {
            for(int c = 0; c < N; ++c)
                reach[tm][c] = reach[tm + 1][c];
            for(const auto &f : byStart[tm]) {
                reach[tm][f.a] |= reach[f.t][f.b];
            }
        }

        vector<vector<int>> latest(N, vector<int>(N, NEG_INF));
        for(int src = 0; src < N; ++src) {
            uint64_t rem = allMask;
            for(int tm = target; tm >= 0 && rem; --tm) {
                uint64_t m = reach[tm][src] & rem;
                while(m) {
                    int dst = __builtin_ctzll(m);
                    latest[src][dst] = tm;
                    rem &= ~(1ULL << dst);
                    m &= (m - 1);
                }
            }
        }
        return latest;
    }

    vector<vector<vector<int>>> precomputeSqBest(const vector<Flight> &sqFlights) const {
        vector<vector<vector<int>>> sqBest(TARGET_CNT, vector<vector<int>>(N, vector<int>(N, NEG_INF)));
        for(int k = 0; k < TARGET_CNT; ++k) {
            int target = TARGET_START + TARGET_STEP * k;
            auto byStart = buildByStart(sqFlights, target);
            sqBest[k] = latestMatrixForTarget(byStart, target);
        }
        return sqBest;
    }

    vector<vector<vector<int>>> precomputeAllLatest(const vector<Flight> &flights) const {
        vector<vector<Flight>> allByStart(MAX_SLOT + 1);
        for(const auto &f : flights) {
            if(0 <= f.s && f.s <= MAX_SLOT && 0 <= f.t && f.t <= MAX_SLOT)
                allByStart[f.s].push_back(f);
        }

        vector<vector<vector<int>>> allLatest(MAX_SLOT + 1, vector<vector<int>>(N, vector<int>(N, NEG_INF)));
        for(int deadline = 0; deadline <= MAX_SLOT; ++deadline) {
            vector<vector<uint64_t>> reach(deadline + 1, vector<uint64_t>(N, 0ULL));
            for(int c = 0; c < N; ++c)
                reach[deadline][c] = (1ULL << c);

            for(int tm = deadline - 1; tm >= 0; --tm) {
                for(int c = 0; c < N; ++c)
                    reach[tm][c] = reach[tm + 1][c];
                for(const auto &f : allByStart[tm]) {
                    if(f.t <= deadline)
                        reach[tm][f.a] |= reach[f.t][f.b];
                }
            }

            for(int src = 0; src < N; ++src) {
                uint64_t rem = allMask;
                for(int tm = deadline; tm >= 0 && rem; --tm) {
                    uint64_t m = reach[tm][src] & rem;
                    while(m) {
                        int dst = __builtin_ctzll(m);
                        allLatest[deadline][src][dst] = tm;
                        rem &= ~(1ULL << dst);
                        m &= (m - 1);
                    }
                }
            }
        }
        return allLatest;
    }

    double score(
        const vector<Flight> &ciFlights,
        const vector<DemandTerm> &demands) const {
        long double vSq = 0.0L, vCi = 0.0L;

        for(int k = 0; k < TARGET_CNT; ++k) {
            int target = TARGET_START + TARGET_STEP * k;
            auto byStart = buildByStart(ciFlights, target);
            auto latest = latestMatrixForTarget(byStart, target);

            for(const auto &d : demands) {
                if(d.sq[k] < latest[d.src][d.dst])
                    vCi += d.w;
                else
                    vSq += d.w;
            }
        }

        long double den = vSq + vCi;
        if(den <= 0.0L)
            return 0.0;
        return (double)(vCi / den);
    }
};

PlanePlan buildCyclicPlan(
    const vector<int> &cycle,
    int offset,
    const vector<vector<int>> &dur) {
    PlanePlan p;
    if(cycle.empty())
        return p;

    p.route.push_back(cycle[0]);
    p.offset = offset;

    int tm = offset;
    int cur = cycle[0];
    int idx = 1 % (int)cycle.size();

    while(true) {
        int nxt = cycle[idx];
        if(nxt == cur)
            break;
        int nt = tm + dur[cur][nxt];
        if(nt > MAX_SLOT)
            break;
        p.route.push_back(nxt);
        tm = nt;
        cur = nxt;
        idx = (idx + 1) % (int)cycle.size();
    }

    p.duration = tm - offset;
    normalizePlane(p, dur);
    return p;
}

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

    int N, R;
    cin >> N >> R;
    vector<City> cities(N);
    for(int i = 0; i < N; ++i)
        cin >> cities[i].x >> cities[i].y >> cities[i].w;

    int M;
    cin >> M;
    vector<Flight> sqFlights(M);
    for(int i = 0; i < M; ++i) {
        int a, b;
        string ss, tt;
        cin >> a >> ss >> b >> tt;
        --a;
        --b;
        sqFlights[i] = {a, b, parseTimeToSlot(ss), parseTimeToSlot(tt)};
    }

    int K;
    cin >> K;

    vector<vector<int>> dur(N, vector<int>(N, 0));
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < N; ++j)
            if(i != j)
                dur[i][j] = calcDurationSlot(cities[i], cities[j]);
    }

    vector<vector<long double>> pairWeight(N, vector<long double>(N, 0.0L));
    double threshold = 0.25 * R;
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < N; ++j) {
            double dx = double(cities[i].x) - double(cities[j].x);
            double dy = double(cities[i].y) - double(cities[j].y);
            double d = sqrt(dx * dx + dy * dy);
            if(d + 1e-12 >= threshold) {
                pairWeight[i][j] = (long double)cities[i].w * (long double)cities[j].w;
            }
        }
    }

    ScoreEvaluator evaluator(N);
    auto sqBest = evaluator.precomputeSqBest(sqFlights); // [k][src][dst]

    vector<DemandTerm> demands;
    demands.reserve(N * N);
    for(int src = 0; src < N; ++src) {
        for(int dst = 0; dst < N; ++dst) {
            if(pairWeight[src][dst] == 0.0L)
                continue;
            DemandTerm d;
            d.src = src;
            d.dst = dst;
            d.w = pairWeight[src][dst];
            for(int k = 0; k < TARGET_CNT; ++k)
                d.sq[k] = sqBest[k][src][dst];
            demands.push_back(d);
        }
    }
    vector<vector<int>> demandId(N, vector<int>(N, -1));
    for(int i = 0; i < (int)demands.size(); ++i)
        demandId[demands[i].src][demands[i].dst] = i;

    vector<long double> cityScore(N, 0.0L);
    for(int i = 0; i < N; ++i) {
        long double s = (long double)cities[i].w * 1e6L;
        for(int j = 0; j < N; ++j)
            s += pairWeight[i][j] + pairWeight[j][i];
        cityScore[i] = max((long double)1.0, s);
    }

    vector<int> cityOrd(N);
    iota(cityOrd.begin(), cityOrd.end(), 0);
    sort(cityOrd.begin(), cityOrd.end(), [&](int a, int b) {
        return cityScore[a] > cityScore[b];
    });

    long double totalDen = 0.0L;
    for(const auto &d : demands)
        totalDen += d.w;
    totalDen *= (long double)TARGET_CNT;

    double bestScoreAll = -1.0;
    int bestTrial = -1;
    vector<PlanePlan> bestPlans;
    vector<Flight> bestFlights;
    auto baseLatestAll = evaluator.precomputeAllLatest({});
    mt19937_64 rng(123456789ULL);
    const double TL = 0.8;
    const int maxLegPerPlane = 14;
    auto begin = chrono::steady_clock::now();

    auto makeSeedOrder = [&](int token) {
        vector<int> seedOrder = cityOrd;
        int mode = token % 4;
        if(mode == 0) {
            int shift = (token * 7) % N;
            rotate(seedOrder.begin(), seedOrder.begin() + shift, seedOrder.end());
        } else if(mode == 1) {
            reverse(seedOrder.begin(), seedOrder.end());
            int shift = (token * 5) % N;
            rotate(seedOrder.begin(), seedOrder.begin() + shift, seedOrder.end());
        } else if(mode == 2) {
            int top = min(N, max(12, K * 2));
            shuffle(seedOrder.begin(), seedOrder.begin() + top, rng);
            int shift = (int)(rng() % N);
            rotate(seedOrder.begin(), seedOrder.begin() + shift, seedOrder.end());
        } else {
            int top = min(N, max(20, K * 3));
            shuffle(seedOrder.begin(), seedOrder.begin() + top, rng);
            int rest = min(N, top + 10);
            if(top < rest)
                shuffle(seedOrder.begin() + top, seedOrder.begin() + rest, rng);
            int shift = (int)(rng() % N);
            rotate(seedOrder.begin(), seedOrder.begin() + shift, seedOrder.end());
        }
        return seedOrder;
    };

    auto applyFlightToLatestAll = [&](vector<vector<vector<int>>> &latestAll, int a, int b, int dep, int arr) {
        const auto &depMat = latestAll[dep];
        for(int target = arr; target <= MAX_SLOT; ++target) {
            auto &targetMat = latestAll[target];

            array<int, 47> dstList{};
            int dstCnt = 0;
            for(int dst = 0; dst < N; ++dst) {
                if(targetMat[b][dst] >= arr)
                    dstList[dstCnt++] = dst;
            }
            if(dstCnt == 0)
                continue;

            for(int src = 0; src < N; ++src) {
                int pre = depMat[src][a];
                if(pre == NEG_INF)
                    continue;
                for(int i = 0; i < dstCnt; ++i) {
                    int dst = dstList[i];
                    if(pre > targetMat[src][dst])
                        targetMat[src][dst] = pre;
                }
            }
        }
    };

    auto calcCiFromLatest = [&](const vector<vector<vector<int>>> &latestAll) {
        long double ci = 0.0L;
        for(int k = 0; k < TARGET_CNT; ++k) {
            int target = TARGET_START + TARGET_STEP * k;
            for(const auto &d : demands) {
                if(d.sq[k] < latestAll[target][d.src][d.dst])
                    ci += d.w;
            }
        }
        return ci;
    };

    auto extendGreedy = [&](vector<vector<int>> &routes,
                            vector<int> &headCity,
                            vector<int> &headTime,
                            vector<int> &legCount,
                            vector<vector<vector<int>>> &latestAll,
                            long double &currentCi,
                            double &currentScore,
                            int unlockPlane) {
        int maxTotalOps = K * maxLegPerPlane;
        for(int op = 0; op < maxTotalOps; ++op) {
            vector<tuple<int, int, int, int>> ops; // plane, from, dep, arr
            ops.reserve(K * N);

            for(int p = 0; p < K; ++p) {
                if(unlockPlane >= 0 && p != unlockPlane)
                    continue;
                if(legCount[p] >= maxLegPerPlane)
                    continue;
                int b = headCity[p];
                int arr = headTime[p];

                for(int a = 0; a < N; ++a) {
                    if(a == b)
                        continue;
                    int dep = arr - dur[a][b];
                    if(dep < 0)
                        continue;
                    ops.emplace_back(p, a, dep, arr);
                }
            }

            if(ops.empty())
                break;
            double bestScore = currentScore;
            long double bestDeltaCi = 0.0L;
            int bestPlane = -1, bestFrom = -1, bestDep = -1, bestArr = -1;

            for(const auto &[p, a, dep, arr] : ops) {
                long double deltaCi = 0.0L;
                const auto &depMat = latestAll[dep];
                array<int, 47> preBySrc{};
                array<int, 47> activeSrc{};
                int activeSrcCnt = 0;
                for(int src = 0; src < N; ++src)
                    if((preBySrc[src] = depMat[src][a]) != NEG_INF)
                        activeSrc[activeSrcCnt++] = src;
                if(activeSrcCnt == 0)
                    continue;

                int startK = 0;
                if(arr > TARGET_START) {
                    startK = (arr - TARGET_START + TARGET_STEP - 1) / TARGET_STEP;
                    if(startK < 0)
                        startK = 0;
                    if(startK > TARGET_CNT)
                        startK = TARGET_CNT;
                }
                int b = headCity[p];
                for(int k = startK; k < TARGET_CNT; ++k) {
                    int target = TARGET_START + TARGET_STEP * k;
                    const auto &targetMat = latestAll[target];

                    array<int, 47> okDst{};
                    int activeDstCnt = 0;
                    for(int dst = 0; dst < N; ++dst) {
                        if(targetMat[b][dst] >= arr)
                            okDst[activeDstCnt++] = dst;
                    }
                    if(activeDstCnt == 0)
                        continue;

                    for(int si = 0; si < activeSrcCnt; ++si) {
                        int src = activeSrc[si];
                        int pre = preBySrc[src];
                        for(int di = 0; di < activeDstCnt; ++di) {
                            int dst = okDst[di];
                            int idx2 = demandId[src][dst];
                            if(idx2 < 0)
                                continue;
                            const auto &d = demands[idx2];
                            int oldCi = targetMat[src][dst];
                            if(pre <= oldCi)
                                continue;
                            if(d.sq[k] < pre && !(d.sq[k] < oldCi))
                                deltaCi += d.w;
                        }
                    }
                }

                double s = (totalDen > 0.0L ? (double)((currentCi + deltaCi) / totalDen) : 0.0);
                if(s > bestScore || (s == bestScore && deltaCi > bestDeltaCi)) {
                    bestScore = s;
                    bestDeltaCi = deltaCi;
                    bestPlane = p;
                    bestFrom = a;
                    bestDep = dep;
                    bestArr = arr;
                }
            }

            if(bestPlane < 0)
                break;

            int b = headCity[bestPlane];
            routes[bestPlane].insert(routes[bestPlane].begin(), bestFrom);
            headCity[bestPlane] = bestFrom;
            headTime[bestPlane] = bestDep;
            legCount[bestPlane]++;
            applyFlightToLatestAll(latestAll, bestFrom, b, bestDep, bestArr);
            currentCi += bestDeltaCi;
            currentScore = (totalDen > 0.0L ? (double)(currentCi / totalDen) : 0.0);
        }
    };

    vector<vector<int>> currentRoutes(K);
    vector<int> currentHeadCity(K), currentHeadTime(K), currentLegCount(K, 0);
    auto seedOrder = makeSeedOrder(0);
    for(int p = 0; p < K; ++p) {
        int c = seedOrder[p % N];
        currentRoutes[p].push_back(c);
        currentHeadCity[p] = c;
        currentHeadTime[p] = MAX_SLOT;
    }
    vector<vector<vector<int>>> latestAll = baseLatestAll;
    long double currentCi = 0.0L;
    double currentScore = 0.0;
    extendGreedy(
        currentRoutes, currentHeadCity, currentHeadTime, currentLegCount, latestAll, currentCi, currentScore, -1);

    auto finalizePlans = [&](const vector<vector<int>> &routes, const vector<int> &headTime) {
        vector<PlanePlan> plans(K);
        for(int p = 0; p < K; ++p) {
            PlanePlan pl;
            pl.route = routes[p];
            pl.offset = headTime[p];
            normalizePlane(pl, dur);
            plans[p] = pl;
        }
        return plans;
    };

    bestPlans = finalizePlans(currentRoutes, currentHeadTime);
    bestFlights = flattenPlans(bestPlans, dur);
    bestScoreAll = evaluator.score(bestFlights, demands);
    bestTrial = 0;
    cerr << fixed << setprecision(12)
         << "iter=0 score=" << bestScoreAll
         << " flights=" << bestFlights.size() << '\n';

    int iter = 1;
    while(true) {
        double elapsed = chrono::duration<double>(chrono::steady_clock::now() - begin).count();
        if(elapsed >= TL)
            break;

        int resetPlane = (int)(rng() % K);
        if(iter % 3 == 0) {
            resetPlane = 0;
            for(int p = 1; p < K; ++p) {
                if(currentLegCount[p] < currentLegCount[resetPlane])
                    resetPlane = p;
            }
        }

        vector<vector<int>> candRoutes = currentRoutes;
        vector<int> candHeadCity = currentHeadCity;
        vector<int> candHeadTime = currentHeadTime;
        vector<int> candLegCount = currentLegCount;

        auto order = makeSeedOrder(iter + 1);
        int seed = order[(resetPlane * 7 + iter) % N];
        candRoutes[resetPlane].clear();
        candRoutes[resetPlane].push_back(seed);
        candHeadCity[resetPlane] = seed;
        candHeadTime[resetPlane] = MAX_SLOT;
        candLegCount[resetPlane] = 0;

        auto candLatestAll = baseLatestAll;
        for(int p = 0; p < K; ++p) {
            int tm = candHeadTime[p];
            for(int j = 1; j < (int)candRoutes[p].size(); ++j) {
                int a = candRoutes[p][j - 1], b = candRoutes[p][j];
                int nt = tm + dur[a][b];
                if(nt > MAX_SLOT)
                    break;
                applyFlightToLatestAll(candLatestAll, a, b, tm, nt);
                tm = nt;
            }
        }
        long double candCi = calcCiFromLatest(candLatestAll);
        double candScore = (totalDen > 0.0L ? (double)(candCi / totalDen) : 0.0);

        extendGreedy(
            candRoutes, candHeadCity, candHeadTime, candLegCount, candLatestAll, candCi, candScore, resetPlane);

        bool accept = (candScore >= currentScore);
        if(!accept) {
            long double delta = currentScore - candScore;
            long double temp = 0.002L;
            long double prob = expl(-delta / temp);
            long double r = (long double)(rng() & ((1ULL << 53) - 1)) / (long double)(1ULL << 53);
            if(r < prob)
                accept = true;
        }
        if(accept) {
            currentRoutes.swap(candRoutes);
            currentHeadCity.swap(candHeadCity);
            currentHeadTime.swap(candHeadTime);
            currentLegCount.swap(candLegCount);
            latestAll.swap(candLatestAll);
            currentCi = candCi;
            currentScore = candScore;
        }

        if(candScore > bestScoreAll) {
            auto candPlans = finalizePlans(candRoutes, candHeadTime);
            auto candFlights = flattenPlans(candPlans, dur);
            double exact = evaluator.score(candFlights, demands);
            if(exact > bestScoreAll) {
                bestScoreAll = exact;
                bestTrial = iter;
                bestPlans = std::move(candPlans);
                bestFlights = std::move(candFlights);
            }
        }

        if(iter % 5 == 0) {
            auto curPlans = finalizePlans(currentRoutes, currentHeadTime);
            auto curFlights = flattenPlans(curPlans, dur);
            cerr << fixed << setprecision(12)
                 << "iter=" << iter
                 << " current=" << currentScore
                 << " best=" << bestScoreAll
                 << " flights=" << curFlights.size() << '\n';
        }
        iter++;
    }

    // Post process: tune each plane's global offset to align arrivals with target deadlines.
    auto scorePlans = [&](const vector<PlanePlan> &plans) {
        auto flights = flattenPlans(plans, dur);
        return evaluator.score(flights, demands);
    };

    vector<PlanePlan> plans = bestPlans;
    double finalScore = bestScoreAll;
    for(int p = 0; p < K; ++p) {
        if(plans[p].route.size() <= 1)
            continue;
        int base = plans[p].offset;
        int lim = max(0, MAX_SLOT - plans[p].duration);
        int lo = max(0, base - 6);
        int hi = min(lim, base + 6);
        int bestOffset = base;
        double bestLocal = finalScore;
        for(int cand = lo; cand <= hi; ++cand) {
            if(cand == base)
                continue;
            int old = plans[p].offset;
            plans[p].offset = cand;
            double s = scorePlans(plans);
            plans[p].offset = old;
            if(s > bestLocal) {
                bestLocal = s;
                bestOffset = cand;
            }
        }
        if(bestOffset != base) {
            plans[p].offset = bestOffset;
            finalScore = bestLocal;
        }
    }
    vector<vector<Flight>> planeFlights(K);
    for(int p = 0; p < K; ++p) {
        int tm = plans[p].offset;
        for(int j = 1; j < (int)plans[p].route.size(); ++j) {
            int a = plans[p].route[j - 1], b = plans[p].route[j];
            int nt = tm + dur[a][b];
            planeFlights[p].push_back({a, b, tm, nt});
            tm = nt;
        }
    }
    auto flattenPlaneFlights = [&](const vector<vector<Flight>> &pf) {
        vector<Flight> flights;
        for(const auto &v : pf)
            flights.insert(flights.end(), v.begin(), v.end());
        return flights;
    };
    auto scorePlaneFlights = [&](const vector<vector<Flight>> &pf) {
        auto flights = flattenPlaneFlights(pf);
        return evaluator.score(flights, demands);
    };

    // Post process 2: from early legs to late legs, pull each leg itself earlier
    // as much as possible while keeping score non-decreasing.
    for(int p = 0; p < K; ++p) {
        auto &pf = planeFlights[p];
        for(int idx = 0; idx < (int)pf.size(); ++idx) {
            int prevArr = (idx == 0 ? 0 : pf[idx - 1].t);
            int slack = pf[idx].s - prevArr;
            if(slack <= 0)
                continue;
            int bestDelta = 0;
            int maxTry = min(slack, 5);
            maxTry = min(maxTry, 25);
            for(int delta = maxTry; delta >= 1; --delta) {
                pf[idx].s -= delta;
                pf[idx].t -= delta;
                double s = scorePlaneFlights(planeFlights);
                pf[idx].s += delta;
                pf[idx].t += delta;
                if(s + 1e-15 >= finalScore) {
                    bestDelta = delta;
                    break; // earliest feasible in 5..1 search
                }
            }
            if(bestDelta > 0) {
                pf[idx].s -= bestDelta;
                pf[idx].t -= bestDelta;
                finalScore = scorePlaneFlights(planeFlights);
            }
        }
    }
    vector<Flight> finalFlights = flattenPlaneFlights(planeFlights);

    vector<int> flownCity(N, 0);
    for(const auto &f : finalFlights) {
        flownCity[f.a] = 1;
        flownCity[f.b] = 1;
    }

    for(int i = 0; i < K; ++i) {
        cout << planeFlights[i].size() << '\n';
        for(const auto &f : planeFlights[i]) {
            cout << (f.a + 1) << ' ' << slotToTime(f.s) << ' ' << (f.b + 1) << ' ' << slotToTime(f.t) << '\n';
        }
    }

    cerr << fixed << setprecision(12)
         << "best_trial=" << bestTrial << ' '
         << "score=" << finalScore
         << " unique_cities=" << count(flownCity.begin(), flownCity.end(), 1)
         << " flights=" << finalFlights.size() << '\n';

    return 0;
}
0