3.观光公交
(bus.cpp/c/pas)
风景迷人的小城 Y 市,拥有 n 个美丽的景点。由于慕名而来的游客越来越多,Y 市特 意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第 0 分钟出现在 1 号景点,随后依次前往 2、3、4……n 号景点。从第 i 号景点开到第 i+1 号景点需要 Di 分钟。 任意时刻,公交车只能往前开,或在景点处等待。
设共有 m 个游客,每位游客需要乘车 1 次从一个景点到达另一个景点,第 i 位游客在
Ti 分钟来到景点 Ai,希望乘车前往景点 Bi(Ai<Bi)。为了使所有乘客都能顺利到达目的地,
公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。
假设乘客上下车不需要时间。 一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一
辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司 机 ZZ 给公交车安装了 k 个氮气加速器,每使用一个加速器,可以使其中一个 Di 减 1。对于 同一个 Di 可以重复使用加速器,但是必须保证使用后 Di 大于等于 0。
那么 ZZ 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
【输入】
输入文件名为 bus.in。
第 1 行是 3 个整数 n, m, k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数
和氮气加速器个数。
第 2 行是 n-1 个整数,每两个整数之间用一个空格隔开,第 i 个数表示从第 i 个景点开 往第 i+1 个景点所需要的时间,即 Di。
第 3 行至 m+2 行每行 3 个整数 Ti, Ai, Bi,每两个整数之间用一个空格隔开。第 i+2 行表 示第 i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。
【输出】
输出文件名为 bus.out。共一行,包含一个整数,表示最小的总旅行时间。
【输入输出样例】
bus.in bus.out
3 3 2
1 4
0 1 3
1 1 2
5 2 3 10
【输入输出样例说明】
对 D2 使用 2 个加速器,从 2 号景点到 3 号景点时间变为 2 分钟。
公交车在第 1 分钟从 1 号景点出发,第 2 分钟到达 2 号景点,第 5 分钟从 2 号景点出发, 第 7 分钟到达 3 号景点。
第 1 个旅客旅行时间 7-0 = 7 分钟。 第 2 个旅客旅行时间 2-1 = 1 分钟。 第 3 个旅客旅行时间 7-5 = 2 分钟。 总时间 7+1+2 = 10 分钟。
【数据范围】
对于 10%的数据,k=0; 对于 20%的数据,k=1;
对于 40%的数据,2 ≤ n ≤ 50,1 ≤ m ≤ 1,000,0 ≤ k ≤ 20,0 ≤ Di ≤ 10,0 ≤ Ti ≤ 500;
对于 60%的数据,1 ≤ n ≤ 100,1 ≤ m ≤ 1,000,0 ≤ k ≤ 100,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 10,000;
对于 100% 的数据,1 ≤ n ≤ 1,000 ,1 ≤ m ≤ 10,000 ,0 ≤ k ≤ 100,000 ,0 ≤ Di ≤ 100 ,
0 ≤ Ti ≤ 100,000。
【思路】
贪心。
考虑没有加速器的情况,可以由题目条件求出观光车到达每个车站的时间time,递归求解如下:
time[i]=max{ time[i-1] , t[i-1] }+d[i-1]
再考虑有加速器的情况,注意使用一个加速器相当于对于一部分人的时间-1。对于边i使用加速器,一定可以使在i+1站下车的人数时间-1,如果time[i+1]>=t[i+1]则观光车需要等待i+1站的人上车后才能走所以加速器的“加速效果”停止于i+1;否则,加速器的效果可以延续。如果设g[i]表示对i边加速所能延续到的最大车站则有以下递推式:
g[i]=time[i+1]>=t[i+1]? i+1 : g[i+1]
令sum(j)表示到j及以前下车的人数。每次贪心选择sum(g[i])-sum(i)最大及加速效果最优的边进行加速,直到用尽所有的加速器或不能加速。
【代码】
#include<iostream>
#include<cstring>
using namespace std; const int maxn = +;
struct Node{
int T,from,to;
}a[maxn]; int n,m,K;
int times[maxn],t[maxn],g[maxn],d[maxn];
int sum[maxn]; int main() {
ios::sync_with_stdio(false); cin>>n>>m>>K;
for(int i=;i<n;i++) cin>>d[i];
for(int i=;i<=m;i++) {
cin>>a[i].T>>a[i].from>>a[i].to;
t[a[i].from]=max(t[a[i].from],a[i].T); //t[i]是从 i 出发的最大时间
sum[a[i].to] ++;
}
for(int i=;i<=n;i++) //到达 i 及 i 之前的人数
sum[i] += sum[i-]; int ans=;
times[]=t[];
for(int i=;i<=n;i++)
times[i]=max(times[i-],t[i-])+d[i-];
for(int i=;i<=m;i++) { //m //统计没有加速器的 ans
ans += times[a[i].to]-a[i].T;
}
while(K--) {
g[n] = g[n-] = n;
for(int i=n-;i;i--) //维护 g
g[i]=(times[i+]<=t[i+])? i+ : g[i+]; int _max=,k;
for(int i=;i<n;i++) { //找到使用加速器最有效的边
if(sum[g[i]]-sum[i]>_max && d[i]>) { //d[i]>0
_max=sum[g[i]]-sum[i];
k=i;
}
} if(!_max) break; ans -= _max; //使用加速器缩短时间
d[k] --; times[]=t[];
for(int i=;i<=n;i++) //维护 time
times[i]=max(times[i-],t[i-])+d[i-];
} cout<<ans;
return ;
}