P10538 [APIO2024] 星际列车 题解报告
题面
P10538 [APIO2024] 星际列车
题目描述
请勿使用 C++14 (GCC 9) 提交。
在 2992 年,机器人已经取代了人类的大部分工作,大家都有着大量的空闲时间。因此你和家人决定利用这些时间来一场星际旅行。
有 \(N\) 个人类已经可以到达的行星,编号为 \(0\) 到 \(N - 1\),以及 \(M\) 种不同的星际列车路线。第 \(i\) 种列车路线 (\(0 \le i < M\)) 在时间 \(A[i]\) 从行星 \(X[i]\) 出发,在时间 \(B[i]\) 到达行星 \(Y[i]\),票价为 \(C[i]\)。在行星之间,这些星际列车是仅有的交通方式。对于你搭乘的一列星际列车,你只能在它的终点站下车,并且你搭乘的下一趟列车的起点站必须和这趟列车的终点站相同(这里认为换乘不耗时)。形式化地,你可以依次乘坐第 \(q[0], q[1], \ldots, q[P]\) 次列车,当且仅当对任意 \(1 \le k \le P\) 都有 \(Y[q[k - 1]] = X[q[k]]\),\(B[q[k - 1]] \le A[q[k]]\)。
在不同行星之间移动是非常耗时的,所以除了车票钱,餐费支出也不可忽视。列车上免费提供不限量的食物,也就是在列车上吃饭不花钱:如果你决定乘坐第 \(i\) 种星际列车,则在任何 \(A[i]\) 到 \(B[i]\) 之间的时刻(包括端点)你都可以免费吃任意多顿饭。但如果你决定在行星 \(i\) 吃饭,每顿饭都需要 \(T[i]\) 元。
你和家人在旅途中总共需要吃 \(W\) 顿饭,第 \(i\ (0 \le i < W)\) 顿饭可以在 \(L[i]\) 到 \(R[i]\)(包括端点)的任何时刻吃,吃饭不耗费时间。吃饭没有顺序要求,例如允许在吃完第 \(1\) 顿饭后再吃第 \(0\) 顿饭(见样例 \(2\))。
现在是 \(0\) 时刻,你和家人正在 \(0\) 号行星上。你需要求出到达 \(N - 1\) 号行星的最小花费,花费定义为车票价格和餐费之和。如果无法到达 \(N - 1\) 号行星,最小花费定义为 \(-1\)。
实现细节
你无需在程序开头引入库 train.h。
你只需要实现以下函数:
long long solve(int N, int M, int W, std::vector<int> T,
std::vector<int> X, std::vector<int> Y,
std::vector<int> A, std::vector<int> B, std::vector<int> C,
std::vector<int> L, std::vector<int> R);
- \(N\):行星数量。
- $ M$:星际列车路线数量。
- \(W\):需要用餐的次数。
- \(T\):一个长度为 \(N\) 的数组。\(T[i]\) 表示在行星 \(i\) 每次用餐的花费。
- \(X, Y, A, B, C\):五个长为 \(M\) 的数组。\((X[i], Y[i], A[i], B[i], C[i])\) 描述了第 \(i\) 条列车路线。
- \(L, R\):两个长为 \(W\) 的数组。\((L[i], R[i])\) 描述了第 \(i\) 顿饭的用餐时间。
- 你需要返回从行星 \(0\) 到达行星 \(N - 1\) 的最小花费。如果行星 \(N - 1\) 不可达,返回 \(-1\)。
- 每个测试点中,该函数恰好被调用一次。
输入格式
评测程序示例读取如下格式的输入:
- 第 \(1\) 行:\(N\ M\ W\)
- 第 \(2\) 行:\(T[0]\ T[1]\ T[2]\ \ldots\ T[N - 1]\)
- 第 \(3 + i\ (0 \le i < M)\) 行:\(X[i]\ Y[i]\ A[i]\ B[i]\ C[i]\)
- 第 \(3 + M + i\ (0 \le i < W)\) 行:\(L[i]\ R[i]\)
输出格式
评测程序示例按照如下格式打印你的答案:
- 第 \(1\) 行:函数
solve的返回值
输入输出样例 #1
输入 #1
3 3 1
20 30 40
0 1 1 15 10
1 2 20 30 5
0 2 18 40 40
16 19
输出 #1
40
输入输出样例 #2
输入 #2
3 5 6
30 38 33
0 2 12 16 38
1 0 48 50 6
0 1 26 28 23
0 2 6 7 94
1 2 49 54 50
32 36
14 14
42 45
37 40
2 5
4 5
输出 #2
197
说明/提示
样例解释
对于样例一,考虑如下调用:
solve(3, 3, 1, {20, 30, 40}, {0, 1, 0}, {1, 2, 2},
{1, 20, 18}, {15, 30, 40}, {10, 5, 40}, {16}, {19});
一种可行的方案是依次乘坐第 \(0, 1\) 次列车,花费为 \(45\),具体流程如下:
| 时刻 | 你的行动 | 花费 |
|---|---|---|
| \(1\) | 乘坐第 \(0\) 次列车从 \(0\) 号行星出发 | \(10\) |
| \(15\) | 到达 \(1\) 号行星 | |
| \(16\) | 在 \(1\) 号行星吃第 \(0\) 顿饭 | \(30\) |
| \(20\) | 乘坐第 \(1\) 次列车从 \(1\) 号行星出发 | \(5\) |
| \(30\) | 到达 \(2\) 号行星 |
一种更优的方案是乘坐第 \(2\) 次列车,花费为 \(40\),具体流程如下:
| 时刻 | 你的行动 | 花费 |
|---|---|---|
| \(18\) | 乘坐第 \(2\) 次列车从 \(0\) 号行星出发 | \(40\) |
| \(19\) | 在第 \(2\) 次列车上吃第 \(0\) 顿饭 | |
| \(40\) | 到达 \(2\) 号行星 |
在这种方案中,在时刻 \(18\) 在第 \(2\) 次列车上吃第 \(0\) 顿饭也是合法的。
因此函数应该返回 \(40\)。
对于样例二,考虑如下调用:
solve(3, 5, 6, {30, 38, 33}, {0, 1, 0, 0, 1}, {2, 0, 1, 2, 2},
{12, 48, 26, 6, 49}, {16, 50, 28, 7, 54}, {38, 6, 23, 94, 50},
{32, 14, 42, 37, 2, 4}, {36, 14, 45, 40, 5, 5});
最优解是:乘坐第 \(0\) 次列车,车费为 \(38\)。在第 \(0\) 次列车上免费吃第 \(1\) 顿饭。第 \(0, 2, 3\) 顿饭在行星 \(2\) 上吃 ,花费 \(33 \times 3 = 99\)。 第 \(4, 5\) 顿饭在行星 \(0\) 上吃,花费 \(30 \times 2 = 60\)。总花费为 \(38 + 99 + 60 = 197\)。
因此函数应该返回 \(197\)。
数据范围
- \(2 \le N \le 10^5\)
- \(0 \le M, W \le 10^5\)
- \(0 \le X[i], Y [i] < N, X[i] \neq Y[i]\)
- \(1 \le A[i] < B[i] \le 10^9\)
- \(1 \le T[i], C[i] \le 10^9\)
- \(1 \le L[i] \le R[i] \le 10^9\)
详细子任务附加限制及分值如下表所示。
| 子任务编号 | 附加限制 | 分值 |
|---|---|---|
| \(1\) | \(N, M, A[i], B[i], L[i], R[i] \le 10^3, W \le 10\) | \(5\) |
| \(2\) | \(W = 0\) | \(5\) |
| \(3\) | 每顿饭的用餐时间两两不交。形式化地,对于任何时刻 \(z\) 满足 \(1 \le z \le 10^9\),至多存在一个 \(i\ (0 \le i < W)\) 使得 \(L[i] \le z \le R[i]\)。 | \(30\) |
| \(4\) | 没有额外的约束条件 | \(60\) |
考虑 \(\text{DP}\),设 \(f_i\) 为经过第 \(i\) 条边,到达结点 \(Y_i\) 的最少代价。方便起见,给起点和终点增加一条自环,最后的答案就是 \(f_{m+1}\)。考虑转移,\(f_i=C_i+\min_{X_i = Y_j, B_j \le A_i} f_j + T_{X_i}\cdot w(B_j+1, A_i-1)\),其中,\(w(l, r)\) 表示有多少 \([L_i, R_i]\subset[l, r]\)。
定义 \(e,ej\) 分别为当前 \(f_i\) 所对应的 \(i\) 和 当前可以被转移的 \(j\)。显然 \(e\) 以 \(A_i\) 排序,\(ej\) 以 \(B_i\) 排序,这样可以使某个节点的入边和出边在序列上呈现连续的一段。
注意到,\(w(l, r)\) 具有决策单调性,不妨设 \(4\) 个决策点 \(a \le b \le c\le d\),有 \(w(a, c) + w(b, d) \le w(a, d) + w(b, c)\),因为后者会多出 \(l\in(a, b], r \in [c, d)\) 的贡献。
对于满足决策单调性的问题,我们一般考虑二分队列。
-
对于假如决策点 \(j\):
设当前队尾为 \((j', l', r')\),决策 \(j'\) 在 \(l'\) 处的价值劣于 \(j\) 在 \(l'\) 处的价值,则弹出队尾,直到不满足为止。
然后我们对队尾进行划分,找到由 \(j\) 转移比 \(j'\) 转移价值大的点,由于决策单调性,这些点一定是一段后缀,使用二分,将其分裂出去。 -
对于计算 \(i\) 的答案:
对队头进行操作,若队头 \((j, l, r)\) 满足 \(r < i\) 说明队头过时,弹出,反复操作,得到 \(i\) 的最优决策点,若当前 \(l < i\) 则令 \(l = i\)。
但是,题目还有一个 \(A_i=B_j\) 的限制,解决方案是对每个节点都维护一个这样的队列,\((j, l, r)\) 也由改动,表示由 \(j\) 这条边转移到 \([l, r]\) 为最优。对于 \(w(l, r)\),用主席树维护一下即可。
$\textit{Code}$
#include <bits/stdc++.h>
using ll = long long; /* I Love You pxy */
constexpr int N = 2e5 + 5; /* I Love You pxy */
constexpr ll INF = 1e18; /* I Love You pxy */
constexpr int T = N * 22; /* I Love You pxy */
struct SegNode { int ls, rs, sum; /* I Love You pxy */ } seg[T]; /* I Love You pxy */
int rt[N], tot; /* I Love You pxy */
void modify(int &p, int pre, int l, int r, int pos, int val)
{
seg[p = ++tot] = seg[pre]; /* I Love You pxy */
if (l == r) { seg[p].sum += val; /* I Love You pxy */ return; /* I Love You pxy */ }
int mid = l + r >> 1; /* I Love You pxy */
if (pos <= mid) modify(seg[p].ls, seg[pre].ls, l, mid, pos, val); /* I Love You pxy */
else modify(seg[p].rs, seg[pre].rs, mid + 1, r, pos, val); /* I Love You pxy */
seg[p].sum = seg[seg[p].ls].sum + seg[seg[p].rs].sum; /* I Love You pxy */
}
int query(int p, int l, int r, int ql, int qr)
{
if (!p || ql > qr) return 0; /* I Love You pxy */
if (ql <= l && r <= qr) return seg[p].sum; /* I Love You pxy */
int mid = l + r >> 1, res = 0; /* I Love You pxy */
if (ql <= mid) res += query(seg[p].ls, l, mid, ql, qr); /* I Love You pxy */
if (mid + 1 <= qr) res += query(seg[p].rs, mid + 1, r, ql, qr); /* I Love You pxy */
return res; /* I Love You pxy */
}
class DecisionQueue {
public:
struct Node { int j, l, r; /* I Love You pxy */ }; /* I Love You pxy */
std::vector < Node > q; /* I Love You pxy */
int head = 0, tail = -1; /* I Love You pxy */
bool empty() { return head > tail; /* I Love You pxy */ }
void push_back(Node node) { q.push_back(node); /* I Love You pxy */ tail++; /* I Love You pxy */ }
void pop_back() { q.pop_back(); /* I Love You pxy */ tail--; /* I Love You pxy */ }
void pop_front() { head++; /* I Love You pxy */ }
Node& front() { return q[head]; /* I Love You pxy */ }
Node& back() { return q[tail]; /* I Love You pxy */ }
}; /* I Love You pxy */
ll solve(int n, int m, int w, std::vector < int > T,
std::vector < int > X, std::vector < int > Y,
std::vector < int > A, std::vector < int > B, std::vector < int > C,
std::vector < int > L, std::vector < int > R) {
struct Edge { int u, v, p, q, w, id; /* I Love You pxy */ }; /* I Love You pxy */
std::vector < Edge > e(m + 2), ej(m + 2); /* I Love You pxy */
std::vector < ll > f(m + 2, INF); /* I Love You pxy */
e[0] = {0, 0, 0, 0, 0, 0}; /* I Love You pxy */ f[0] = 0; /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i < m; /* I Love You pxy */ i++) e[i + 1] = {X[i], Y[i], A[i], B[i], C[i], i + 1}; /* I Love You pxy */
e[m + 1] = {n - 1, n - 1, (int)2e9, (int)2e9, 0, m + 1}; /* I Love You pxy */
std::vector < int > times; /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i <= m + 1; /* I Love You pxy */ i++) {
times.push_back(e[i].p); /* I Love You pxy */ times.push_back(e[i].q); /* I Love You pxy */
}
for (int i = 0; /* I Love You pxy */ i < w; /* I Love You pxy */ i++) {
times.push_back(L[i]); /* I Love You pxy */ times.push_back(R[i]); /* I Love You pxy */
}
std::sort(times.begin(), times.end()); /* I Love You pxy */
times.erase(std::unique(times.begin(), times.end()), times.end()); /* I Love You pxy */
int TIME_MAX = times.size() + 5; /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i <= m + 1; /* I Love You pxy */ i++) {
e[i].p = std::lower_bound(times.begin(), times.end(), e[i].p) - times.begin() + 1; /* I Love You pxy */
e[i].q = std::lower_bound(times.begin(), times.end(), e[i].q) - times.begin() + 1; /* I Love You pxy */
}
for (int i = 0; /* I Love You pxy */ i < w; /* I Love You pxy */ i++) {
L[i] = std::lower_bound(times.begin(), times.end(), L[i]) - times.begin() + 1; /* I Love You pxy */
R[i] = std::lower_bound(times.begin(), times.end(), R[i]) - times.begin() + 1; /* I Love You pxy */
}
std::vector < std::pair < int, int > > meals; /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i < w; /* I Love You pxy */ i++) meals.push_back({R[i], L[i]}); /* I Love You pxy */
std::sort(meals.begin(), meals.end()); /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i < w; /* I Love You pxy */ i++) modify(rt[i + 1], rt[i], 0, TIME_MAX, meals[i].second, 1); /* I Love You pxy */
auto count_meals = [&](int l, int r) -> int {
if (l > r) return 0; /* I Love You pxy */
int idx = std::upper_bound(meals.begin(), meals.end(), std::make_pair(r, INT_MAX)) - meals.begin(); /* I Love You pxy */
return query(rt[idx], 0, TIME_MAX, l, TIME_MAX); /* I Love You pxy */
}; /* I Love You pxy */
std::sort(e.begin(), e.end(), [](const Edge & a, const Edge & b) { return a.p < b.p; /* I Love You pxy */ }); /* I Love You pxy */
ej = e; /* I Love You pxy */
std::sort(ej.begin(), ej.end(), [](const Edge & a, const Edge & b) { return a.q < b.q; /* I Love You pxy */ }); /* I Love You pxy */
std::vector < std::vector < int > > qaq(n); /* I Love You pxy */
for (int i = 0; /* I Love You pxy */ i <= m + 1; /* I Love You pxy */ i++) qaq[e[i].u].push_back(i); /* I Love You pxy */
auto val = [&](int i, int j) -> ll {
ll res = f[ej[j].id] + e[i].w; /* I Love You pxy */
int l = ej[j].q + 1, r = e[i].p - 1; /* I Love You pxy */
res += 1ll * count_meals(l, r) * T[e[i].u]; /* I Love You pxy */
return std::min(res, INF); /* I Love You pxy */
}; /* I Love You pxy */
std::vector < DecisionQueue > queues(n); /* I Love You pxy */
int mxj = 0; /* I Love You pxy */
for (int i = 1; /* I Love You pxy */ i <= m + 1; /* I Love You pxy */ i++)
{
int cur = e[i].u; /* I Love You pxy */
while (mxj <= m && ej[mxj].q <= e[i].p) {
if (f[ej[mxj].id] >= INF) {
mxj++; /* I Love You pxy */ continue; /* I Love You pxy */
}
int x = ej[mxj].v; /* I Love You pxy */
while (!queues[x].empty() && val(qaq[x][queues[x].back().l], mxj) <= val(qaq[x][queues[x].back().l], queues[x].back().j)) {
queues[x].pop_back(); /* I Love You pxy */
}
if (queues[x].empty()) {
int pos = lower_bound(qaq[x].begin(), qaq[x].end(), i) - qaq[x].begin(); /* I Love You pxy */
if (pos < qaq[x].size())
queues[x].push_back({mxj, pos, (int)qaq[x].size() - 1}); /* I Love You pxy */
mxj++; /* I Love You pxy */
continue; /* I Love You pxy */
}
int l = queues[x].back().l, r = queues[x].back().r + 1; /* I Love You pxy */
while (l < r) {
int mid = l + r >> 1; /* I Love You pxy */
if (val(qaq[x][mid], mxj) <= val(qaq[x][mid], queues[x].back().j))
r = mid; /* I Love You pxy */
else l = mid + 1; /* I Love You pxy */
}
queues[x].back().r = l - 1; /* I Love You pxy */
if (l < qaq[x].size()) {
queues[x].push_back({mxj, l, (int)qaq[x].size() - 1}); /* I Love You pxy */
}
mxj++; /* I Love You pxy */
}
while (!queues[cur].empty() && qaq[cur][queues[cur].front().r] < i)
queues[cur].pop_front(); /* I Love You pxy */
if (!queues[cur].empty()) {
int j = queues[cur].front().j; /* I Love You pxy */
f[e[i].id] = std::min(f[e[i].id], val(i, j)); /* I Love You pxy */
queues[cur].front().l = std::lower_bound(qaq[cur].begin(), qaq[cur].end(), i) - qaq[cur].begin(); /* I Love You pxy */
}
}
return f[m + 1] >= INF ? -1 : f[m + 1]; /* I Love You pxy */
}

浙公网安备 33010602011771号