Luogu 1315 【NOIP2011】观光公交 (贪心)
Description
风景迷人的小城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 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
Input
第1 行是3 个整数n, m,k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数和氮气加速器个数。
第2 行是n-1 个整数,每两个整数之间用一个空格隔开,第i个数表示从第i个景点开往第i+1 个景点所需要的时间,即Di。
第3 行至m+2 行每行3 个整数Ti, Ai,Bi,每两个整数之间用一个空格隔开。第i+2 行表示第i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。
Output
共一行,包含一个整数,表示最小的总旅行时间。
Sample Input
3 3 2
1 4
0 1 3
1 1 2
5 2 3
Sample Output
10
Http
Luogu:https://www.luogu.org/problem/show?pid=1315
Source
贪心
解决思路
首先可以贪心的想到,要让每一个加速器的效应最大,那么要让其作用的人最多,所以我们可以想到是一个取最大值的操作。
再来考虑每一个景点。首先如果当前公交车到达某一个景点后还要等乘客,这时在前面这条路上使用加速器是不实用的,因为同样还要等,没有必要浪费。即我们可以得出一个结论,就是公交车从一个景点出发的理论最晚时间(LastTime)就是这个景点的最后来的那个人的时间。
所以,我们可以先把不使用加速器的总时间算出来,然后再最优地减去最多的时间。
不使用加速器的总时间是多少呢?
\]
\(Ranger[i]\)代表第i个人的终点景点
\(PeopleTime[i]\)代表第i个人到来的时间
\(LeaveTime[i]\)表示从i点出发的实际时间,那么\(LeaveTime[i]=max(LeaveTime[i-1],LastTime[i-1])+Dist[i-1]\)
其中\(Dist[i-1]\)表示第i-1个景点到景点i消耗的时间。
从计算总时间的式子可以看出,\(PeopleTime[i]\)是不会变的,我们能减少的就是\(LeaveTime[]\)这一部分。
也就是说,我们要使得每一个人尽可能地早下车。
然后考虑如何取这个最优。
首先需要知道的是,如果我们对一条路上的时间-1,那么它影响的不仅仅只是这条路,还可能会影响到后面的若干条路。如果\(LeaveTime[i]>LastTime[i]\),也就是说在第i个点上,最晚来的那个人也要等一下,那么如果公交车来早1,这个景点的所有人就都可以早出发1,这也就导致后面的时间都会提早1,向后传递。否则,若在第i个景点,公交车本来就要等最后一个人来,那么此时公交车来早1,同样还是要等到那个时间,后面的时间不会因此改变,所以不会影响后面。
具体在实现上面的计算时,我们用LastInfluence[i]代表第i+1个景点最远能影响到的景点。为什么这里是第i+1呢?因为实际上我们在计算影响时是根据两点之间的边来算的,但后面统计又要用到人,所以把边换成对点的操作。
那么对于从i到\(LastInfluence[i]\)这其中我们要统计什么呢?
结合我们上面所说的让每个人尽早下车,我们统计一下每一个景点下车的人数,那么如果我们把第i条边的时间-1,那么对于景点i到景点\(LastInfluence[i]\)这其中所有下车的人的时间都-1。那么现在的问题就是要找出这些影响区间所覆盖的下车人数最大。简单来说,就是区间最大和。这个最大和,我们可以用前缀和来维护。
注意,每使用一次加速器,要把LeaveTime[i]重新统计,因为使用加速器后会有一条边的时间减少,对应的会影响一段区间的时间,所以要重新计算。
简单地总结一下,此题的步骤就是
1.求出不使用加速器时的总时间
2.每一次找出一个影响区间覆盖的下车人数最多的区间,减去对应的答案,并重新统计实际出发时间,以此进行下次求值。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxN=2000;
const int maxM=20000;
const int inf=2147483647;
int n,m,K;
int Dist[maxN];
int EndSum[maxN];//EndSum[i]表示在i号位置下车的人的数量,输入完后,再用来计算前缀和
int Rangel[maxM];//人出发的景点
int Ranger[maxM];//人到达的景点
int PeopleTime[maxM];//人到达景点的时间
int LastTime[maxN];//最后到达的人的时间
int LeaveTime[maxN];//离开的时间
int LastInfluence[maxN];//影响区间
void init();//计算LeaveTime数组
int main()
{
memset(LastTime,0,sizeof(LastTime));
memset(EndSum,0,sizeof(EndSum));
scanf("%d%d%d",&n,&m,&K);
for (int i=1;i<n;i++)
scanf("%d",&Dist[i]);
Dist[n]=0;
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&PeopleTime[i],&Rangel[i],&Ranger[i]);
EndSum[Ranger[i]]++;
LastTime[Rangel[i]]=max(LastTime[Rangel[i]],PeopleTime[i]);
}
int Ans=0;
for (int i=1;i<=n;i++)//计算前缀和
EndSum[i]+=EndSum[i-1];
init();
for (int i=1;i<=m;i++)//统计不使用加速器时的总时间
Ans+=LeaveTime[Ranger[i]]-PeopleTime[i];
while (K--)
{
LastInfluence[n]=LastInfluence[n-1]=n;//倒着推出每一个景点向后最远能影响到哪里
for (int i=n-2;i>=1;i--)
LastInfluence[i]=(LeaveTime[i+1]<=LastTime[i+1])?i+1:LastInfluence[i+1];//感谢@泪眼问花花不语 找到一个错误
int nowmax=0,id;//求出最大区间和
for (int i=1;i<n;i++)
if ((EndSum[LastInfluence[i]]-EndSum[i]>nowmax)&&(Dist[i]!=0))
{
nowmax=EndSum[LastInfluence[i]]-EndSum[i];
id=i;
}
Dist[id]--;//减去使用加速器造成的效果
Ans-=nowmax;
init();//重新计算LeaveTime
}
printf("%d\n",Ans);
return 0;
}
void init()
{
LeaveTime[1]=LastTime[1];
for (int i=2;i<=n;i++)
LeaveTime[i]=max(LastTime[i-1],LeaveTime[i-1])+Dist[i-1];
return;
}