bzoj 3611 【heoi2014】大工程 虚树+树形DP

2 篇文章 0 订阅
1 篇文章 0 订阅

题意:给定一棵n个节点的树,q组询问,每次询问找m个关键点,求m个关键点两两之间距离的和、距离的最大值和最小值

n个点的树,对k个关键进行操作(询问)的一眼过去一般都是虚树题

因为原树边权是1,所以虚树上两点之间的边权为abs(d[x]-d[y])(d[i]表示节点i在原树的深度)

(具体虚树怎么搞请自行百度...)

以下的“树”皆表示虚树,树P还真是有点恶心...

我们维护四个数组,分别是 :

size[x]  表示以x为根的子树中关键点的个数(含x本身)

sum[x] 表示以x为根的子树中所有关键点到x的距离和

maxn[x]、minx[x] 表示以x为根的子树中,到x的距离最大(小)值

同时用三个全局变量 ans、ans_min、ans_max 表示该询问的最终答案(分别表示距离和、最小值和最大值)

当我们对于节点x,每新遍历完它的一个子树p,设连接x与p的边权为len,

考虑对最终答案的更新:

ans += sum[x]*size[p] +  len*size[x]*size[p] + sum[p]*size[x]

ans_min = min{ans_min,minn[p]+len+minn[x]};

ans_max = max{ans_max,maxn[p]+len+maxn[x]}

解释:对于对最值的更新还是很好理解的,即p子树内的关键点到x的最值+之前已经更新过的x的其他子树内的关键点到x的最值(因为实际上是关键点走到关键点),由于已经更新过的子树的最值已经转移给了x(具体转移放到下面讲),即minn[x](maxn[x]),便有了上面的更新式子;

那么对于和的更新个人是这样理解并计算的:

我们将它看做三部分:

绿色框内的部分表示我们已经更新过的部分(当然可能已经更新过的部分为空),这部分子树的各个值已经转移给了x(具体转移下面讲),即我们只考虑sum[x]、minn[x]和maxn[x];

蓝色的边表示连接x和p的那条边,边权为len;

第三部分就是我们刚刚更新完的子树p

真正对ans有新的贡献的就是绿色部分内的关键点通过根x和蓝色的边走到子树p内的关键点所经过的路径和。这条路线我们把它拆成三部分,即绿色部分内的关键点走到x、从x走到p、从p走到以p为根的子树内的所有关键点

考虑各个部分对ans的贡献:

绿色部分:绿色部分内的每个关键点都会先从自己走到根x,而每个点都会走到以p为根的子树内的所有关键点,所以绿色部分内的每个关键点会走size[p]次。所以对ans贡献 sum[x]*size[p]

蓝边:蓝边x端有size[x]个关键点要走到p端size[p]个关键点,那么根据乘法原理,易知蓝边的贡献为len*size[x]*size[p]

以p为根的子树内:我们会有size[x]个关键点走到p,并进入以p为根的子树内走到每个关键点,即以p为根的子树对答案的贡献为 size[x]*sum[p]

三部分累加即对ans的贡献和,ans+=三部分贡献和


然后我们考虑子树对x的转移:

sum[x] = sum[x]+sum[p]+size[p]*len  (p子树内的关键点到p的距离和+p子树内的关键点通过蓝边走到x的距离和)

maxn[x] = max{maxn[x],maxn[p]+len} (p子树内关键点到p的最值+len)

minn[x] = minn{minn[x],minn[p]+len}

结合上面的图和我们对各个数组的定义,其实还是很好理解的

注意初始化的时候,由于虚树中我们并不能保证每个点x都是关键点,所以初始化是不同的

对于关键点:初始化 size[x]=1;maxn[x]=0 ;minn[x]=0 (因为它自己本身就是关键点,初始最值当然都是0)

对于非关键点: 初始化 size[x]=0 ;maxn[x]=-inf ;minn[x]=inf (因为它自己不是关键点,所以它子树内关键点到它的最值都是未知的,初始当然是最大值为负无穷、最小值为正无穷)

然后就没有然后了...

{$M 100000000}
{$Q-}
uses math;
var
        n,m,l,q,ll,tl   :longint;
        tot,x,y         :longint;
        ans             :int64;
        ans_min,ans_max :longint;
        i,j             :longint;
        size,maxn,minn  :array[0..1000010] of longint;
        sum             :array[0..1000010] of int64;
        vis,flag        :array[0..1000010] of boolean;
        last,la         :array[0..1000010] of longint;
        pre,other       :array[0..2000010] of longint;
        pr,ot,le        :array[0..2000010] of longint;
        z,d,dfn,que,a   :array[0..1000010] of longint;
        jump            :array[0..1000010,0..20] of longint;

