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 */
}
posted @ 2026-07-28 15:19  J1angHz  阅读(2)  评论(0)    收藏  举报