noip2011观光公交贪心详解

这题就是个贪心,但是细节决定成败(给跪了)

每个人到起始站的时间为s[i],到达的目的地为t[i],

arrive[i]表示到第i站的时间,last[i]表示第i站最后一个人到达的时间(读入时O(m)处理)

每个人在车上的时间=到达目的站的时间-这个人到达起始站的时间

即n个人的总时间ans:=sigema(arrive[t[i]]-s[i])(1<=i<=m)


易知每一站的到站时间只由从前一站离开的时间决定

易得arrive[i] = max{arrive[i-1], last[i-1]} + d[i] (道路的编号与所连的右点相同,即d[i]为从i走到i+1所用的时间O(n)的时间求出arrive[i]

当我们令d[i]减少1时,很显然我们一定会影响到在第i站下车的人即ans-num[i](num[i]表示第i站下车的总人数)

同时很显然我们一定不会影响[1,i-1]站

那么我们会不会影响第i站以后的人呢?

一定是可能的

那么,影响到后面的站的条件是什么呢?

易知影响后面的站的到站时间取决于离开i站的时间,如果离开i站的时间可以更优那么我们就能影响到后面的站

离开时间为max{last[i],arrive[i]},

而last[i]是不变的

所以只有当last[i]<arrive[i]的时候离开时间才可以更优

不然再怎么优化除了对第i站的影响,你的离开时间不变对后面不会造成影响

所以每一条道路的修改都会至少影响一个点(i站)或一个区间,而一个点可以表示为[i,i]

我们用change[i]表示修改第i条道路会影响的人数和r[i]表示它所影响的区间右端点(左端点一定是i)

如果last[i]>arrive[i]那么r[i]=r[i+1],change[i]=num[i]+change[i+1]

否则r[i]=i,change[i]=num[i]

所以我们从后往前O(n)更新求值

然后每次选择一个最大change[i],即影响人数最多的道路,

令d[i]在满足要求的前提下能减少多少就减少多少即可

再在ans中减去最大所用的加速器tt*chang[i]

每次修改后 要重新计算新的arrive[i]、change[i]、r[i]

时间复杂度O(kn)(其实是到不了的)

=w=

接下来是决定你能不能AC的细节(给跪了QAQ)

先来看题目要求:

1、最多用k个加速器

2、保证d[i]>=0

再来看贪心思路的要求:

1、每次挑一个最大change[i],能减多少减多少的要求:

(1)修改后第i条道路仍然可以影响到[i,r[i]]的车站

          即第i站到第r[i]-1的arrive>=last

       并且注意不要把r[i]也算进去,因为反正i只能影响到r[i]后面的影响不到(即arrive[i]<=last[i])

 (2)当我们把k个加速器都用完后我们就完成了加速,退出循环循环输出答案

   (3)所有的道路都变成了0而k没有用完也直接退出

  (4)在找到最大change[i]的时候要保证第i条道路不能为0不然的话就会死循环(因为每次都找到它但是每次都不能修改)

  (5)但是在每次更新change[i]和r[i]的时候我们不需要考虑它的d[i],因为即使d[i]=0但是它左面的点可能也影响到这个站所以更新方式不需要考虑d[i],不然会让左面的站影响人数和范围减少

然后在各种wa红、TLE红以后AC绿变得格外亲切=。=

var
        n,m,k,tt,x,maxn    :longint;
        i,j                :longint;
        ans                :int64;
        d                  :array[0..1010] of longint;
        s,c,t              :array[0..10010] of longint;
        last               :array[0..1010] of longint;
        change,arrive,num,r:array[0..1010] of longint;
function max(a,b:longint):longint;
begin
   if a<b then exit(b) else exit(a);
end;

function min(a,b:longint):longint;
begin
   if a<b then exit(a) else exit(b);
end;

begin
   read(n,m,k);
   for i:=2 to n do read(d[i]);
   for i:=1 to m do
   begin
      read(s[i],c[i],t[i]);
      if (s[i]>last[c[i]]) then last[c[i]]:=s[i];
      inc(num[t[i]]);
   end;
   for i:=2 to n do arrive[i]:=d[i]+max(arrive[i-1],last[i-1]);
   for i:=1 to m do inc(ans,arrive[t[i]]-s[i]);
   //
   while true do
   begin
      arrive[1]:=0;
      for i:=2 to n do arrive[i]:=d[i]+max(arrive[i-1],last[i-1]);
      //
      change[n]:=num[n];
      if d[n]>0 then
      begin
         maxn:=num[n];x:=n;
      end else maxn:=-1;

      r[n]:=n;
      for i:=n-1 downto 2 do
      begin
         change[i]:=num[i];
         r[i]:=r[i+1];
         if (arrive[i]>last[i]) then change[i]:=change[i]+change[i+1]
          else if (arrive[i]<=last[i]) then r[i]:=i;
         if (change[i]>maxn) and (d[i]>0) then
         begin
            maxn:=change[i];x:=i;
         end;
      end;
      //
      if maxn<0 then break;
      tt:=maxlongint;
      for i:=x to r[x]-1 do tt:=min(tt,arrive[i]-last[i]);
      tt:=min(tt,k);
      tt:=min(tt,d[x]);
      //
      dec(d[x],tt);
      dec(k,tt);
      dec(ans,tt*maxn);
      if (k=0) then break;
   end;
   writeln(ans);
end.
——by Eirlys

转载请出名出处=w=

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值