procedure swap(var a,b:longint);
var
        c:longint;
begin
   c:=a; a:=b; b:=c;
end;

procedure connect(x,y:longint);
begin
   inc(l);
   pre[l]:=last[x];
   last[x]:=l;
   other[l]:=y;
end;

procedure connect2(x,y:longint);
begin
   if x=y then exit;
   inc(ll);
   pr[ll]:=la[x];
   la[x]:=ll;
   ot[ll]:=y;
   le[ll]:=d[y]-d[x];
end;

procedure sort(l,r:longint);
var
        i,j,x:longint;
begin
   i:=l; j:=r; x:=dfn[a[(l+r)>>1]];
   while i<=j do
   begin
      while dfn[a[i]]<x do inc(i);
      while dfn[a[j]]>x do dec(j);
      if i<=j then
      begin
         swap(a[i],a[j]);
         inc(i); dec(j);
      end;
   end;
   if i<r then sort(i,r);
   if j>l then sort(l,j);
end;

procedure dfs(x:longint);
var
        p,q:longint;
begin
   inc(tot);
   dfn[x]:=tot;
   q:=last[x];
   while q<>0 do
   begin
      p:=other[q];
      if not vis[p] then
      begin
         vis[p]:=true;
         jump[p,0]:=x;
         d[p]:=d[x]+1;
         dfs(p);
      end;
      q:=pre[q];
   end;
end;

function lca(x,y:longint):longint;
var
        i:longint;
begin
   if d[x]>d[y] then swap(x,y);
   for i:=0 to 20 do
    if ((d[y]-d[x]) and (1<<i) <>0) then y:=jump[y,i];
   if x=y then exit(x);
   for i:=20 downto 0 do
     if jump[x,i]<>jump[y,i] then
     begin
        x:=jump[x,i];
        y:=jump[y,i];
     end;
   exit(jump[x,0]);
end;

procedure virtree;
var
        x,anc,top,p,i:longint;
begin
   top:=1; z[top]:=1;
   for i:=1 to m do
   begin
      x:=a[i]; anc:=lca(z[top],x);
      while (top>0) and (d[z[top]]>d[anc]) do
      begin
         if (d[z[top-1]]<=d[anc]) then
         begin
            p:=z[top];
            dec(top);
            if d[z[top]]<>d[anc] then
            begin
               inc(top);
               z[top]:=anc;
            end;
            connect2(anc,p);
            break;
         end;
         connect2(z[top-1],z[top]);
         dec(top);
      end;
      //
      if z[top]<>x then
      begin
         inc(top);
         z[top]:=x;
      end;
   end;
   for i:=top-1 downto 1 do connect2(z[i],z[i+1]);
end;

procedure dp(x:longint);
var
        p,q:longint;
begin
   inc(tl); que[tl]:=x; sum[x]:=0;
   if flag[x] then
   begin
      size[x]:=1;
      maxn[x]:=0;
      minn[x]:=0;
   end else
   begin
      size[x]:=0;
      maxn[x]:=-maxlongint div 10;
      minn[x]:=maxlongint div 10;
   end;
   q:=la[x];
   while q<>0 do
   begin
      p:=ot[q];
      dp(p);
      inc(ans,sum[x]*int64(size[p]) + int64(le[q])*int64(size[x])*int64(size[p]) + sum[p]*int64(size[x]));
      inc(size[x],size[p]);
      inc(sum[x],int64(le[q])*int64(size[p]) + sum[p]);
      ans_max:=max(ans_max,maxn[p]+le[q]+maxn[x]);
      ans_min:=min(ans_min,minn[p]+le[q]+minn[x]);
      minn[x]:=min(minn[x],minn[p]+le[q]);
      maxn[x]:=max(maxn[x],maxn[p]+le[q]);
      q:=pr[q];
   end;

end;

begin
   read(n);
   for i:=1 to n-1 do
   begin
      read(x,y);
      connect(x,y);
      connect(y,x);
   end;
   vis[1]:=true; d[1]:=1; dfs(1);
   for j:=1 to 20 do
     for i:=1 to n do jump[i,j]:=jump[jump[i,j-1],j-1];
   //
   read(q);
   for j:=1 to q do
   begin
      read(m);
      for i:=1 to m do read(a[i]);
      for i:=1 to m do flag[a[i]]:=true;
      sort(1,m);
      virtree;
      tl:=0; ans:=0; ans_min:=maxlongint; ans_max:=-ans_min;
      dp(1);
      writeln(ans,' ',ans_min,' ',ans_max);
      ll:=0;
      for i:=1 to m do flag[a[i]]:=false;
      for i:=1 to tl do la[que[i]]:=0;
   end;
end.
——by Eirlys


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值