Vijos P1741 观光公交

NOIP 2011 day2

观光公交

问题描述

       风景迷人的小城 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 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

输入

第 1 行是3 个整数n, m, k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数
和氮气加速器个数。
第 2 行是n-1 个整数,每两个整数之间用一个空格隔开,第i 个数表示从第i 个景点开
往第i+1 个景点所需要的时间,即Di。
第 3 行至m+2 行每行3 个整数Ti, Ai, Bi,每两个整数之间用一个空格隔开。第i+2 行表
示第i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。

输出

共一行,包含一个整数,表示最小的总旅行时间。

样例输入输出

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[i]表示当前到达i站的时间,latest[i]表示i站最晚来的乘客到达的时刻,sum[i]表示在第i站之前下车的乘客总数。

       考虑Ans=∑time[b[i]]-t[b[i]] (1<=i<=m) => ∑time[b[i]]-∑t[b[i]]。这个变形是解本题的关键。可以看出,t[i]是不变的,而要Ans最小,则∑time[i]就应该最小咯。

       怎么让time[i]最小呢?

       考虑time[i]的由来,可以想出递推式:

              Time[i]=max(time[i-1],latest[i-1])+d[i-1]

       显然每减小d[i-1],time[i]肯定是会减小的,至于i之后的time[i+1]是否会减小,取决于time[i]和lateset[i]的大小关系。如果time[i]>lateset[i],那么time[i]起作用,即time[i+1]肯定会减少。相反的,如果time[i]<=lateset[i]那么time[i]减小是不会影响到time[i+1]的。

       这里讲一下,不要把time[i]认为是等价于latest[i]的,具体点就是当time[i]<=lastest[i]时,time[i]继续减小是有意义的。很显然,从我们的目的上来看是我们要让∑time[j](1<=j<=m)更少,更通俗的说time[i]的减小可以让到i站的人更早的下车。所以time[i]在任意时刻的减小都是有意义的。

       根据time的递推式,可以发现,如果某一个d[i]减小了,那么time中减小的肯定会是连续的一段(或者仅仅是一个),且从i+1开始。设d[i]减少1后能影响到g[i],就是说如使d[i]-1,那么[i+1..g[i]]区间内的time[i]都会减少。g[i]可以通过递推来求:

       g[i]= i+1 (time[i]<=latest[i])

              g[i+1](time[i]>latest[i])

       我们每次只需让sum[g[i]]-sum[i]最大的d[i]用一个氮气加速器(如果在i这里使用一个加速器,那么sum[g[i]]-sum[i]个人都能提前到达),用完后在维护time[i],g[i]即可。

 

       正确性:显然,哪一个氮气加速器用在哪里并不影响其他氮气加速器的使用,所以满足当前最优就是全局的最优决策。

 

代码:

#include<stdio.h>

#include<stdlib.h>

#define max(a,b) (a>b ? (a):(b))

 

long long i,j,k,n,m,l;

long long time[1100],f[1100],g[1100],sum[1100],d[1100];

long long a[11000],b[11000],t[11000],ans;

 

void init()

{

     long long i,j;

     scanf("%I64d%I64d%I64d",&n,&m,&k);

     for (i=1;i<n;i++)

         scanf("%I64d",&d[i]);

     for (i=1;i<=m;i++)

     {

         scanf("%I64d%I64d%I64d",&t[i],&a[i],&b[i]);

         sum[b[i]]++;

         f[a[i]]=max(f[a[i]],t[i]);

     }

     

     return ;

}

void ini_time()

{

     long long i,j,l;

     

     time[1]=0;

     for (i=1;i<=n;i++)

         time[i]=max(time[i-1],f[i-1])+d[i-1];

     g[n]=n;

     g[n-1]=n;

     for (i=n-2;i>=1;i--)

         if (time[i+1]<=f[i+1])

            g[i]=i+1;

         else g[i]=g[i+1];

     for (i=1;i<=m;i++)

         ans+=time[b[i]]-t[i];

     for (i=1;i<=n;i++)

         sum[i]=sum[i-1]+sum[i];

     return ;

}

void work()

{

     long long i,j;

     long long maxs=0,x=0;

     

     for (i=1;i<n;i++)

         if (maxs<sum[g[i]]-sum[i]&&d[i]>0)

            maxs=sum[g[i]]-sum[i],

            x=i;

     long long l=x,r=g[x];

     d[x]--;

     ans-=maxs;

     if (r>n-1)

        r=n-1;

     for (i=l;i<=r;i++)

         time[i]=max(f[i-1],time[i-1])+d[i-1];

     for (i=r;i>=l;i--)

         if (time[i+1]<=f[i+1])

            g[i]=i+1;

         else

         g[i]=g[i+1];

     

     return ;

}

void out()

{

     printf("%I64d\n",ans);

     return ;

}

int main()

{

    init();

    ini_time();

    for (i=1;i<=k;i++)

        work();

    out();

    return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值