AcWing 303 运输小猫

题目描述:

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

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

第 i 座山与第 i-1 座山之间的距离为 Di。

饲养员都住在 1 号山。

有一天,猫出去玩。

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

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

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

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

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

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

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

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

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

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

输入格式

第一行包含三个整数N,M,P。

第二行包含n-1个整数,D2,D3,…,DN。

接下来M行,每行包含两个整数 Hi和 Ti。

输出格式

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

数据范围

2≤N≤10^5 ,
1≤M≤10^5,
1≤P≤100,
1≤Di<1000,
1≤Hi≤N,
0≤Ti≤10^9

输入样例:

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

输出样例:

3

分析:

题意比较复杂,首先要明白的是一个饲养员某时刻出发,能够接走哪些猫,或者说,要满足什么条件的猫才能够被改饲养员接走,显然只需要到达某只猫所在山的时候猫已经玩完了就可以接走猫了。设饲养员出发的时间是st,某只猫在第i座山上玩,并且要玩ti时间,该饲养员能够接走该猫只需要出发时间加上赶路时间大于等于猫玩耍的结束时间即可,即st + di >= ti时可以接走猫,di和ti都是固定的,而饲养员的出发时间是可以变化的,故st时刻出发,可以接走剩下猫中的ti - di <=st的猫。因此需要按照所有猫的玩耍结束时间减去山的距离自小到大排序,方便确定饲养员能够接走哪些猫。

状态表示:f[i][j]表示前i个饲养员接走排好序的前j只猫的最小等待时间。状态转移方程为f[i][j] = min(f[i-1][k] + aj*(j-k)-(sj-sk)),其中aj是第j只猫的玩完时间减去山的距离,也就是第i个饲养员出发的时间,也就是说前i-1个饲养员接走了前k只猫,k + 1到j只猫由第i个饲养员接走,每只猫的等待时间分别是aj - ak+1,aj - ak+2,...,aj - aj,求和得到aj*(j-k) - (ak+1 + ak+2 + ...+aj) = aj*(j-k)-(sj-sk),其中sj表示a1到aj的前缀和。k的取值可以从0到j-1,当然k取到j也是没问题的,表示第i个人一只猫都没接到,但是显然多一个人去接口的可以减少猫的等待时间,所以f[i-1][j]不可能是f[i][j]的最优解,或者说最多与最优解相等,因此也就不用考虑k = j的情况了。设k是使得f[i][j]取得最小值的那个k,则f[i][j] = f[i-1][k] + aj*(j-k)-(sj-sk),求f[i][j]时i和j相关的变量都是已知的,变形得到f[i-1][k] + sk = aj * k + f[i][j] - a[j]*j + sj,其中aj,j,sj都是已知的,将f[i-1][k] + sk看作y,k看作x,就得到了一个一次函数,要使f[i][j]最小即使得截距最小,就可以使用斜率优化了,具体斜率优化的推导可以参考AcWing 301 任务安排2,写习惯后就可以直接分析这个式子的性质,随着j的增加,新加入点集中的点(k,f[i-1][k] + s[k])也是递增的,并且斜率aj也是递增的,因为之前已经对a数组排序了。所以后面的就可以完全仿照任务安排那题了,维护一个斜率自队头向队尾递增的单调队列。

遍历到j时,查询使f[i][j]取得最小值的k,查询到比当前斜率aj小的斜率都出队,查询到第一个大于sj的斜率就找到了k。

出队尾时,也是要保证新加入队尾的斜率要严格的大于之前队尾的斜率,否则就要出队尾。

至于如何求队头队尾的斜率,就相当简单了,(k,f[i-1][k] + s[k])是点坐标的形式,队头的斜率等于k取q[hh]以及q[hh+1]这两点构成的斜率,队尾的斜率等于k取q[tt]和q[tt-1]这两点构成的斜率,(斜率表达式较长,这里就不写了,具体表达式见代码即可)。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100005;
typedef long long ll;
int n,m,p,q[N];
ll d[N],t[N],a[N],s[N],f[105][N];
int main(){
    scanf("%d%d%d",&n,&m,&p);
    for(int i = 2;i <= n;i++){
        scanf("%lld",&d[i]);
        d[i] += d[i-1];
    }
    for(int i = 1;i <= m;i++){
        int h;
        scanf("%d%lld",&h,&t[i]);
        a[i] = t[i] - d[h];
    }
    sort(a + 1,a + m + 1);
    for(int i = 1;i <= m;i++)   s[i] = s[i-1] + a[i];
    memset(f,0x3f,sizeof f);
    for(int i = 0;i <= p;i++)   f[i][0] = 0;
    for(int i = 1;i <= p;i++){
        int hh = 0,tt = 0;
        for(int j = 1;j <= m;j++){
            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]]);
            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;
        }
    }
    printf("%lld\n",f[p][m]);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值