【题目描述】
风景迷人的小城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该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
【问题分析】
考场上想到了是贪心算法,肯定把氮气放在人多的时候用,但是考虑到车要等人之类的因素,觉得无法证明其正确性,所以没有采用,那么现在我们来说明一下这个神奇的正确性 ps 显然大法好。。。
首先我们可以得到这样一个性质,即氮气的使用满足最优子结构。无论有几个氮气加速器,第一个的使用都是一样的,所以我们可以对每个加速器进行单独分析,即可采用贪心策略。那么该如何贪心呢?显然,应该讲氮气使用在公交车上面人数较多的时刻上面。那么这个加速器会对多少人造成影响呢?
公交车到一个站之后,可能需要等人,也可能不需要等人,直接开往下一站。那么如果公交车等人了,那么使用氮气加速后也没有效果,所以加速器的作用区间为从使用的那一站开始一直到某一站公交车需要等人为止,这个区间内的所有旅客都可以受益,所以一个氮气加速器的作用应该是这一段区间的和。
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1001;
int arrive[N],leave[N],bus[N],off[N],sum[N];
//arrive[i] 到达i站的时间 leave[i] 离开i站的时间
//off[i] i站下车人数 sum[i] 区间和
int n,m,k,ans;
int readin()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void read()
{
int i,t,a,b;
n=readin(); m=readin(); k=readin();
for(i=1;i<n;i++) bus[i]=readin();
for(i=1;i<=m;i++)
{
t=readin(); a=readin(); b=readin();
off[b]++; leave[a]=max(leave[a],t); ans-=t;
}
return;
}
void work()
{
int i,now,use;
while(k)
{
for(i=1;i<=n;i++)
arrive[i]=max(arrive[i-1],leave[i-1])+bus[i-1];
now=0;
for(i=n;i>1;i--)
{
//倒序是为了方便累加sum的和
if(!bus[i-1])
sum[i-1]=0;
else
{
sum[i-1]=off[i];
if(arrive[i]>leave[i])
sum[i-1]+=sum[i];
}
}
for(i=1;i<n;i++)
if(now<sum[i])
{
now=sum[i];
use=i;
}
if(!now) break;
bus[use]--; k--;
}
for(i=1;i<=n;i++)
arrive[i]=max(arrive[i-1],leave[i-1])+bus[i-1];
for(i=1;i<=n;i++)
ans+=arrive[i]*off[i];
printf("%d",ans);
return;
}
int main()
{
freopen("bus.in","r",stdin);
freopen("bus.out","w",stdout);
read();
work();
return 0;
}