[ZJOI2015]幻想乡战略游戏

题目大意

一个 n 个节点的树,点带有权值d,边也带有权值(小于等于 1000 的正整数)。初始时点权为 0
定义dist(u,v) u v两点之间简单路径的边权和。
会有 Q 次操作,形如:
u e,表示 du 加上 e(0|e|1000)
每次操作之后,你都需要找到一个点 x ,最小化

w=i=1nd(i)dist(i,x)

请输出 w

1n105,1Q105
数据保证任何时候 d 都为非负数。


题目分析

这题显然就是求树的带权重心。
首先我们想一下最暴力的方法。一个点x,如果它儿子 y 比它优,那么显然就有2size(y)>total(这里的 size 指的是子树点权和,以当前答案点为整棵树的根, total 指的是所有点 d 之和)。所以我们就从上一个答案点开始一直往满足这个条件的儿子走(肯定最多一个),直到没有这样的儿子为止。走的时候我们要不断地更新size(树根已经更换),还有更新答案。这个方法在随机数据下表现优良(貌似可以过官配数据)。
这个方法没有利用好树的重心(这里以及下文的“重心”指的都是不带权重心)的性质。显然我们要找的带权重心要么为某个点,要么在某个点的一颗固定的子树里。这是可以使用点分治优化的。
fx 表示分治中心 x 的整个分治块所有点的d和; hx 表示对于该分治块,选择点 x 时的w gx 表示分治中心 x 分治树里面的所有儿子的h之和。
先讲讲怎么查询,假设当前处理分治中心 x ,我们需要做的事情是:

  1. 枚举x分治树里面的所有儿子 y ,查看是否存在2fy>total

    • 如果不存在,那么我们要找的点就是 x ,退出即可。否则进入第3步。
    • 设不满足条件的儿子为z,我们递归到 z 那里找即可。但是在找之前我们需要修改一下f,令 x 到分治块z的连边指向点 u ,那么点u分治树中到 z 路径上所有点的f都要加上 fxfz 。找到答案退出时记得将加上的数改回来。
    • 然后我们找到了带权重心 c ,怎么统计答案呢?我们可以从c开始一直向分治树中的父亲节点跳,令 l 为上一个点,然后每个点x会对答案造成的贡献有 gx 以及 dist(x,c)×(fxfl) ,当然还要减去 hl 。具体为什么希望读者自行推导。

      修改时我们只需要从修改点开始向分治树中的父亲节点跳,将沿途的点 f gh h 都加上相应的值即可,这个也由读者习性脑补。

      时间复杂度O(nlog2n+Qlog2n)


      代码实现

      这道题很久以前做的,只是为了做课件挖出来写写题解。
      久远到什么程度呢?那个时候我还是用 Pascal 的。

      const
          Maxn=100005;
          Maxe=(Maxn-1)<<1;
          Maxel=Maxn<<1-1;
          MaxLgel=trunc(ln(Maxel)/ln(2));
      var
          Que,App,Last,First,Size,Invfa,Corefa,Orig,Up,High:array[1..Maxn]of longint;
          Tov,Next,Len,Aim,Prev:array[1..Maxe]of longint;
          Rmq:array[1..Maxel,0..MaxLgel]of longint;
          Euler:array[1..Maxel]of longint;
          f,g,h:array[0..Maxn]of int64;
          Vis:array[1..Maxn]of boolean;
          Root,Tot,Cnt,n,q,All,Order:longint;
      procedure Insert(x,y,z:longint);
      begin
          inc(Tot);
          Tov[Tot]:=y;
          Len[Tot]:=z;
          Next[Tot]:=Last[x];
          Last[x]:=Tot;
      end;
      
      procedure Add(x,y,z:longint);
      begin
          Corefa[y]:=x;
          Orig[y]:=z;
          inc(Cnt);
          Aim[Cnt]:=y;
          Prev[Cnt]:=First[x];
          First[x]:=Cnt;
      end;
      
      function Core(p:longint):longint;
      var
          i,x,y,Head,Tail,Temp,Sum,Key:longint;
      begin
          Head:=0;
          Tail:=1;
          Que[1]:=p;
          Size[p]:=0;
          Invfa[p]:=0;
          repeat
              inc(Head);
              x:=Que[Head];
              i:=Last[x];
              while i<>0 do
              begin
                  y:=Tov[i];
                  if not Vis[y] and (y<>Invfa[x]) then
                  begin
                      inc(Tail);
                      Que[Tail]:=y;
                      Size[y]:=1;
                      Invfa[y]:=x;
                  end;
                  i:=Next[i];
              end;
          until Head=Tail;
          for i:=Tail downto 2 do
          begin
              x:=Que[i];
              inc(Size[Invfa[x]],Size[x]);
          end;
          Key:=Maxlongint;
          Sum:=Size[p];
          for Head:=1 to Tail do
          begin
              x:=Que[Head];
              i:=Last[x];
              Temp:=0;
              while i<>0 do
              begin
                  y:=Tov[i];
                  if not Vis[y] and (y<>Invfa[x]) then
                      if Size[y]>Temp then
                          Temp:=Size[y];
                  i:=Next[i];
              end;
              if Sum-Size[x]>Temp then
                  Temp:=Sum-Size[x];
              if Key>Temp then
              begin
                  Key:=Temp;
                  Core:=x;
              end;
          end;
      end;
      
      function Build(x:longint):longint;
      var
          i,y:longint;
      begin
          y:=Core(x);
          x:=y;
          Vis[x]:=true;
          i:=Last[x];
          while i<>0 do
          begin
              y:=Tov[i];
              if not Vis[y] then
                  Add(x,Build(y),y);
              i:=Next[i];
          end;
          exit(x);
      end;
      
      procedure Dfs(x,La:longint);
      var
          i,y:longint;
      begin
          inc(Order);
          Euler[Order]:=x;
          App[x]:=Order;
          i:=Last[x];
          while i<>0 do
          begin
              y:=Tov[i];
              if y<>La then
              begin
                  High[y]:=High[x]+1;
                  Up[y]:=Up[x]+Len[i];
                  Dfs(y,x);
                  inc(Order);
                  Euler[Order]:=x;
              end;
              i:=Next[i];
          end;
      end;
      
      procedure Rmq_Preparation;
      var
          i,j,Lgel:longint;
      begin
          Lgel:=trunc(ln(Order)/ln(2));
          for i:=1 to Order do
              Rmq[i,0]:=Euler[i];
          for j:=1 to Lgel do
              for i:=1 to Order-1<<j+1 do
                  if High[Rmq[i,j-1]]<High[Rmq[i+1<<(j-1),j-1]] then
                      Rmq[i,j]:=Rmq[i,j-1]
                  else
                      Rmq[i,j]:=Rmq[i+1<<(j-1),j-1];
      end;
      
      procedure Preparation;
      begin
          fillchar(Vis,sizeof(Vis),0);
          Root:=Build(1);
          High[1]:=1;
          Order:=0;
          Dfs(1,0);
          Rmq_Preparation;
      end;
      
      procedure Scanf;
      var
          i,x,y,z:longint;
      begin
          assign(input,'tree.in');
          reset(input);
          readln(n,q);
          for i:=1 to n-1 do
          begin
              readln(x,y,z);
              Insert(x,y,z);
              Insert(y,x,z);
          end;
          Preparation;
      end;
      
      function LCA(x,y:longint):longint;
      var
          Rlg:longint;
      begin
          x:=App[x];
          y:=App[y];
          if x>y then
          begin
              x:=x xor y;
              y:=x xor y;
              x:=x xor y;
          end;
          Rlg:=trunc(ln(y-x+1)/ln(2));
          if High[Rmq[x,Rlg]]<High[Rmq[y-1<<Rlg+1,Rlg]] then
              exit(Rmq[x,Rlg])
          else
              exit(Rmq[y-1<<Rlg+1,Rlg]);
      end;
      
      function Dist(x,y:longint):longint;
      begin
          exit(Up[x]+Up[y]-Up[LCA(x,y)]<<1);
      end;
      
      function Dynamic_Core(p:longint):longint;
      var
          i,y,Lag,Total:longint;
          Flag:boolean;
      begin
          Flag:=true;
          i:=First[p];
          while i<>0 do
          begin
              y:=Aim[i];
              Flag:=Flag and (f[y]<<1<=All);
              i:=Prev[i];
          end;
          if Flag then
              exit(p);
          Lag:=0;
          i:=First[p];
          while i<>0 do
          begin
              y:=Aim[i];
              if f[Lag]<f[y] then
                  Lag:=y;
              i:=Prev[i];
          end;
          Total:=f[p]-f[Lag];
          y:=Orig[Lag];
          while y<>p do
          begin
              inc(f[y],Total);
              y:=Corefa[y];
          end;
          i:=Dynamic_Core(Lag);
          y:=Orig[Lag];
          while y<>p do
          begin
              dec(f[y],Total);
              y:=Corefa[y];
          end;
          exit(i);
      end;
      
      procedure Solve;
      var
          x,y,p,La:longint;
          l:int64;
      begin
          assign(output,'tree.out');
          rewrite(output);
          All:=0;
          while q>0 do
          begin
              readln(x,y);
              inc(All,y);
              l:=0;
              p:=x;
              while p<>0 do
              begin
                  inc(f[p],y);
                  inc(g[p],l);
                  if Corefa[p]<>0 then
                      l:=int64(y)*Dist(x,Corefa[p]);
                  inc(h[p],l);
                  p:=Corefa[p];
              end;
              x:=Dynamic_Core(Root);
              l:=0;
              La:=0;
              p:=x;
              while p<>0 do
              begin
                  inc(l,g[p]);
                  inc(l,int64(f[p]-f[La])*Dist(p,x));
                  if Corefa[p]<>0 then
                      dec(l,h[p]);
                  La:=p;
                  p:=Corefa[p];
              end;
              writeln(l);
              dec(q);
          end;
          close(input);
          close(output);
      end;
      
      begin
          Scanf;
          Solve;
      end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值