結果
| 問題 | No.5023 Airlines Optimization |
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2026-02-25 23:31:29 |
| 言語 | C++17 (gcc 15.2.0 + boost 1.89.0) |
| 結果 |
AC
|
| 実行時間 | 929 ms / 1,000 ms |
| コード長 | 32,169 bytes |
| 記録 | |
| コンパイル時間 | 4,620 ms |
| コンパイル使用メモリ | 213,972 KB |
| 実行使用メモリ | 7,844 KB |
| スコア | 35,480,746 |
| 最終ジャッジ日時 | 2026-02-25 23:33:13 |
| 合計ジャッジ時間 | 102,953 ms |
|
ジャッジサーバーID (参考情報) |
judge4 / judge1 |
| 純コード判定しない問題か言語 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| other | AC * 100 |
ソースコード
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdint>
#include <iostream>
#include <string>
#include <tuple>
#include <unordered_set>
#include <vector>
using namespace std;
using ll = long long;
namespace {
constexpr int DAY_START = 6 * 60;
constexpr int DAY_END = 21 * 60;
constexpr int SLOT_STEP = 5;
constexpr int DEADLINE_COUNT = 21;
constexpr int NEG = -1000000000;
constexpr double TOTAL_TIME_LIMIT_SEC = 0.98;
constexpr double SA_MAX_TIME_SEC = 0.90;
constexpr double SA_MARGIN_SEC = 0.04;
constexpr int BEAM_WIDTH = 16;
constexpr int BEAM_DEPTH = 3;
constexpr int BEAM_SCAN_FIRST = 140;
constexpr int BEAM_SCAN_NEXT = 90;
constexpr int WAIT_FIRST_MAX = 240;
constexpr int WAIT_NEXT_MAX = 90;
constexpr int ZERO_WAIT_ALLOW = 60;
constexpr int REBUILD_MAX_LEGS = 14;
constexpr int INIT_SCAN_MAX = 500;
constexpr int ACTIVE_SCAN_MAX = 190;
constexpr int ACTIVE_WAIT_HORIZON = 120;
constexpr int FUTURE_HORIZON = 120;
constexpr int FUTURE_SCAN_MAX = 70;
constexpr int NEAR_PER_CITY = 4;
constexpr int NEAR_TIME_STEP = 10;
struct Flight {
int a; // 0-index
int b; // 0-index
int s; // minutes
int t; // minutes
};
struct Candidate {
int a;
int b;
int s;
int t;
int edge;
uint32_t mask; // 締切ごとの勝利ビット
ll pair_w; // 対象ODの人口積
ll base_gain; // スクエア単体に対する直行便利得
};
struct PlaneState {
bool active = false;
int city = -1;
int tm = DAY_START;
};
struct XorShift64 {
uint64_t x;
explicit XorShift64(uint64_t seed = 88172645463325252ULL) : x(seed ? seed : 88172645463325252ULL) {}
uint64_t next_u64() {
x ^= x << 7;
x ^= x >> 9;
return x;
}
int next_int(int l, int r) {
return l + (int)(next_u64() % (uint64_t)(r - l + 1));
}
double next_double() {
return (double)((next_u64() >> 11) * (1.0 / 9007199254740992.0));
}
};
int calc_duration_min(long double dist) {
long double raw = 60.0L * dist / 800.0L + 40.0L;
int q = (int)ceill(raw / 5.0L - 1e-12L);
return q * 5;
}
int parse_time(const string& tok) {
size_t p = tok.find(':');
if (p != string::npos) {
int hh = stoi(tok.substr(0, p));
int mm = stoi(tok.substr(p + 1));
return hh * 60 + mm;
}
int x = stoi(tok);
// HHMM 形式にも対応
if (x >= 600 && x <= 2359 && x % 100 < 60) {
return (x / 100) * 60 + (x % 100);
}
// 分として扱う
return x;
}
string to_hhmm(int m) {
char buf[8];
snprintf(buf, sizeof(buf), "%02d:%02d", m / 60, m % 60);
return string(buf);
}
vector<int> build_deadlines() {
vector<int> d(DEADLINE_COUNT);
for (int k = 0; k < DEADLINE_COUNT; ++k) d[k] = 11 * 60 + 30 * k;
return d;
}
inline uint64_t flight_key(int a, int b, int s) {
return ((uint64_t)s << 12) | ((uint64_t)a << 6) | (uint64_t)b;
}
// 時間拡張DPで、開始時刻から終了時刻までの便列を最大時間だけ埋める
vector<Flight> build_best_fill_path(int start_city, int start_tm, int end_tm,
const vector<vector<int>>& dur,
const vector<vector<int>>& sq_edge_count,
const unordered_set<uint64_t>& used_keys,
bool need_return) {
int rem = end_tm - start_tm;
if (rem <= 0) return {};
int lim = rem / SLOT_STEP;
if (lim <= 0) return {};
int n = (int)dur.size();
const int NEG_INF = -1000000000;
vector<vector<int>> best(lim + 1, vector<int>(n, NEG_INF));
vector<vector<int>> prev_t(lim + 1, vector<int>(n, -1));
vector<vector<int>> prev_city(lim + 1, vector<int>(n, -1));
best[0][start_city] = 0;
for (int t = 0; t <= lim; ++t) {
int abs_tm = start_tm + t * SLOT_STEP;
for (int u = 0; u < n; ++u) {
if (best[t][u] == NEG_INF) continue;
for (int v = 0; v < n; ++v) {
if (v == u) continue;
int d = dur[u][v];
int dt = d / SLOT_STEP;
int nt = t + dt;
if (nt > lim) continue;
if (used_keys.find(flight_key(u, v, abs_tm)) != used_keys.end()) continue;
int ne = best[t][u] + sq_edge_count[u][v];
if (ne > best[nt][v]) {
best[nt][v] = ne;
prev_t[nt][v] = t;
prev_city[nt][v] = u;
}
}
}
}
int goal_t = 0;
int goal_city = -1;
if (need_return) {
for (int t = lim; t >= 1; --t) {
if (best[t][start_city] != NEG_INF) {
goal_t = t;
goal_city = start_city;
break;
}
}
} else {
for (int t = lim; t >= 1; --t) {
int best_city = -1;
int best_edge = NEG_INF;
for (int v = 0; v < n; ++v) {
if (best[t][v] > best_edge) {
best_edge = best[t][v];
best_city = v;
}
}
if (best_edge != NEG_INF) {
goal_t = t;
goal_city = best_city;
break;
}
}
}
if (goal_t == 0 || goal_city == -1) return {};
vector<Flight> rev;
int cur_t = goal_t;
int cur_city = goal_city;
while (cur_t > 0) {
int pt = prev_t[cur_t][cur_city];
int pc = prev_city[cur_t][cur_city];
if (pt < 0 || pc < 0) {
rev.clear();
break;
}
rev.push_back(Flight{pc, cur_city, start_tm + pt * SLOT_STEP, start_tm + cur_t * SLOT_STEP});
cur_t = pt;
cur_city = pc;
}
if (rev.empty() || cur_t != 0 || cur_city != start_city) return {};
reverse(rev.begin(), rev.end());
return rev;
}
// 区間[start_tm, end_tm]を、開始都市に戻る制約付きで最大限埋める
void fill_gap_roundtrips(vector<Flight>& out, int base, int start_tm, int end_tm,
const vector<vector<int>>& dur,
const vector<vector<int>>& sq_edge_count,
unordered_set<uint64_t>& used_keys) {
auto path =
build_best_fill_path(base, start_tm, end_tm, dur, sq_edge_count, used_keys, true);
for (const auto& f : path) {
out.push_back(f);
used_keys.insert(flight_key(f.a, f.b, f.s));
}
}
// 末尾区間[start_tm, DAY_END]を、戻り制約なしで最大限埋める
void fill_tail_chain(vector<Flight>& out, int start_city, int start_tm,
const vector<vector<int>>& dur,
const vector<vector<int>>& sq_edge_count,
unordered_set<uint64_t>& used_keys) {
auto path = build_best_fill_path(start_city, start_tm, DAY_END, dur, sq_edge_count, used_keys,
false);
for (const auto& f : path) {
out.push_back(f);
used_keys.insert(flight_key(f.a, f.b, f.s));
}
}
// スクエア便の発着回数が多い都市を返す
int pick_busy_city(const vector<vector<int>>& sq_edge_count) {
int n = (int)sq_edge_count.size();
int best_city = 0;
int best_score = -1;
for (int v = 0; v < n; ++v) {
int sc = 0;
for (int u = 0; u < n; ++u) {
sc += sq_edge_count[v][u];
sc += sq_edge_count[u][v];
}
if (sc > best_score) {
best_score = sc;
best_city = v;
}
}
return best_city;
}
// 既存ルートの前後ギャップを埋めて、待機時間を減らした便列を作る
vector<Flight> densify_route(const vector<int>& route_idx, const vector<Candidate>& cand,
const vector<vector<int>>& dur,
const vector<vector<int>>& sq_edge_count, int fallback_city,
unordered_set<uint64_t>& used_keys) {
vector<Flight> core;
core.reserve(route_idx.size());
for (int ci : route_idx) {
const auto& c = cand[ci];
core.push_back(Flight{c.a, c.b, c.s, c.t});
}
vector<Flight> out;
if (core.empty()) {
fill_tail_chain(out, fallback_city, DAY_START, dur, sq_edge_count, used_keys);
return out;
}
// 初便までの待機を往復で埋める
fill_gap_roundtrips(out, core[0].a, DAY_START, core[0].s, dur, sq_edge_count, used_keys);
for (int i = 0; i < (int)core.size(); ++i) {
out.push_back(core[i]);
used_keys.insert(flight_key(core[i].a, core[i].b, core[i].s));
if (i + 1 < (int)core.size()) {
// 既存2便の間の待機を往復で埋める(出発都市は維持)
fill_gap_roundtrips(out, core[i].b, core[i].t, core[i + 1].s, dur, sq_edge_count,
used_keys);
}
}
// 最終便以降を21:00まで埋める
fill_tail_chain(out, core.back().b, core.back().t, dur, sq_edge_count, used_keys);
return out;
}
// 奪取寄与がない便を、連結性を壊さない範囲で削除する
void prune_redundant_won_routes(vector<vector<int>>& routes, const vector<Candidate>& cand, int n) {
vector<vector<vector<int>>> bit_cnt(
n, vector<vector<int>>(n, vector<int>(DEADLINE_COUNT, 0)));
for (const auto& seq : routes) {
for (int ci : seq) {
const auto& c = cand[ci];
if (c.mask == 0) continue;
for (int k = 0; k < DEADLINE_COUNT; ++k) {
if ((c.mask >> k) & 1u) bit_cnt[c.a][c.b][k]++;
}
}
}
bool changed = true;
while (changed) {
changed = false;
for (int p = 0; p < (int)routes.size(); ++p) {
auto& seq = routes[p];
for (int i = 0; i < (int)seq.size(); ++i) {
int ci = seq[i];
const auto& c = cand[ci];
if (c.mask == 0) continue;
bool has_unique = false;
for (int k = 0; k < DEADLINE_COUNT; ++k) {
if (((c.mask >> k) & 1u) && bit_cnt[c.a][c.b][k] == 1) {
has_unique = true;
break;
}
}
if (has_unique) continue;
bool link_ok = true;
if (i > 0 && i + 1 < (int)seq.size()) {
const auto& prv = cand[seq[i - 1]];
const auto& nxt = cand[seq[i + 1]];
if (prv.b != nxt.a || prv.t > nxt.s) link_ok = false;
}
if (!link_ok) continue;
for (int k = 0; k < DEADLINE_COUNT; ++k) {
if ((c.mask >> k) & 1u) bit_cnt[c.a][c.b][k]--;
}
seq.erase(seq.begin() + i);
--i;
changed = true;
}
}
}
}
// latest[k][dst][src] = 締切kまでにdstへ到着するsrcの最遅出発時刻
vector<vector<vector<int>>> build_latest_tensor(int n, const vector<Flight>& flights,
const vector<int>& deadlines) {
vector<Flight> fs = flights;
sort(fs.begin(), fs.end(), [](const Flight& p, const Flight& q) {
if (p.s != q.s) return p.s > q.s;
return p.t > q.t;
});
vector<vector<vector<int>>> latest(
DEADLINE_COUNT, vector<vector<int>>(n, vector<int>(n, NEG)));
for (int k = 0; k < DEADLINE_COUNT; ++k) {
int dl = deadlines[k];
for (int dst = 0; dst < n; ++dst) {
vector<int> cur(n, NEG);
cur[dst] = dl;
for (const auto& f : fs) {
if (f.t <= cur[f.b] && f.s > cur[f.a]) cur[f.a] = f.s;
}
latest[k][dst] = std::move(cur);
}
}
return latest;
}
ll calc_exact_score(const vector<vector<int>>& routes, const vector<Candidate>& cand,
int n, const vector<int>& deadlines,
const vector<vector<vector<int>>>& latest_sq,
const vector<pair<int, int>>& eval_pairs,
const vector<vector<ll>>& pair_w) {
vector<Flight> circle;
for (const auto& seq : routes) {
for (int ci : seq) {
const auto& c = cand[ci];
circle.push_back(Flight{c.a, c.b, c.s, c.t});
}
}
auto latest_ci = build_latest_tensor(n, circle, deadlines);
ll vci = 0;
for (int k = 0; k < DEADLINE_COUNT; ++k) {
for (const auto& pr : eval_pairs) {
int a = pr.first;
int b = pr.second;
if (latest_sq[k][b][a] < latest_ci[k][b][a]) vci += pair_w[a][b];
}
}
return vci;
}
// 1手先を返すためのビーム(評価はスクエア基準の近似)
int beam_pick_next(int start_city, int start_tm, const vector<Candidate>& cand,
const vector<int>& all_by_time, const vector<vector<int>>& by_from,
const vector<char>& blocked) {
struct Node {
int city;
int tm;
int depth;
int first_ci;
double score;
};
vector<Node> beam;
beam.push_back(Node{start_city, start_tm, 0, -1, 0.0});
int best_first = -1;
double best_score = -1e300;
auto try_expand = [&](const Node& node, int ci, vector<Node>& nxt) {
if (blocked[ci]) return;
const auto& c = cand[ci];
if (c.s < node.tm) return;
int wait = c.s - node.tm;
int wait_lim = (node.city == -1 ? WAIT_FIRST_MAX : WAIT_NEXT_MAX);
if (wait > wait_lim) return;
ll gain = c.base_gain;
int d = c.t - c.s;
double add = -1e300;
if (gain > 0) {
add = 4.2 * (double)gain + 140.0 * c.edge - 2.8 * wait - 0.5 * d;
} else {
// 既に奪取済み(増分なし)になりやすい便は再構築でも採用しない
if (c.mask != 0) return;
if (c.edge == 0) return;
if (wait > ZERO_WAIT_ALLOW) return;
add = 130.0 * c.edge - 9.0 * wait - 0.7 * d;
}
Node nn;
nn.city = c.b;
nn.tm = c.t;
nn.depth = node.depth + 1;
nn.first_ci = (node.first_ci == -1 ? ci : node.first_ci);
nn.score = node.score + add;
if (nn.first_ci != -1 && nn.score > best_score) {
best_score = nn.score;
best_first = nn.first_ci;
}
nxt.push_back(nn);
};
for (int dep = 0; dep < BEAM_DEPTH; ++dep) {
vector<Node> nxt;
nxt.reserve((size_t)beam.size() * 64ULL);
for (const auto& node : beam) {
if (node.city == -1) {
auto it = lower_bound(all_by_time.begin(), all_by_time.end(), node.tm,
[&](int idx, int t0) { return cand[idx].s < t0; });
int scanned = 0;
for (; it != all_by_time.end() && scanned < BEAM_SCAN_FIRST; ++it, ++scanned) {
try_expand(node, *it, nxt);
}
} else {
const auto& list = by_from[node.city];
auto it = lower_bound(list.begin(), list.end(), node.tm,
[&](int idx, int t0) { return cand[idx].s < t0; });
int scanned = 0;
for (; it != list.end() && scanned < BEAM_SCAN_NEXT; ++it, ++scanned) {
try_expand(node, *it, nxt);
}
}
}
if (nxt.empty()) break;
sort(nxt.begin(), nxt.end(), [](const Node& a, const Node& b) {
if (a.score != b.score) return a.score > b.score;
if (a.tm != b.tm) return a.tm < b.tm;
return a.city < b.city;
});
if ((int)nxt.size() > BEAM_WIDTH) nxt.resize(BEAM_WIDTH);
beam.swap(nxt);
}
return best_first;
}
// 1機体をビームで再構築
vector<int> rebuild_plane_with_beam(
int p, const vector<vector<int>>& routes, const vector<Candidate>& cand,
const vector<int>& all_by_time, const vector<vector<int>>& by_from,
int n, int slot_cnt) {
(void)n;
(void)slot_cnt;
// 他機体で既に使われている候補は再利用しない
vector<char> blocked(cand.size(), 0);
for (int q = 0; q < (int)routes.size(); ++q) {
if (q == p) continue;
for (int ci : routes[q]) blocked[ci] = 1;
}
vector<int> seq;
int city = -1;
int tm = DAY_START;
int zero_streak = 0;
for (int leg = 0; leg < REBUILD_MAX_LEGS; ++leg) {
int ci = beam_pick_next(city, tm, cand, all_by_time, by_from, blocked);
if (ci == -1) break;
const auto& c = cand[ci];
if (c.base_gain <= 0) {
++zero_streak;
} else {
zero_streak = 0;
}
if (zero_streak >= 3) break;
seq.push_back(ci);
blocked[ci] = 1;
city = c.b;
tm = c.t;
if (tm >= DAY_END) break;
}
return seq;
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
auto global_start = chrono::steady_clock::now();
int N, R;
cin >> N >> R;
vector<int> x(N), y(N);
vector<ll> w(N);
for (int i = 0; i < N; ++i) cin >> x[i] >> y[i] >> w[i];
int M;
cin >> M;
vector<Flight> square;
square.reserve(M);
for (int i = 0; i < M; ++i) {
int a, b;
string s_tok, t_tok;
cin >> a >> s_tok >> b >> t_tok;
int s = parse_time(s_tok);
int t = parse_time(t_tok);
square.push_back(Flight{a - 1, b - 1, s, t});
}
int K;
cin >> K;
vector<int> deadlines = build_deadlines();
int SLOT_CNT = (DAY_END - DAY_START) / SLOT_STEP + 1;
// 直行所要時間
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) continue;
long double dx = (long double)x[i] - (long double)x[j];
long double dy = (long double)y[i] - (long double)y[j];
long double d = sqrtl(dx * dx + dy * dy);
dur[i][j] = calc_duration_min(d);
}
}
// スクエア便密度
vector<vector<int>> sq_edge_count(N, vector<int>(N, 0));
for (const auto& f : square) sq_edge_count[f.a][f.b]++;
// 評価対象ODと人口積
vector<vector<char>> valid_pair(N, vector<char>(N, 0));
vector<vector<ll>> pair_w(N, vector<ll>(N, 0));
vector<pair<int, int>> eval_pairs;
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
if (i == j) continue;
long double dx = (long double)x[i] - (long double)x[j];
long double dy = (long double)y[i] - (long double)y[j];
long double d = sqrtl(dx * dx + dy * dy);
if (d + 1e-12L >= 0.25L * (long double)R) {
valid_pair[i][j] = 1;
pair_w[i][j] = w[i] * w[j];
eval_pairs.push_back({i, j});
}
}
}
// スクエア側の最遅出発時刻
auto latest_sq = build_latest_tensor(N, square, deadlines);
// 候補便生成(スクエアOD + 近距離OD)
vector<Candidate> cand;
cand.reserve(M * 9 + N * 8 * 120);
const int shifts[9] = {0, 5, 10, 15, 20, 25, 30, 35, 40};
for (const auto& f : square) {
int a = f.a, b = f.b;
int d = dur[a][b];
for (int sh : shifts) {
int s = f.s + sh;
int t = s + d;
if (s < DAY_START || t > DAY_END) continue;
uint32_t mask = 0;
if (valid_pair[a][b]) {
for (int k = 0; k < DEADLINE_COUNT; ++k) {
if (t > deadlines[k]) continue;
if (s > latest_sq[k][b][a]) mask |= (1u << k);
}
}
ll pw = pair_w[a][b];
ll bg = pw * (ll)__builtin_popcount(mask);
cand.push_back(Candidate{a, b, s, t, sq_edge_count[a][b], mask, pw, bg});
}
}
// 待機活用のため、各都市から近い都市への便も候補に入れる
for (int a = 0; a < N; ++a) {
vector<pair<int, int>> near;
near.reserve(N - 1);
for (int b = 0; b < N; ++b) {
if (a == b) continue;
near.push_back({dur[a][b], b});
}
sort(near.begin(), near.end());
int use = min((int)near.size(), NEAR_PER_CITY);
for (int z = 0; z < use; ++z) {
int b = near[z].second;
int d = dur[a][b];
// あまり長い便は待機埋め用途としては使いにくいので除外
if (d > 120) continue;
for (int s = DAY_START; s + d <= DAY_END; s += NEAR_TIME_STEP) {
int t = s + d;
uint32_t mask = 0;
if (valid_pair[a][b]) {
for (int k = 0; k < DEADLINE_COUNT; ++k) {
if (t > deadlines[k]) continue;
if (s > latest_sq[k][b][a]) mask |= (1u << k);
}
}
ll pw = pair_w[a][b];
ll bg = pw * (ll)__builtin_popcount(mask);
cand.push_back(Candidate{a, b, s, t, sq_edge_count[a][b], mask, pw, bg});
}
}
}
// 重複除去
sort(cand.begin(), cand.end(), [](const Candidate& p, const Candidate& q) {
return tie(p.a, p.b, p.s) < tie(q.a, q.b, q.s);
});
cand.erase(unique(cand.begin(), cand.end(), [](const Candidate& p, const Candidate& q) {
return p.a == q.a && p.b == q.b && p.s == q.s;
}),
cand.end());
// 候補便の索引
vector<vector<int>> by_from(N);
vector<int> all_by_time;
all_by_time.reserve(cand.size());
for (int i = 0; i < (int)cand.size(); ++i) {
by_from[cand[i].a].push_back(i);
all_by_time.push_back(i);
}
for (int a = 0; a < N; ++a) {
sort(by_from[a].begin(), by_from[a].end(), [&](int i, int j) {
return cand[i].s < cand[j].s;
});
}
sort(all_by_time.begin(), all_by_time.end(), [&](int i, int j) {
return cand[i].s < cand[j].s;
});
// 初期解: 増分奪取を主軸にした全体貪欲
vector<vector<int>> routes(K);
vector<PlaneState> ps(K);
vector<vector<uint32_t>> won_mask(N, vector<uint32_t>(N, 0));
vector<char> used_ci(cand.size(), 0);
auto incremental_gain = [&](const Candidate& c) -> ll {
if (c.pair_w == 0 || c.mask == 0) return 0;
uint32_t add_bits = c.mask & (~won_mask[c.a][c.b]);
if (add_bits == 0) return 0;
return c.pair_w * (ll)__builtin_popcount(add_bits);
};
auto future_gain_from = [&](int city, int tm) -> ll {
const auto& list = by_from[city];
auto it = lower_bound(list.begin(), list.end(), tm,
[&](int idx, int t0) { return cand[idx].s < t0; });
ll best = 0;
int checked = 0;
for (; it != list.end(); ++it) {
int ci = *it;
if (used_ci[ci]) continue;
const auto& c = cand[ci];
if (c.s > tm + FUTURE_HORIZON) break;
if (++checked > FUTURE_SCAN_MAX) break;
ll g = incremental_gain(c);
if (g > best) best = g;
}
return best;
};
while (true) {
ll best_gain = 0;
int best_wait = 1e9;
int best_edge = -1;
int best_p = -1;
int best_ci = -1;
ll best_move_future = 0;
int best_move_wait = 1e9;
int best_move_edge = -1;
int best_move_p = -1;
int best_move_ci = -1;
int first_inactive = -1;
for (int p = 0; p < K; ++p) {
if (!ps[p].active) {
first_inactive = p;
break;
}
}
// 未使用機体の初手候補は1回だけ評価する
if (first_inactive != -1) {
int scanned = 0;
for (int idx : all_by_time) {
if (used_ci[idx]) continue;
const auto& c = cand[idx];
int wait = c.s - DAY_START;
if (wait < 0) continue;
if (wait > WAIT_FIRST_MAX) break;
if (++scanned > INIT_SCAN_MAX) break;
ll gain = incremental_gain(c);
if (gain > 0) {
if (gain > best_gain ||
(gain == best_gain && wait < best_wait) ||
(gain == best_gain && wait == best_wait && c.edge > best_edge)) {
best_gain = gain;
best_wait = wait;
best_edge = c.edge;
best_p = first_inactive;
best_ci = idx;
}
} else {
if (c.mask != 0) continue;
if (wait > ZERO_WAIT_ALLOW) continue;
if (c.edge == 0) continue;
ll fut = future_gain_from(c.b, c.t);
if (fut <= 0) continue;
if (fut > best_move_future ||
(fut == best_move_future && wait < best_move_wait) ||
(fut == best_move_future && wait == best_move_wait &&
c.edge > best_move_edge)) {
best_move_future = fut;
best_move_wait = wait;
best_move_edge = c.edge;
best_move_p = first_inactive;
best_move_ci = idx;
}
}
}
}
// 使用中機体の次手候補を評価
for (int p = 0; p < K; ++p) {
if (!ps[p].active) continue;
int cur = ps[p].city;
int tm = ps[p].tm;
const auto& list = by_from[cur];
auto it = lower_bound(list.begin(), list.end(), tm,
[&](int idx, int t0) { return cand[idx].s < t0; });
int scanned = 0;
for (; it != list.end() && scanned < ACTIVE_SCAN_MAX; ++it, ++scanned) {
int ci = *it;
if (used_ci[ci]) continue;
const auto& c = cand[ci];
int wait = c.s - tm;
if (wait > ACTIVE_WAIT_HORIZON) break;
ll gain = incremental_gain(c);
if (gain > 0) {
if (gain > best_gain ||
(gain == best_gain && wait < best_wait) ||
(gain == best_gain && wait == best_wait && c.edge > best_edge)) {
best_gain = gain;
best_wait = wait;
best_edge = c.edge;
best_p = p;
best_ci = ci;
}
} else {
if (c.mask != 0) continue;
if (wait < 0 || wait > ZERO_WAIT_ALLOW) continue;
if (c.edge == 0) continue;
ll fut = future_gain_from(c.b, c.t);
if (fut <= 0) continue;
if (fut > best_move_future ||
(fut == best_move_future && wait < best_move_wait) ||
(fut == best_move_future && wait == best_move_wait &&
c.edge > best_move_edge)) {
best_move_future = fut;
best_move_wait = wait;
best_move_edge = c.edge;
best_move_p = p;
best_move_ci = ci;
}
}
}
}
int pick_p = -1;
int pick_ci = -1;
ll pick_gain = 0;
if (best_p != -1 && best_ci != -1 && best_gain > 0) {
// 将来見込みが十分高い0奪取移動は優先を許可
if (best_move_p != -1 && best_move_ci != -1 &&
best_move_future * 10 >= best_gain * 13LL) {
pick_p = best_move_p;
pick_ci = best_move_ci;
pick_gain = 0;
} else {
pick_p = best_p;
pick_ci = best_ci;
pick_gain = best_gain;
}
} else if (best_move_p != -1 && best_move_ci != -1) {
pick_p = best_move_p;
pick_ci = best_move_ci;
pick_gain = 0;
} else {
break;
}
const auto& c = cand[pick_ci];
if (!ps[pick_p].active) ps[pick_p].active = true;
ps[pick_p].city = c.b;
ps[pick_p].tm = c.t;
routes[pick_p].push_back(pick_ci);
used_ci[pick_ci] = 1;
if (pick_gain > 0) {
won_mask[c.a][c.b] |= c.mask;
}
}
// 焼きなまし: 1機体をビーム再構築して採用可否を温度で判定
auto start_tp = chrono::steady_clock::now();
XorShift64 rng(123456789ULL);
ll cur_score = calc_exact_score(routes, cand, N, deadlines, latest_sq, eval_pairs, pair_w);
vector<vector<int>> best_routes = routes;
ll best_score = cur_score;
long double t0 = max(1.0L, fabsl((long double)cur_score) * 0.02L);
long double t1 = max(1.0L, fabsl((long double)cur_score) * 1e-6L);
double pre_elapsed =
chrono::duration<double>(chrono::steady_clock::now() - global_start).count();
double sa_budget = min(SA_MAX_TIME_SEC, TOTAL_TIME_LIMIT_SEC - pre_elapsed - SA_MARGIN_SEC);
if (sa_budget < 0.0) sa_budget = 0.0;
while (sa_budget > 0.0) {
double elapsed = chrono::duration<double>(chrono::steady_clock::now() - start_tp).count();
if (elapsed >= sa_budget) break;
int p = rng.next_int(0, K - 1);
vector<vector<int>> nxt_routes = routes;
nxt_routes[p] =
rebuild_plane_with_beam(p, routes, cand, all_by_time, by_from, N, SLOT_CNT);
if (nxt_routes[p] == routes[p]) continue;
ll nxt_score =
calc_exact_score(nxt_routes, cand, N, deadlines, latest_sq, eval_pairs, pair_w);
ll delta = nxt_score - cur_score;
bool accept = false;
if (delta >= 0) {
accept = true;
} else {
long double r = (long double)elapsed / (long double)sa_budget;
long double temp = t0 * pow((double)(t1 / t0), (double)r);
long double prob = expl((long double)delta / temp);
if (prob > (long double)rng.next_double()) accept = true;
}
if (accept) {
routes.swap(nxt_routes);
cur_score = nxt_score;
if (cur_score > best_score) {
best_score = cur_score;
best_routes = routes;
}
}
}
// 探索後に、寄与のない奪取便を削って待機埋めの余地を作る
prune_redundant_won_routes(best_routes, cand, N);
// 出力直前に待機時間を埋める後処理を適用
int busy_city = pick_busy_city(sq_edge_count);
vector<vector<Flight>> final_flights(K);
unordered_set<uint64_t> used_keys;
used_keys.reserve(cand.size() * 2 + 4096);
// コア便を先に予約し、後から入れる待機埋め便との衝突を防ぐ
for (int p = 0; p < K; ++p) {
for (int ci : best_routes[p]) {
const auto& c = cand[ci];
used_keys.insert(flight_key(c.a, c.b, c.s));
}
}
for (int p = 0; p < K; ++p) {
int fallback_city = busy_city;
if (!best_routes[p].empty()) {
fallback_city = cand[best_routes[p][0]].a;
}
final_flights[p] =
densify_route(best_routes[p], cand, dur, sq_edge_count, fallback_city, used_keys);
}
for (int p = 0; p < K; ++p) {
cout << final_flights[p].size() << '\n';
for (const auto& f : final_flights[p]) {
cout << (f.a + 1) << ' ' << to_hhmm(f.s) << ' ' << (f.b + 1) << ' ' << to_hhmm(f.t)
<< '\n';
}
}
return 0;
}