包裹快递

GitHub跳转原题关系图返回列表

二分最大速度,给定速度后顺着维护每个地点可行签收时间区间的下界,线性判断是否能按时送完。

OJ: luogu

题目 ID: P1542

难度:普及+/提高

标签:二分贪心数学模拟

日期: 2026-06-20 13:31

题意

n 个地点需要按顺序依次送达。

i 个地点给出:

  • 可签收时间区间 [x_i, y_i]
  • 从前一个地点到这里的距离 s_i

小 K 可以在某个地点提前到达并等待, 但不能晚于该地点的签收上界。

要求最小化整条路线允许使用的最大速度。

思路

先看一个可以直接验证想法的朴素解:

cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 105;

int n;
double l[MAXN], r[MAXN], s[MAXN];

bool check(double speed) {
    double low = l[1];
    double high = r[1];

    for (int i = 2; i <= n; i++) {
        double need = s[i] / speed;
        low = max(l[i], low + need);
        high = r[i];
        if (low > high) return false;
    }

    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i] >> s[i];
    }

    // 小数据暴力版仍然直接二分答案。
    // 这题核心是可行性判定,不存在更自然的离散暴力。
    double left = 0.0;
    double right = 1e8;
    for (int i = 0; i < 100; i++) {
        double mid = (left + right) / 2.0;
        if (check(mid)) right = mid;
        else left = mid;
    }

    cout << fixed << setprecision(2) << right << '\n';
    return 0;
}

这题答案有很明显的单调性:

  • 如果某个速度可行,更大的速度也一定可行;
  • 如果某个速度不可行,更小的速度也一定不可行。

所以整体框架是二分答案。

关键在于如何判断给定速度 v 是否可行。

设当前已经处理到第 i 个地点, 并且在这个地点能够合法完成签收的时间范围是 [low, high]

到下一个地点至少要花:

  • s_{i+1} / v

所以最早只能在:

  • low + s_{i+1} / v

时到达第 i+1 个地点。

如果这个时刻早于 x_{i+1},可以等到 x_{i+1}; 如果它晚于 y_{i+1},就说明这个速度不够快。

因此新的最早可行时间是:

  • max(x_{i+1}, low + s_{i+1}/v)

只要这个值不超过 y_{i+1},就还能继续。

于是 check(v) 只需要从前往后线性扫描一遍地点, 不断更新这个“当前最早可行签收时间”即可。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 200000 + 5;

int n;
double l[MAXN], r[MAXN], s[MAXN];

// 判断最大速度 speed 是否可行。
bool check(double speed) {
    double low = l[1];
    double high = r[1];

    for (int i = 2; i <= n; i++) {
        double need = s[i] / speed;

        // 从上一站可行到达区间 [low, high] 出发,
        // 加上这一段行驶时间 need 后,到达当前站的可行时间区间是:
        // [low + need, +inf)
        //
        // 由于上一站可以等待到任意不超过 high 的时刻再出发,
        // 所以只要当前站签收区间与 [low + need, +inf) 有交集即可。
        low = max(l[i], low + need);
        high = r[i];

        if (low > high) return false;
    }

    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i] >> s[i];
    }

    double left = 0.0;
    double right = 1e8;

    for (int i = 0; i < 100; i++) {
        double mid = (left + right) / 2.0;
        if (check(mid)) {
            right = mid;
        } else {
            left = mid;
        }
    }

    cout << fixed << setprecision(2) << right << '\n';
    return 0;
}

复杂度

每次判定 O(n)O(n), 二分固定迭代若干次, 总复杂度可视为 O(n)O(n)

总结

这题本质上不是路径规划,而是“速度够不够快”的可行性判定。

一旦把固定速度后的问题转成时间区间递推, 整题就是标准的“二分答案 + 线性 check”。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析