AcWing 303. 运输小猫

AcWing 303. 运输小猫

一、题目描述

S 是农场主,他养了 M 只猫,雇了 P 位饲养员。

农场中有一条笔直的路,路边有 N 座山,从 1N 编号。

i 座山与第 i1 座山之间的距离为 Di

饲养员都住在 1 号山。

有一天,猫出去玩。

i 只猫去 Hi 号山玩,玩到时刻 Ti 停止,然后在原地等饲养员来接。

饲养员们必须回收所有的猫。

每个饲养员沿着路从 1 号山走到 N 号山,把各座山上已经在等待的猫全部接走。

饲养员在路上行走需要时间,速度为 1 米/单位时间。

饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。

例如有两座相距为 1 的山,一只猫在 2 号山玩,玩到时刻 3 开始等待。

如果饲养员从 1 号山在时刻 23 出发,那么他可以接到猫,猫的等待时间为 01

而如果他于时刻 1 出发,那么他将于时刻 2 经过 2 号山,不能接到当时仍在玩的猫。

你的任务是规划每个饲养员从 1 号山出发的时间,使得所有猫等待时间的总和尽量小。

饲养员出发的时间可以为负。

输入格式
第一行包含三个整数 NMP

第二行包含 n1 个整数,D2,D3,,DN

接下来 M行,每行包含两个整数 HiTi

输出格式
输出一个整数,表示所有猫等待时间的总和的最小值。

数据范围
2N105,1M105,1P100,1Di<1000,1HiN,0Ti109

输入样例

4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12

输出样例

3

二、分析

题意比较复杂,首先要明白的是 一个饲养员 某时刻 出发,能够 接走哪些猫,或者说,要满足什么条件的 才能够被 该饲养员 接走,显然只需要 到达 某只猫所在山的时候,猫已经 玩完 了就可以接走猫了。

下面来思考 某个饲养员+某只猫常规 情况:

设饲养员出发的时间是st某只猫 在第hi座山上玩,并且,要玩到ti时间,该饲养员能够接走该猫需要 出发时间 + 赶路时间 >= 此猫玩耍的结束时间

st+sdhi>=ti

sd:d的前缀和,描述每座山距离一号山的距离,此题中ti不用做前缀和
ti<=st+sdhi,表示ti小,从时间方向思考,就是先到这个时间点,也就是玩完时间先到达,然后小猫开始等待人来接它,后面的是饲养员的到达时间。

此时可以接走这只猫,当讨论到具体某只猫时,sdhiti都是 固定 的,而 饲养员出发时间 st 是可以变化的st时刻出发,可以接走 剩下猫 中的满足下面式子ti<=st+sdhi所有小猫

令:ai=tisdhi,问题变成st时刻出发,此饲养员可以接走ai<=st所有小猫

重点: 将 ai 自小到大 排序,方便确定饲养员在st时刻出发,能够接走 哪些小猫

注:
极限思考法:我们认为饲养员可以瞬移,不管距离有多远,都可以0秒到达。

为了满足这个要求,那么需要把与每只小猫相关的时间,都 划归 到每只小猫的 游玩 时间上。

举栗子
假设1号小猫,原游玩时长为10小时,距离1号山8个长度,那么饲养员在2点出发,可以在到达时 直接 接走1号小猫,可以视为小猫1的游玩时间是108=2,这个2,我把它重新定义为 游玩时间

这个 游玩时间 在概念上就有了变化,在几号山就不再重要(因为消化到转换后的 游玩时间 里了),按新产生的 游玩时间 由小到大进行排序,就是它们被接走的 顺序

继续思考,如果只有1名饲养员,那他必须在 游玩时间最长 小猫的结束后,通过 瞬移大法 完成接猫工作,这样才能保证前面的小猫也都玩完了,可以一并接走,这样的话,需要晚点出发,除最后一只小猫外,前面的小猫多等一会也是没办法的事,否则无法接走所有小猫。

如果有2个饲养员呢?情况就会好一些:12点出发,接走按 游玩时间 由小到大排序后的1,2,3小猫,2号饲养员3点出发,接走4,5,6小猫,这样会使小猫的整体等待时间变少。

闫式DP分析法

状态表示

集合
f[i][j]:前i饲养员 接走 排好序 的前j只猫的 所有方案

:此处排好序是指在 瞬移大法 的前提下,重新计算的 游玩时间 由小到大排序。

属性
所有方案的 最小等待时间和

转移方程

f[i][j]=min(f[i1][k]+aj(jk)(sjsk)) k[0,j)

