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  阅读(15)  评论(0)    收藏  举报