NOIP2011 观光公交 题解(超详细)

描述

风景迷人的小城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位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。

输出格式

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

样例1

样例输入1[复制]

3 3 2
1 4
0 1 3
1 1 2
5 2 3

样例输出1[复制]

10

限制

1s

提示

样例说明:

对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。

分析

每个人在车上的时间 等于 车到达目的站的时间 减去 这个人到达起始站的时间
由于人到达起始站的时间题目给出不会变化
所以求解一个最优的车到达目的站的时间即可

假设到达第i+1站的时间是time[i]
从前往后逐个求解到站时间
可以得出time[i] = max{time[i-1], last[i]} + d[i] 
其中last[i]为从第i站上车的旅客中最后到达的一个到达的时间 可以用O(n+m)的时间预处理得出
d[i]为从i走到i+1所用的时间
很明显的 我们可以利用这个递推关系式用O(n)的时间求解time[i]

当我们令d[i]减少1时
time[1..i-1]不会变化
time[i]会减少1
考虑time[i+1]
由前面的式子得出time[i+1] = max{time[i], last[i+1]} + d[i+1]
若我们令d[i]减少之前 存在time[i] > last[i+1]则time[i+1]会减少1 否则time[i+1]就会不变
若time[i+1]减少了1 我们可以用同样的方法判断time[i+2]直到最后是否变化
若time[i+1]不变 则time[i+2]及之后的time值都不会变化
**所以 当我们令某个d[i]减少1时 从time[i]开始会有一段区间的time值均减少1

这个区间的左端为i 我们令右端为range[i]
对于j属于从i+1到range[i] 均存在time[j-1] > last[j] 且对于range[i]+1不存在time[j-1] > last[j]

说到这里 大家应该发现求解range[i]的方法了
若range[i+1] = t 则对于从i+2到t前不等式均成立且对于t+1不成立
所以我们求解range[i]只需判断对于i+1前不等式是否成立即可
若成立则 range[i] = range[i+1] 不成立则 range[i] = i

若我们修改每个值所减少的时间为reduce[i] 则reduce[i] = v[i] + v[i+1] + ... + v[range[i]]
v[i]表示到达第i+1个车站的人的数量
很明显的 reduce[i] = v[i] + reduce[i+1] 或 v[i]

现在 我们可以用O(n)的时间求解reduce了
然后每次选择一个令reduce[i]最大的i 令d[i]减少即可

注意每次修改d[i]之后 要重新计算新的time[i]

时间复杂度O(kn)




program bus;
  const
  ma
mx=10001;

  maxf=1001;
  var a,b,t:array[0..maxm] of longint;
      right,s,ina,off,get,leave,d:array[0..maxf+1] of longint;
      p,c,n,m,k,i,j,maxk,maxi,temp:longint;
      reducenum:longint;
function max(q
,p:longint):longint;
  begin
   if q>p then exit(q);
   exit(p);
  end;
 function min(q,p:longint):longint;
  begin
   if q>p then exit(p);
   exit(q);
  end;
 begin
 read(n,m,k);
 for i:=1 to n-1 do read(D[i]);
 for i:=1 to m do
   begin
   read(t[i],a[i],b[i]);
   inc(off[b[i]]);
   leave[a[i]]:=max(leave[a[i]],t[i]);
   end;
 for i:=2 to n do
   get[i]:=max(leave[i-1],get[i-1])+D[i-1];
 p:=1;
 for i:=1 to n do
  begin
   while((p<n)and(leave[p]<get[p]))or(p<=i)do
   inc(p);
   right[i]:=p;
   end;
 for i:=1 to n do
   s[i]:=s[i-1]+off[i];
 while k>0 do
   begin
   maxk:=0;
   for i:=1 to n-1 do
     if (s[right[i]]-s[i]>maxk)and(d[i]>0)
       then begin
            maxk:=s[right[i]]-s[i];
            maxi:=i;
            end;
   if maxk=0
     then break
     else begin
         temp:=maxlongint;
         j:=maxi+1;
          while(j<n)and(leave[j]<get[j])do
            begin
            temp:=min(get[j]-leave[j],temp);
            inc(j);
          end;
          temp:=min(D[maxi],temp);
          temp:=min(k,temp);
          dec(k,temp);
          dec(D[maxi],temp);
          for j:=maxi+1 to right[i] do
           get[j]:=max(get[j-1],leave[j-1])+D[j-1];
           p:=maxi;
           c:=off[maxi];
   for j:=maxi to right[i]-1 do
            begin
              while((p<n)and(leave[p]<get[p]))or(p<=j)do inc(p);
              if p>=right[j] then
              break;
              right[j]:=p;
            end;
          end;
   end;
 get[1]:=leave[1];
 get[n]:=max(get[n],leave[n]);
 for i:=1 to m do
   inc(reducenum,get[b[i]]-t[i]);
 writeln(reducenum);
 end.





答案
 

program bus;

 var n,m,k,i,j,tt,tz,dd,da,dz,mn:longint;

     a,b,c,d,mm,num,time,yx:array[0..10000] of longint;

     ans:longint;

 function max(q,p:longint):longint;

  begin

   if q>p then exit(q);

   exit(p);

  end;

 function min(q,p:longint):longint;

  begin

   if q>p then exit(p);

   exit(q);

  end;

 function sum(q,p:longint):longint;

  var i,ans:longint;

  begin

   ans:=0;

   for i:=p+1 to q do

    inc(ans,num[i]);

   exit(ans);

  end;

 begin

  assign(input,'bus.in');reset(input);

  assign(output,'bus.out');rewrite(output);

  readln(n,m,k);

  for i:=1 to n-1 do read(d[i]);                //记录从i站到达i+1站所用时间

  for i:=1 to m do                              //记录每个人旅行的信息

   begin

    readln(a[i],b[i],c[i]);

    if mm[b[i]]<a[i] then mm[b[i]]:=a[i];       //统计最后一个从b[i]站上车的时间

    inc(num[c[i]]);                             //统计从c[i]站下车的人数

   end;

  while true do

   begin

    time[1]:=0;

    for i:=2 to n do                           //递推到达i站的时间

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

    yx[n]:=n;                                  //判断修改ii+1所用时间影响的范围

    for i:=n-1 downto 1 do

     begin

      yx[i]:=yx[i+1];

      if time[i+1]<=mm[i+1] then yx[i]:=i+1;

     end;

    tt:=1;

    while (d[tt]=0)and(tt<=n-1) do

     inc(tt);

    if(tt=n)or(k=0)then break;

    for i:=tt+1 to n-1 do                      //找出影响范围最大的(影响的人的个数)

     if (d[i]<>0)and(sum(yx[tt],tt)<sum(yx[i],i)) then tt:=i;

    if sum(yx[tt],tt)=0 then break;

    dd:=maxlongint;     //减去最小的time[i]-mm[i],使后面不会出现mm[i]>time[i]的情况

    for i:=tt+1 to yx[tt]-1 do

     dd:=min(dd,time[i]-mm[i]);

    dd:=min(dd,k)

    dd:=min(dd,d[tt]);

    k:=k-dd;

    d[tt]:=d[tt]-dd;

   end;

  for i:=1 to m do

   inc(ans,time[c[i]]-a[i]);                   //统计每个人旅行的时间

  writeln(ans);

  close(input); close(output);

 end.




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值