1 条题解

  • 0
    @ 2026-10-26 3:39:04

    思路与算法

    设起点到山 hh 的距离为 dhd_h,把猫的到达时刻变换为 Ai=Ti−dHiA_i=T_i-d_{H_i}。在时刻 xx 出发的喂养员可接到它当且仅当 x≥Aix\ge A_i,等待时间为 x−Aix-A_i。允许负出发时刻,不要将负的 AiA_i 改成零。

    将 AA 非降排序,记前缀和 SiS_i。把猫交给最早可接它的喂养员不会增加等待;按出发时刻排列人员后,各组可取为排序后的连续段。每段的最优出发时刻等于段内最大 AA。令 fk(i)f_k(i) 为前 ii 只猫使用至多 kk 人的最小等待,f0(0)=0f_0(0)=0,其它为不可达,每层保留 fk(0)=0f_k(0)=0 以允许人员不用。枚举末组 j+1,…,ij+1,\ldots,i:

    $$f_k(i)=iA_i-S_i+\min_{0\le j<i}\{f_{k-1}(j)+S_j-jA_i\}.$$

    可达前驱生成斜率 −j-j、截距 fk−1(j)+Sjf_{k-1}(j)+S_j 的直线。斜率严格递减,查询点 AiA_i 非降,用队列维护最小值下凸包。对每个 ii 先查询再加入 j=ij=i,保证末组非空。两层滚动数组即可,层数截为 min⁡(P,M)\min(P,M)。

    正确性说明

    路程变换保持可接条件和等待时间。给定各人出发时刻,将每只猫交给最早可接它的人逐猫最优,排序后的分配形成连续区间,空组可删;各非空段最优出发时刻是其最大 AA,所以连续分组包含一个最优解。

    任一合法划分有最后一组,删除它后前缀须取最优,否则能改进;反之最优前缀加末组构成合法方案。因此递推既不遗漏也不低估。直线表达式与递推完全等价。斜率递减时,交点次序使中间线不再有优势区间就可删除;使用精确交叉乘积。查询点非降,若后线当前不劣于队首,以后也不劣,故队首可永久弹出。不可达前驱不入队,凸包始终保留一个最优前驱。归纳每层每个前缀求得最优值,最终输出 fmin⁡(P,M)(M)f_{\min(P,M)}(M)。

    复杂度

    令 K=min⁡(P,M)K=\min(P,M)。路程和前缀和初始化 O(N+M)O(N+M),排序 O(Mlog⁡M)O(M\log M)。每层每条线至多入队出队一次,查询共 O(M)O(M),总时间 O(N+Mlog⁡M+KM)O(N+M\log M+KM)。滚动数组、队列和排序数组 O(M)O(M),路程数组 O(N)O(N),总额外空间 O(N+M)O(N+M);数组初始化已计入时间。

    易错点

    1. 原始到达时刻非负,变换值和出发时刻可以为负。
    2. 先查询后插入,限制 j<ij<i,每层空前缀为零。
    3. 查询点可重复,队首以非严格比较弹出;斜率因下标不同而严格不同。
    4. 前缀、等待和直线值用 long long,交叉乘积可能超64位,先转 __int128 再乘。不可达哨兵不参与运算。
    5. 覆盖单猫、人员多于猫、重复变换值、最大规模与大正负变换值。测试为非官方训练数据。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    struct Line {
        ll m, b;
        ll value(ll x) const {
            return m * x + b;
        }
    };
    bool obsolete(Line a, Line b, Line c) {
        return (__int128)(b.b - a.b) * (b.m - c.m) >= (__int128)(c.b - b.b) * (a.m - b.m);
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n, m, p;
        cin >> n >> m >> p;
        vector<ll> distance(n + 1);
        for (int i = 2; i <= n; ++i) {
            ll d;
            cin >> d;
            distance[i] = distance[i - 1] + d;
        }
        vector<ll> a(m + 1), prefix(m + 1);
        for (int i = 1, h; i <= m; ++i) {
            ll t;
            cin >> h >> t;
            a[i] = t - distance[h];
        }
        sort(a.begin() + 1, a.end());
        for (int i = 1; i <= m; ++i)
            prefix[i] = prefix[i - 1] + a[i];
        p = min(p, m);
        const ll INF = 4000000000000000000LL;
        vector<ll> previous(m + 1, INF), current(m + 1);
        vector<Line> q(m + 1);
        previous[0] = 0;
        for (int layer = 1; layer <= p; ++layer) {
            int head = 0, tail = 1;
            q[0] = {0, 0};
            current[0] = 0;
            for (int i = 1; i <= m; ++i) {
                while (tail - head >= 2 && q[head].value(a[i]) >= q[head + 1].value(a[i]))
                    ++head;
                current[i] = a[i] * i - prefix[i] + q[head].value(a[i]);
                if (previous[i] < INF) {
                    Line line = {-i, previous[i] + prefix[i]};
                    while (tail - head >= 2 && obsolete(q[tail - 2], q[tail - 1], line))
                        --tail;
                    q[tail++] = line;
                }
            }
            previous.swap(current);
        }
        cout << previous[m] << '\n';
    }
    
    • 1

    信息

    ID
    1652
    时间
    1000ms
    内存
    512MiB
    难度
    提高
    标签
    递交数
    1
    已通过
    1
    上传者