1 条题解
-
0
思路与算法
把一条乘完的线路看成状态。每站两条入线与两条出线只有两种一一接续配对;入线a接出线b的等待为(l_b-(l_a+d_a) mod 24+24) mod 24。枚举1号站的两种配对与被切开的两条接续之一:切开处的出线作为第一班,入线作为最终一班,费用用首班发车l替换该接续的环形等待。其余站各选等待总和更小的配对,所有边状态形成若干互不相交的循环。翻转某站的配对会交换两条入线的后继,若它们原在两个循环中,就把两个循环合为一个;代价是该站另一配对减当前最小配对的非负差值。将基础循环缩成点,各可翻转站连接它的两个入线所在循环,求最小生成树。连接成功的最小费用加所有线路行驶时间与基础等待,四种首尾情况取小。
正确性说明
任何Euler行程在每站都给两入线与两出线指定一个双射,在1号站把末线虚接首线后也成立,因此被枚举的中央配对与切口必含最优行程。给定该选择后,每条接续等待只依赖线路时刻,行驶与等待费用精确可加。其余站独立选最小配对使任何翻转的增量非负。基础接续排列分成循环;一次跨循环翻转恰合并两个循环,同循环翻转会拆环。任何最终单循环所用的跨基础环翻转,必须在基础环之间形成连通图,否则不可能跨越被分离的环集合,因此其非负增量之和至少为该图MST权。反过来,按MST逐条翻转时,每条树边总连接两个尚分离的环组,归纳每组对应一个已合并循环,最终得到一个循环且增量恰为MST权。沿所定切口展开即可得到合法1号首尾行程,所以下界可实现,四种情况最小值为全局最优。
复杂度
每种首尾情况初始化和找基础环O(n),构造至多n-1条候选翻转边并排序、Kruskal O(n log n),一共4种,总O(n log n)时间、O(n)空间。输入与所有数组初始化O(n)。
易错点
到站恰逢发车不必多等一天,等待取0..23。d可超过24小时,不能只把d mod 24计入总行驶时间。1号站首班从绝对0时等待,其费用必须替换而不是额外叠加环形等待;中央配对在当前枚举中固定,不能再作为MST翻转边。相同起终点的两条线路仍是不同状态。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; struct DSU { vector<int> p; DSU(int n) : p(n) { iota(p.begin(), p.end(), 0); } int find(int a) { return p[a] == a ? a : p[a] = find(p[a]); } bool join(int a, int b) { a = find(a); b = find(b); if (a == b) return false; p[a] = b; return true; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; if (!(cin >> n)) return 0; int m = 2 * n; vector<int> dest(m), leave(m), duration(m), incomingCount(n); vector<array<int, 2>> in(n); long long total = 0; for (int i = 0; i < m; i++) { cin >> dest[i] >> leave[i] >> duration[i]; --dest[i]; in[dest[i]][incomingCount[dest[i]]++] = i; total += duration[i]; } auto wait = [&](int a, int b) { return (leave[b] - (leave[a] + duration[a]) % 24 + 24) % 24; }; vector<array<int, 2>> cost(n); for (int v = 0; v < n; v++) for (int t = 0; t < 2; t++) cost[v][t] = wait(in[v][0], 2 * v + t) + wait(in[v][1], 2 * v + (t ^ 1)); long long answer = LLONG_MAX; for (int rootMatch = 0; rootMatch < 2; rootMatch++) for (int cut = 0; cut < 2; cut++) { vector<int> choice(n), next(m); long long base = total; for (int v = 0; v < n; v++) { int t = v == 0 ? rootMatch : (cost[v][0] <= cost[v][1] ? 0 : 1); choice[v] = t; base += cost[v][t]; next[in[v][0]] = 2 * v + t; next[in[v][1]] = 2 * v + (t ^ 1); } int last = in[0][cut], first = next[last]; base += leave[first] - wait(last, first); vector<int> cycle(m, -1); int count = 0; for (int i = 0; i < m; i++) if (cycle[i] < 0) { for (int x = i; cycle[x] < 0; x = next[x]) cycle[x] = count; count++; } vector<array<int, 3>> edges; for (int v = 1; v < n; v++) { int a = cycle[in[v][0]], b = cycle[in[v][1]]; if (a != b) edges.push_back({{cost[v][choice[v] ^ 1] - cost[v][choice[v]], a, b}}); } sort(edges.begin(), edges.end()); DSU d(count); int components = count; for (auto e : edges) if (d.join(e[1], e[2])) base += e[0], components--; if (components == 1) answer = min(answer, base); } cout << answer << '\n'; }
- 1