注:

  • i1个饲养员 已经 接走了 k只小猫>k+1j只小猫由 i个饲养员 接走

  • aj:第j只猫的 玩完时间 减去 山的距离,也就是 i个饲养员出发的时间
    此阶段,需要接走k+1j只小猫,那么第i位出发的饲养员,出发时间st=aj
    为啥呢?因为此时接到j个小猫时,等待时间为零,如果提前出发,则到达时无法完成接j小猫的任务;如果延误出发,则会让j号小猫多等,不划算。所以,只能是在aj这个时间出发,最合理。(贪心)

  • 此区间[k+1j],每只猫的 等待时间 分别是ajak+1,ajak+2,...,ajaj \aj×(jk)(ak+1+ak+2+...+aj)  aj×(jk)(sjsk)

    sj=u=1ja[u],sk=u=1ka[u],sjsk=u=k+1ja[u]

k是使得fi,j取得最小值的那个k最佳转移点】,则fi,j=fi1,k+aj×(jk)(sjsk)

变形原则

  • 当前选择的转移点k是变化量,将原始的k相关的式子视为 自变量(x) 式子
  • k变化后影响结果的式子 视为 因变量(y) 式子
  • 自变量前面的系数,如果是常数的话,那么视为 斜率

如此处理后,变形:

fi1,k+sk=aj×k+fi,j+sjaj×j

于是我们把转移方程写成了 y=kx+b 的形式,其中:

{x=ky=fi1,k+skk=ajb=fi,j+sjaj×jy

由于 x,k 都是非严格单调递增,可以用单调队列做斜率优化,时间复杂度 O(M×P)

① 首先要明确 目标: 找出 min(f[i][j]),也就是在给出i名饲养员,完成接走j只小猫,需要小猫整体的等待时间最少。
k=aj,此时j是枚举到的固定值,即斜率是固定的,不断向下凸壳逼近,试图找到与下凸壳的交点,此时产生的截距b最小
b=fi,j+sjaj×j,当b最小时,因为此时sjaj×j是固定值,也就是fi,j最小

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 100010;
const int P = 110;
int n, m;
int p;
int q[N];

LL d[N], t[N];
LL a[N], s[N];
LL f[P][N];
int h;

int main() {
    cin >> n >> m >> p;

    for (int i = 2; i <= n; i++) cin >> d[i], d[i] += d[i - 1]; // 从2开始
    for (int i = 1; i <= m; i++) cin >> h >> t[i], a[i] = t[i] - d[h], s[i] = s[i - 1] + a[i];
    sort(a + 1, a + m + 1); // 由小到大排序,m只小猫

    // dp初始
    memset(f, 0x3f, sizeof f);
    for (int i = 0; i <= p; i++) f[i][0] = 0; // 出动i个饲养员,接0只小猫,等待0。将DP TABLE第一列修改为0

    for (int i = 1; i <= p; i++) {     // 饲养员
        int hh = 0, tt = 0;            // 哨兵
        for (int j = 1; j <= m; j++) { // 遍历小猫
            // 凸包的斜率单调上升,并且,直线的斜率也是单调上升
            // 准备在下凸壳中找到第一个斜率大于k的直线,小于k的全部剔除
            while (hh < tt && (f[i - 1][q[hh + 1]] + s[q[hh + 1]] - f[i - 1][q[hh]] - s[q[hh]]) <= a[j] * (q[hh + 1] - q[hh]))
                hh++;
            // 推出的公式
            f[i][j] = f[i - 1][q[hh]] + a[j] * (j - q[hh]) - (s[j] - s[q[hh]]);
            // 维护下凸壳 [下面是 k=(y1-y2)/(x1-x)的PK,大的干掉。同时,为了避免除法,采用了十字相乘法变形]
            while (hh < tt && (f[i - 1][j] + s[j] - f[i - 1][q[tt]] - s[q[tt]]) * (q[tt] - q[tt - 1]) <= (f[i - 1][q[tt]] + s[q[tt]] - f[i - 1][q[tt - 1]] - s[q[tt - 1]]) * (j - q[tt]))
                tt--;
            // 加入
            q[++tt] = j;
        }
    }
    cout << f[p][m] << endl;
    return 0;
}

三、总结

要注意相减顺序,在去除队首<=k的点时

 while (hh < tt && (f[i - 1][q[hh + 1]] + s[q[hh + 1]] - f[i - 1][q[hh]] - s[q[hh]]) <= a[j] * (q[hh + 1] - q[hh]))
                hh++;

如果是hh+1>hh 就是<= ,如果是hh>hh+1就是>= 虽然算斜率,两点谁减谁都一样,但是咱们把分母乘过去,负数不等式要变号。队尾斜率计算同理。

posted @   糖豆爸爸  阅读(105)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2020-01-19 如何使用印象的markdown
2017-01-19 最近的学习
2016-01-19 mydumper备份
Live2D
点击右上角即可分享
微信分享提示