GDKOI2014Day2T2

题目大意:电脑上n个程序,第i个程序占着a[i]个单位内存,还需要b[i]个单位的空闲内存才能结束,一共有m分钟,每一分钟一个程序的a,b都会改变,问每分钟结束所有程序需要的最小空闲内存(1<=n,m<=100000)

首先当然会想到贪心算法,那就是每次都运行b最小的程序,这样所需要的空闲内存一定是最小的。然而,暴力算法条件下,把所有程序按b升序排序后,每改变一个元素并查询的时间复杂度为O(n),共操作m次,最坏情况下要运算100000*100000次,那么明显就会TLE。于是我们果断想到用可以把单次查询时间复杂度压缩到O(log n)的算法。

稍作推理可发现题目有以下几个性质:

1.      对于任意两个排好序的程序i,j,都可以得到:

b=b[i]+max(b[j]-a[i]-b[i],0)

证明如下:

首先,为了把i程序结束,我们会需要b[i]个单位的空闲内存,然后i程序结束,其占有的a[i]+b[i]个内存全部释放,释放后会有两种情况,一是b[j]<=a[i]+b[i],那么我们可以直接结束程序j而不需要其它的内存,但是如果b[j]>a[i]+b[i],那么我们就要b[j]-a[i]-b[i]个单位的空闲内存,那么:b=b[i]                (b[j]<=a[i]+b[i])

        b[i]+b[j]-a[i]-b[i]     (b[j]>a[i]+b[i])

更进一步b=b[i]+max(b[j]-a[i]-b[i],0)

2.      如果b[i]=b[j],那么可以合并得到一个新程序p,其中a[p]=a[i]+a[j],b[p]=b[i]=b[j],否则可以得到a[p]=a[i]+a[j],b[p]= b[i]+max(b[j]-a[i]-b[i],0)

当然这里还有一个可以改进插入时间的东西,那就是先把所有程序读进来以后离线操作

据此,我们可以获得这么一个算法

1.      先读入所有程序(包括后m个要改变的)

2.      按b值排序

3.      建立一棵线段树,对于第k大的b,其对应存储节点为[k,k],然后依据性质2,把所有b为第k大的程序信息存入[k,k]中,然后再依据性质1得到其他所有节点的a和b(当然,我们只对编号为1..n的程序进行存储,后m个先不存进去)

4.      每次修改时,把源程序删除,然后按照新程序的b值找到其在线段树中位置,再加进线段树中,随后得到的根节点的b值即为答案

type tpoint=^tppoint;
     tppoint=record
             l,r,m:longint;
             a,b:int64;
             lc,rc:tpoint;
             end;
     num=record p,a,b:int64;end;
var head:tpoint;
    s,data:array[0..200000]of num;
    pnum,qnum,a,b:array[0..200000]of int64;
    n,m,i,j,t:longint;
    k,l:int64;
function max(a,b:int64):int64;
begin
  if a>b then exit(a) else exit(b);
end;
procedure sort(l,r:longint);//把程序按b值升序排序
var i,j,x,y:longint;m:num;
begin
  i:=l;
  j:=r;
  m:=s[(l+r) div 2];
  repeat
    while s[i].b<m.b do inc(i);
    while s[j].b>m.b do dec(j);
    if i<=j then
    begin
      s[0]:=s[i];
      s[i]:=s[j];
      s[j]:=s[0];
      inc(i);
      dec(j);
    end;
  until i>j;
  if i<r then sort(i,r);
  if l<j then sort(l,j);
end;
procedure build(var p:tpoint;l,r:longint);//建树
begin
  new(p);
  p^.l:=l;
  p^.r:=r;
  p^.m:=(l+r)div 2;
  if l=r then
  begin
    p^.a:=data[l].a;
    p^.b:=data[l].b;
    p^.lc:=nil;
    p^.rc:=nil;
    exit;
  end;
  build(p^.lc,l,p^.m);
  build(p^.rc,p^.m+1,r);
  p^.a:=p^.lc^.a+p^.rc^.a;//性质2
  p^.b:=p^.lc^.b+max(p^.rc^.b-p^.lc^.a-p^.lc^.b,0);//性质1
end;
procedure update(var p:tpoint;a,b,typ:longint);//节点修改
begin
  if p=nil then exit;
  if p^.l=p^.r then//已找到,添加新程序
  begin
    p^.a:=p^.a+a;//性质2
    pnum[p^.l]:=pnum[p^.l]+typ;
    if pnum[p^.l]=0 then p^.b:=0
    else p^.b:=b;//注意:除去程序后要小心b值第k大的程序可能会没有了
    exit;
  end;
  if b<=s[p^.m].b then update(p^.lc,a,b,typ)//二分查找找到对应位置
  else update(p^.rc,a,b,typ);
  p^.a:=p^.lc^.a+p^.rc^.a;
  p^.b:=p^.lc^.b+max(p^.rc^.b-p^.lc^.a-p^.lc^.b,0);
end;
begin
  readln(n,m);
  for i:=1 to n do
  begin
    readln(a[i],b[i]);
    s[i].p:=i;
    s[i].a:=a[i];
    s[i].b:=b[i];
  end;
  for i:=1 to m do
  begin
    readln(qnum[i],a[i+n],b[i+n]);
    s[i+n].p:=i+n;
    s[i+n].a:=a[i+n];
    s[i+n].b:=b[i+n];
  end;
  sort(1,n+m);
  for i:=1 to n+m do//初始化,把编号为1..n的程序存进data中
  if (i=1)or(s[i].b<>s[i-1].b) then
  begin
    inc(t);
    s[t]:=s[i];
    if s[i].p<=n then
    begin
      data[t]:=s[t];
      pnum[t]:=1;
    end
    else data[t].p:=s[i].p;
  end
  else
  begin
    inc(s[t].a,s[i].a);
    if s[i].p<=n then
    begin
      if data[t].b=0 then data[t].b:=s[i].b;
      inc(data[t].a,s[i].a);
      inc(pnum[t]);
    end;
  end;
  build(head,1,t);
  for i:=1 to m do
  begin
    k:=b[qnum[i]];
    l:=-a[qnum[i]];
    update(head,l,k,-1);//删除程序
    b[qnum[i]]:=b[i+n];
    k:=b[i+n];
    a[qnum[i]]:=a[i+n];
    l:=a[i+n];
    update(head,l,k,1);//添加程序
    writeln(head^.b);
  end;
end.


代码片如下:

https://code.youkuaiyun.com/snippets/1557062

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值